Darren Ferguson - Blog
16 February 2009 at 08:01
Adding jQuery autocomplete to TeamSite Formspublisher
The TeamSite Formspublisher GUI is getting a little old. Although extremely powerful when it comes to capturing user input, it hasn’t always kept up with some of the advancements in Web GUI.
Recently, I was asked to add a select dialogue to a TeamSite data-capture template that contained hundreds of options. Out of the box you could either have a massive scrolling select box or a callout that launched a pop-up window and displayed the data in some custom fashion, neither of which are a particularly pleasant user experience.
The obvious UI pattern for choosing from a large data set is an “auto complete” control. jQuery provides a plugin to do all of the hard work for us.
After a bit of hacking around I managed to get everything working nicely in TeamSite.

Getting this working isn’t completely straight forward so I’ve decided to share the process here.
Start by placing the jQuery files an any plugins that you want to use within Formspublisher in iw-home/iw. Include jQuery within your DCT as follows:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE datacapture SYSTEM "datacapture6.0.dtd"> <data-capture-requirements> <ruleset name="dct"> <script language="javascript" type="text/javascript" src="/iw/jquery/jquery-1.3.1.min.js" /> <root-container location="..." name="...."> .... DCT Structure here ..... </root-container> </ruleset> </data-capture-requirements>
Now we need to add some JavaScript to the DCT:
var o = {};
o.iwInitialised = false;
o.loadedScripts = 0;
o.stylesheets = new Array(
'/iw/mi_custom/jquery/plugins/jquery.autocomplete.css
');
o.scripts = new Array(
'/iw/mi_custom/jquery/jquery-1.3.1.min.js',
'/iw/mi_custom/jquery/plugins/jquery.autocomplete.pack.js'
);
o.formField = null;
o.data= [];Above we are just setting up some variables. You’ll notice a list of scripts and CSS files we want to use within Formspublisher and a few basic state variables.
The next thing we want to achieve is to wait until both the FormAPI onFormInit event has been fired and the DOM is fully loaded in order that we start our manipulations:
IWEventRegistry.addFormHandler("onFormInit", o.iwInitalise);
$().ready(function() {
o.waitForIwInitialise();
});
o.waitForIwInitialise = function() {
if(!o.iwInitialised) {
setTimeout('o.waitForIwInitialise()', 500);
} else {
o.ready();
}
}The code above instructs jQuery to periodically check whether the o.iwInitialised variable has been set to true by the formAPI onFormInit event before executing the next steps. The onFormInit code looks like this:
o.iwInitalise = function() {
o.iwInitialised = true;
o.formField = IWDatacapture.getItem('my/text');
o.formField.setReadOnly(true);
}Note that as well as setting a flag to say that Formspublisher is initialised I am also disabling the field that we will apply auto-completion to. I had issues when a user would start to type before auto complete was fully initialised.
Next we’ll get some JSON from the TeamSite server that contains all of the values for our auto-complete field:
o.ready = function() {
$.getJSON('/iw/data/city.json', function(data) {
o.data= data;
o.loadStylesheets();
});
}I am storing my JSON in iw-home/httpd/iw. The jQuery auto-complete plugin allows for a number of different JSON formats but I am just using an array of cities for purposes of demonstration.
A TeamSite DCT is rendered to the browser as a series of iFrames. Our next step is to inject the Javascript and CSS that we need into the iFrame containing the form that makes up our DCT.
o.loadStylesheets = function() {
var f = window.top.formframe.document;
var head = f.getElementsByTagName('head')[0];
$(o.stylesheets).each(function() {
var script = f.createElement("link");
script.setAttribute("rel", "stylesheet");
script.setAttribute("type", "text/css");
script.setAttribute("href", this);
head.appendChild(script);
});
o.loadScripts();
}
o.loadScripts = function() {
var f = window.top.formframe.document;
if(o.loadedScripts < o.scripts.length) {
var head = f.getElementsByTagName('head')[0];
var src = o.scripts[o.loadedScripts];
var script = f.createElement('script');
script.setAttribute('src', src);
script.setAttribute('type', 'text/javascript');
o.loadedScripts++;
script.onreadystatechange= function () {
if (this.readyState == 'loaded') o.loadScripts();
}
script.onload = o.loadScripts;
head.appendChild(script);
} else {
o.topFrameLoaded();
}
}A few things to note about the functions above. We are targeting our CSS and scripts at window.top.formframe.document which is where the DCT form resides. The list of Javascript and CSS files is taken from our configuration variables so you could reuse this code to add any jQuery plugins that you wish to use.
CSS is being loaded in a fire and forget fashion, we are not checking it’s load state before proceeding to load JavaScript, this is only because I’m not sure how to do it. This could expose us to the styles not being loaded before the user starts to interact with the form but I haven’t experienced this to date. The code to load JavaScript is a bit smarter, only proceeding once all scripts are loaded.
You’ll note that we’ve loaded jQuery again as we are interested in manipulating the DOM in another iFrame. The best way that I have found to do that is simply to have an instance of JQuery within the frame.
Now we have all of our pre-requisites loaded the final step is to enable auto complete.
o.topFrameLoaded = function() {
var f = window.top.formframe;
f.document.mi0 = {};
f.document.o.data = o.data;
f.$('input[name=my/text]').blur();
f.$('input[name=my/text]').autocomplete(f.document.o.data, {matchContains: true});
o.formField.setReadOnly(false);
f.$('input[name=my/text]').focus();
}Here we are finding a reference to our text field in the DCT and enabling auto complete. We are also re-enabling the field now that all is ready. I found that I had to blur the field prior to auto complete initialisation through some trial and error.
Obviously the auto complete plugin has many options and can be configured however you wish.
Finally, for bonus points you can ensure that the user enters a value from your list of options using the following bit of formAPI.
IWEventRegistry.addItemHandler('/my/text', 'onItemChange', function(item) {
var v = item.getValue();
var found = false;
$(o.data).each(function() {
if(v == this) {
found = true;
return;
}
});
item.setValid(found);
});Anyway, I hope you found this helpful. For me it opens the door to give Formspublisher some extra usability.
Written by: Darren Ferguson
16 February 2009 at 12:46
I'll be looking at adding some more plugins to formsPublisher where appropriate.
16 February 2009 at 13:01
C.
16 February 2009 at 15:49
C.
16 February 2009 at 16:51
16 February 2009 at 16:55
Regards,
C.
16 February 2009 at 17:12
Is this IE compatible? It could be the particular corporate rollout of IE I am forced to use, but the part of the script that defers loading doesn't appear to be working for me. If I rework o.loadScripts to execute as 'fire and forget', it works for me, but obviously if I could get deferring to work, that would be ideal.
Great stuff, I'm looking forward to digging into it.
Chris
16 February 2009 at 18:14
16 February 2009 at 18:59
script.onreadystatechange= function () {
if (this.readyState == 'loaded' || this.readyState == 'complete') o.loadScripts();
}
Thanks for this!
18 February 2009 at 18:01
It looks like perhaps readystate should be checked for interactive as well to be 100% compatible with IE.
Works a treat, having a blast with it! Think we can get Interwoven to roll Jquery out with TeamSite ;)
19 February 2009 at 15:35
13 August 2009 at 07:20
18 August 2009 at 08:19
Let me take this back to the engineering team and figure out how to make this easier for you in the future.
Stay tuned..
06 March 2009 at 23:21
03 September 2009 at 05:18
I tried implementing the same, but for some reason, it is not working. I would appreciate if you may kindly provide the necessary guidance in order to achieve the functionality.
Regards...
17 June 2010 at 19:22
31 October 2011 at 15:35