It’s The DataLink That Binds Us – jQuery DataLink

Recently I posted two articles about one of the new official jQuery plugins called jQuery Templates here and here. Another of these plugins is the jQuery DataLink plugin (Download from GitHub). Essentially what the DataLink plugin does is create a two way, although you can configure it to be one-way only, link between a JavaScript object, a form and it’s elements. So what do I mean by this exactly? Well, it means that when any of the values in the form changes, the representative values in the underlying linked JavaScript object is also change and vice versa, if the data in the underlying object changes, the values of the form will be updated to reflect this.

The plugin consists of two functions link() and unlink(). If you head over the the jQuery Documentation for the link() function and look at the first bit of code it can be a bit misleading. If you attempt to run this, you won’t get the results you are expecting:

var person = {};
$("form").link(person);
$("[name=firstName]").val("NewValue"); // Set firstName to a value.
(a) person.firstName; // NewValue

// User types a value into the form field.
(b) person.firstName; // firstName now contains the user-ented value.

What the above code suggests is that if we print the value of firstName at (a) it will have the value of ‘NewValue’. Once the user has entered something into the field then (b) will have the value that the user has entered. While the second will be true, depending on a condition I will go into in a second, the first one is not. Just to take a quick detour, the names of the properties of the object will be determined by the ‘name’ attribute on each input field. So, when looking at the code above in it’s current context, it tends to suggest that the line:

$("form").link(person);

Will parse the form’s HTML and add properties to the person object for each field of the form and these properties will then be named based on the ‘name’ attribute of each field as mentioned above. This is not the case however. Let’s look at what is happening. Create a new HTML page and add the following:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>jQuery DataLink</title>
</head>
<body>
    <h1>jQuery DataLink</h1>
	<form name="personLink" id="personLink" action="" method="post">
		<fieldset>
			<label for="name">Name:</label><br />
			<input type="text" name="name" id="name" /><br />

			<label for="surname">Surname:</label><br />
			<input type="text" name="surname" id="surname" /><br />

			<label for="landline">Landline:</label><br />
			<input type="text" name="landline" id="landline" /><br />

			<label for="cellnumber">Cell Number:</label><br />
			<input type="text" name="cellnumber" id="cellnumber" /><br />

			<input type="submit" value="Submit" id="print" />
		</fieldset>
	</form>
</body>
</html>

Next in the head of the document add the following:

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.js"></script>
<script src="js/jquery.datalink.js"></script>

Next we add a script block with the code above wrapped in a document.ready function:

<script>
$().ready(function() {
	var person = {};
	$("form").link(person);

	$("[name=surname]").val("Enter surname");

	$("#print").click(function(event) {
		event.preventDefault();
		console.log(person);
	});
});
</script>

The above is then exactly the code from above, with the person.firstName lines left out, you can leave them in if you want it won’t make change the result, along with a small function that bind to the submit button and prints the person object out to the console. Open up the demo page and click on the submit button. Inside Firebug, Web Inspector or the relevant tool for the browser you use, expand the object. It will look as follows:

Firebug screen shot of Object

As you can see from looking at the object above, there is a changeData event that is bound to the person object but, even though the line below was executed, none of the form’s fields are defined as properties of the object and there is no surname property with the value of ‘Enter surname’.

$("[name=surname]").val("Enter surname");

If we replace the ‘$(“[name=surname]“).val(“Enter surname”);’ with the following line:

person.surname ="Neethling";

Our logged object now looks as follows:

Updated person object

You can now clearly see that the surname property of the person Object exists and contains the correct value. What you will also notice however is that the form field has not been updated to reflect this value. With all of this in perspective, the second statement of the following from the jQuery DataLink plugin page on Github may be seen as a false statement:

Any changes to the form fields are automatically pushed onto the object, saving you from writing retrieval code. By default, changes to the object are also automatically pushed back onto the corresponding form field, saving you from writing even more code.

But surely the jQuery team would not have chosen to make the DataLink plugin an official plugin if it had a bug that broke one of the core pieces of functionality provided by DataLink. And after much research and debugging of the jQuery DataLink plugin, I am happy to say that they did not. DataLink provides exactly the functionality it claims to do, the problem is the documentation. I will not go as far as saying the documentation is wrong but that a very core part of the documentation is incorrect and leaves off a very important aspect that is tripping everyone up. So how does DataLink work then?

link()

Let’s first look at getting data from the form to the Object. As we have when the document is loaded and you link the form to the Object the object is not automatically filled with properties based on the fields in the form. Whether this would be useful or desired, I don’t believe there is much to gain from this. One reason you may think it would be useful is for form validation but, even though the object is not populated you first check will generally be to ensure a field has been filled in.

Now if a user did not interact with the field accessing the property on the object will return undefined so, you can do the following:

if(person.name === "undefined") {
   alert("Please enter a first name");
}

Ancient style messaging but, you get the idea. So when does a field become a property of the object. Let’s look at the following line again:

$("form").link(person);

Once this line is executed there are two functions bound, one on the object you passed into the link() method and another on the form. Let’s have a look at the code inside DataLink:

self.each(function() {
	bind( this, $(this), handler );
	var link = {
		handler: handler,
		handlerRev: hasTwoWay ? handlerRev : null,
		target: target,
		source: this
	};
	getLinks( this ).s.push( link );
	if ( target.nodeType ) {
		getLinks( target ).t.push( link );
	}
});
if ( hasTwoWay ) {
	bind( target, $(target), handlerRev );
}

The important lines in the above are the two ‘bind’ event calls. The first binds to the form and the second binds to the object if you have not passed in an options map and set twoWay to false. If you add console.log command before each of the ‘bind’ function calls and logged out the jQuery objects you would see the following:

DataLink binding

Next let’s log out each the objects to have a look at their events:

console.log($("form#personLink").data("events"));
$(person);

You can simply run the above commands inside Firebug’s console. Once the command has executed, click on the first Object which is the form and then click on the second Object which is the person Object.

DataLink bound events

As you will see in you console, as well as the image above, a change event is bound to the form and a changeData event is bound to the person JavaScript Object. Looking at the person Object, you will also see that as explained before, none of the form elements have yet been added as properties of the person Object. So this was quite a long, but needed, detour to get to the point where you might ask, Ok, but when does the form elements become part of the Object.

If you read about the DOM level 2 change event you will discover that it is triggered whenever a control looses focus and it’s contents has changed since it last gained focus. In a nutshell, if you click into the name field add some text and move to the next input field, the change event will be fired. The change event will then bubble up until it reaches the form which will stop the event from bubbling further as a handler for the change event has been bound to the form.

It is then at this point that the field will become part of the JavaScript Object. To test this, enter some text into the name field and move out of the field so that it looses focus. Next run the following line from within the console:

$(person);

You should now see something like the following:

Field set on Object

As you can see from the above the name field has now been added to the person Object along with it’s value. As you proceed through each of the form elements each of the other fields will be added in turn. For the most part of the jQuery DataLink plugin has not presented a problem to most. The part of the plugin that has tripped up many is the other side of the DataLink i.e. when changes are made to the Object these changes needs to be pushed back to the front end UI.

The Other Side of The Link

Let build a simple translation app using Google’s AJAX Language API. For this we will create a new Object and link it to our new form. The aspect of this that is going to be very different to how you would usually go about building something like is, that in the end, we will never write any code that get or set any values on the form elements, this will all be handled with jQuery DataLink. Create a new HTML document and add the following inside the body tag:

<form name="translator" id="translator" method="post" action="index.html">
	<fieldset>
		<label for="text">Enter text to translate</label><br />
		<textarea name="text" id="text" cols="150"></textarea><br />

		<label for="translation">Translation</label><br />
		<textarea name="translation" id="translation" cols="150"></textarea><br />

		<input type="button" value="Translate" id="translate" />
	</fieldset>
</form>

Next import jQuery, the Google API, the DataLink plugin and the script block:

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.js"></script>
<script src="js/jquery.datalink.js"></script>
<script src="http://www.google.com/jsapi?key=YOUR-API-KEY"></script>
<script>
        google.load("language", "1");

	$().ready(function() {

	});
</script>

Next we create the Object and link the form and the Object together:

var translation = {};
$("#translator").link(translation);

Next we bind a click event to the button:

$("#translate").click(function(event) {
    event.preventDefault();
});

Next we will add the code that will handle the translation for us right after the event.preventDefault() line:

$(".error").remove();

if(translation.text !== undefined) {
  google.language.detect(translation.text, function(result) {
    if (!result.error && result.language) {
      google.language.translate(translation.text, result.language, "en", function(result) {

      });
    }
  });
} else {
  $("<p class='error'>Please enter the text to be translated.</p>").insertAfter($("#text"));
}

So, there’s some lines in here we need to discuss further. Skipping the very first line for now, on the next line we are doing some basic validation. As discussed earlier, until someone has triggered the change event by adding some content to a field and moving focus to another part of the form, none of the form fields will be mapped onto the Object yet so, by testing whether translation.text is undefined, we will no whether this field is empty and if it is, we present the user with a message. So getting back to the first line, this is simply required to remove any error message that might have been added earlier.

Assuming we have passed the validation and some text was entered into the first field, we can proceed into the ‘if’ statement. Let’s look at the following line:

google.language.detect(translation.text, function(result) {

This line sends the text we want to translate over to Google to first determine what language it is. As you can see there is no need to first read the value from the input element as it is already stored on the JavaScript object. After we get the result back we send the text over to Google to be translated into English, again no need to read from the input element, we just read it from the Object. The next step in the process is to display the result of the translation to the user.

Using DataLink we should simply be able to set the translation property of the object and it will push the data to the form element. This is where the documentation is incorrect, using either:

(result.translation) ? $("#translation").val(result.translation) : $("#translation").val("No result found");

or

(result.translation) ? translation.translation = result.translation : translation.translation = "No result found";

Will not cause the desired effect. The first will update the form field but, as it will not cause the change event to be triggered, the Object will not be updated. The second will set the value on the Object but it will not push the change to the UI, but why not? If you remember when I discussed the events bound to the form and the Object, the one bound to the Object is the changeData event. Unlike the change event discussed before the changeData event is not part of POJ (Plain Old JavaScript).

The changeData event is a new jQuery custom event added to the .data() function specifically for the jQuery DataLink plugin. The jQuery .data() function is used extensively by jQuery itself internally to store information on DOM elements to keep track of for example events bound to the object as well as callback handlers that should be invoked etc. The other aspect of .data() is that it fires custom events for when data changes happen. One of the events that is fired is the setData event and the DataLink plugin was using that event to be notified of changes to the Object so that it can push back the changes to the source element, in this case, form elements.

There is a problem with this though because the setData event fires before any data has changed, and this presents a problem to the functioning of the DataLink plugin. With this, the new changeData event was added to solve this exact problem. With that, here is how the reverse side of the DataLink works. Add the following code inside the ‘google.language.translate’ callback:

(result.translation) ? $(translation).data("translation", result.translation) : $(translation).data("translation", "No result found");

With this go ahead and open up the demo page. As we are translating to English the language we provide should not be in English. In the top textarea paste the following:

Se réveiller quand vous avez un bébé, vous vous sentez comme vous avez bu une bouteille de whisky la veille, à l’exception de la merde dans un pantalon de quelqu’un d’autre.

Click on the translate button and watch the magic happen. As you can see from this, the DataLink plugin is extremely powerful and I can think of at least a handful of uses for this already. There is even more to the DataLink plugin, which I will cover in a follow on article, such as the options map you can pass in as well as conversion functions.

unlink()

Before I end this article I want to quickly touch on the unlink function. Taking our example from above the unlink function will work as follows:

var translation = {};
$("#translator").link(translation);

//Some functions to make use of the linking. When you are done and no longer need the link
$("#translator").unlink(translation);

Summary

So in summary, to link a form to an object, we use the following code:

var translation = {};
$("#translator").link(translation);

Once a user has entered data into the form fields and caused the change event to be fired, the property, based on the name attribute of the form element, will be added to the object along with the value of the input and can be read as follows:

//Will print the result of the translation
translation.translation;

When receiving information back that needs to update the Object and as such the form elements, you use the following code:

//Using the data() function here is important as this will trigger the required changeData custom jQuery event.
(result.translation) ? $(translation).data("translation", result.translation) : $(translation).data("translation", "No result found");

If at some point you wish to break the link between the form and the Object use the following:

$("#translator").unlink(translation);

I hope this has helped out some developers out there that is struggling to make complete sense of how this all works, especially updating the UI when the underlying Object changes. I look forward to reading your comments.