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.

jQuery auto complete in TeamSite Formspublisher

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.

Comments

Leave a comment