Arieh.co.il

Automating object creation - using Behavior to better decouple the server from the client

A while back, I came across a true jewel on the Mootool's user group – Aaron Newton's Behavior. Behavior is a Mootools class designated at automating the creation of JS objects on a page, using the new HTML5 data-api.

A moment before I published this article the author Aaron Newton finally published a new release of Behavior. This release comes with a much reacher toolset and also some major changes in the way you can write your filters. Since I haven't yet had the time to use it properly, this post might actually be a bit out dated, though the concepts introduces here are just as valid. I strongly recommend you read this post describing the awesome features that come with the new release.

The main idea behind Behavior is this – most generic web pages today work something like this – various elements get their own special class-name marking them as targets for later JS widgets. Then, at the bottom of the page (or at DOM ready, or at a separate file), we find them and send them to the widget's constructor. At its root, Behavior simply takes it one step further – instead of using a class-name, it uses a designated attribute (data-behavior), and then looks for defined filters for that piece of HTML. So, for example, this piece of code:

<input type="text" class="autocomplete"/>
var inputs = $$('autocomplete');
inputs.each(function(i){
	new Autocomplete(i);
});

Becomes this:

<input type="text" data-behavior="Autocomplete"/>
Behavior.addGlobalFilter('Autocomplete', function(el){
	new Autocomplete(el);
});

var b = new Behavior;
b.apply(document.body);

As you might notice, throughout the article I register my filters using addGlobalFilter. There are 2 reasons for this: 1) Since filters can be registered on multiple files, I don't want to be dependant on the location of Behavior's instance creation, and 2) in order to write a plugin for a filter (which is not covered here) that filter must already be defined.

It seems quite simple, but the potential is huge. First of all - it allows us to create an agreed and unobtrusive way for the server to tell the client what to do. Now, with every example that simple one might think that difference between class name and data-filters might actually seem silly, considering the overhead introduced by such a mechanism, but in fact, it is that simple concept that hold the entire power of the class on it's shoulders (and if you read further down I will list a few other advantages for this approach even before we extend it).

Now, there is a second step to take – when developing rich client side apps, we often have a scenario where a widget has a state – certain data unique to it (or simply some configuration data). Leveraging the HTML data-api, we can store this data on the elements, and provide it to the filters:

<input type="text" data-behavior="Autocomplete" data-url="/path/to/autocomplete.php" />
Behavior.addGlobalFilter('Autocomplete',function(el){
	var url = el.getData('url');
	new Autocomplete(el,{url:url});
});

Now that we have the basics, I want to address 3 major advantages that come with this approach:

  1. We have a single point of initialization, in which we can control what happens and when. This is extremely helpful when debugging our applications – each object in the page has a single point of construction – its filter.
  2. Decoupling of modules – since we don't actually write the construction code, but instead provide rules at which the code will run, each widget is completely isolated – injecting a new widget is simply a matter of adding a filter. If done correctly, this would also mean that no widget can actually directly interact with another.
  3. Since Behavior implements Events, we can use it as a pub/sub mediator between our Objects, further ensuring the decoupling of our widgets.

There is one last thing to mention about the basic usage of Behavior before we continue – and that is the second argument passed to the filters. As you might have noticed, Behavior requires an active activation – you must use the apply method on a given element for it to scan that element for filters. Some people find this to be a limitation, but I find it to be a very powerful feature. The problem comes when we want to add new content to the page, say using an AJAX call – we want to be able to apply the new filters. Now, we can create a global Behavior instance with a unique name and use it from all scopes, but this breaks the encapsulation of each filter, and so we get the API argument. The second argument of each filter is an accessor object to various useful methods we might want to use (such as the apply method or add/remove events). When designed correctly, we can use dependency injection to allow our classes to apply filters without worrying at all about how or where it happens:

var Widget = new Class({
	initialize : function(el, api, opts){
		this.element = $(el);
		this.api = api;
		this.setOptions(opts);
		
	}
	, update : function(){
		var $this = this;
		this.req = new Request({
			onComplete : function(res){
				$this.element.innerHTML = res.innerHTML;
				$this.api.applyFilters($this.element);
			}
		});
	}
});

Going forward

I've been using Behavior for quite a while now, and I've started implementing some advanced patterns that allows me to automate the creation of objects. One specific pattern I found to be very useful was defining my Classes into families that share the same factory. I'll try to demonstrate with an example. Most sites today use some sort of advanced form controls – be it styled select boxes, checkboxes, or datepickers, they all share various similarities when it comes to their construction, but change on the implementation of specific actions. This is a classic example of where we can use a Class. What I do is first create a basic template Class:

var Input = new Class({
	Implements : [Options,Events]
	, value : ''
	, template : '<input  class="input" type="text" />'
	, initialize : function(el,api,opts){
		this.element = $(el);
		this.api = api;
		this.setOptions(opts);
		this.generate();
		this.attach();
	}
	, generate : function(){
		this.container = new Element('div.input-container',{
			html:this.tempalte
		}).replace(this.element);
		
		this.input = this.container.getElement('.input');
	}
	, attach : function(){
		var $this = this;
		this.input.addEvent('change',function(){
			$this.set(this.value);
		});
	}
	, set : function(value){
		this.value = value;
		this.fireEvent('change',[value]);
	}
	, get : function(){
		return this.value;
	}
});

As you can see, this class does nothing more than dispatch events and values. What I do next is start adding various extensions to the family:

Input.Checkbox = new Class({
	Extends : Input
	, template : '<input type="checkbox" class="input" />'
	, set : function(value){
		this.input.checked = !!value;
		this.parent(!!value);
	}
	, get : function(value){
		return this.value;
	}
});

What we do is add each type into its parent namespace. This is a very common Mootools approach, and it has one key advantage – it allows me to create a common factory that is completely clueless of what it creates:

	Input.create = function(el, api, type){
			if (type && Input[type]) return new Input[type](el,api);
			return new Input(el,api);
	};

Last but not least, now that we have a generic factory, we can create a filter that automates its creation:

Behavior.addGlobalFilter('Input',function(el,api){
	var type = el.getData('type');
	Input.create(el,api,type);
});

When used correctly, this pattern is extremely powerful. We can now gradually add and augment various features on our site without touching the markup or the actual code of the site. I might start with no implementation for Input.Select, but then it would simply use the base class (a side effect of this approach is also that all communication with these instances is used through a very solid and simple API that doesn't change between various GUI implementations). Adding the feature will only affect the specific element using that filter, without affecting any other.

By now I hope you could start to see the power of this pattern – it allows us to add better decoupling for our code; it allows us to automate the creation of our objects; it gives us great tools for rapid development; when used correctly, it can make unit-testing a low easier; it makes our code cleaner, more organized, and easier to read; it creates a very clean separation from where our classes reside and where the actual construction code reside.

There's also a lot more to it. I haven't even touched the fact that Behavior handles other aspects that concern a long-living app (such as garbage collection). I haven't discussed plugins. In fact, the new release of Behavior holds some amazing new tools specifically designed for testing and benchmarking your filters.

But for the time being, I suggest you take it for a ride and play with it. You can also try and use my own fork, which handles an issue I discussed on a previous post regarding the way it calls its filters.

JavaScript Reference, JavaScript Guide, JavaScript API, JS API, JS Guide, JS Reference, Learn JS, JS Documentation