Darren Ferguson - Blog

16 February 2009 at 08:01

Adding jQuery autocomplete to TeamSite Formspublisher

Tags: JSON , Javascript , DOM , GUI , Web GUI , completely straight forward
Author: Darren Ferguson

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.

Written by: Darren Ferguson

Comments

  1. Tom Wentworth says:

    Gravatar of Tom WentworthThis is very cool! With your permission, I want to add this to our standard demos so we can start showing autocomplete to all our customers.

  2. Darren Ferguson says:

    Gravatar of Darren Ferguson@Tom - by all means, please just give credit to jQuery where due. Thanks.

    I'll be looking at adding some more plugins to formsPublisher where appropriate.

  3. Chris Hardie says:

    Gravatar of Chris HardieCrazy - I was just looking for a tutorial on how to get Jquery plugins working within TeamSite last week. This is great stuff, many thanks.

    C.

  4. Chris Hardie (1) says:

    Gravatar of Chris Hardie (1)As an aside, have you tried this in Firefox? In o.LoadScripts(), the else block is never executed so o.topFrameLoaded() is never fired. Works like a charm in IE tho.

    C.

  5. Darren Ferguson (1) says:

    Gravatar of Darren Ferguson (1)@Chris - It is working fine in FF 3.0.6 for me!

  6. Chris Hardie (2) says:

    Gravatar of Chris Hardie (2)Deep-six my last comment, I had not updated the path to jquery in the config array and once I fixed it up everything began working as expected.

    Regards,

    C.

  7. Calum Smith says:

    Gravatar of Calum SmithHi,

    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

  8. Darren Ferguson (2) says:

    Gravatar of Darren Ferguson (2)@Chris/Calum - Works fine in IE for me. You can drop me over your adaptation via email and I can see if I can figure out the problem.

  9. Calum Smith (1) says:

    Gravatar of Calum Smith (1)I had to change one line in o.loadScripts() to make it more palatable to IE:

    script.onreadystatechange= function () {
    if (this.readyState == 'loaded' || this.readyState == 'complete') o.loadScripts();
    }

    Thanks for this!

  10. Calum Smith (2) says:

    Gravatar of Calum Smith (2)http://msdn.microsoft.com/en-us/library/ms534359(VS.85).aspx

    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 ;)

  11. Jagadeesh Raju says:

    Gravatar of Jagadeesh RajuDo you have any idea to achieve jquery datepicker in Teamsite DCT.

  12. Jagadeesh Raju (1) says:

    Gravatar of Jagadeesh Raju (1)Thanks Darren and Calum Smith. I implemented Datepicker jquery in DCT as described the above steps. Its working fine.

  13. Sunil Menon says:

    Gravatar of Sunil MenonThis is very nice and as the TeamSite Product Manager, I am happy to see interesting extensions being made to the product in the field..

    Let me take this back to the engineering team and figure out how to make this easier for you in the future.

    Stay tuned..

  14. GIrish says:

    Gravatar of GIrishVery Nice Implementation Darren, I am trying to use the Jquery effects in my DCR, It is like if I click on add replicant button the replicant should have a slide effect. I had used ur code with slight modifications but i couldnt achieve. Do you have any idea how to achieve?

  15. Amit Celly says:

    Gravatar of Amit CellyHi Darren,

    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...

  16. Paolo says:

    Gravatar of PaoloWTF, after hacking exactly the same functionality into our TeamSite over here, I find this very post I was planning to write myself.

Leave a comment