In the first post I briefly explained how to use pre-made Low Pro behaviours but, although I’m in the process of writing a few myself, Low Pro is not going to be a widget library – what it is is a great framework to write your own components with. So, in this post, I’m going to go into a little detail about how to write your own behaviours. If the idea of that doesn’t bore your rigid then read on…
Creating a behaviour
As discussed in the last post behaviours are actually just specialised constructor functions. So, to create our behaviour we use Behavior.create in a very similar way to Class.create:
var Hover = Behavior.create();
Adding methods and properties
This creates an empty behaviour much the same as Class.create gives us an empty class. Now we add our methods on to the prototype of the constructor that we’ve created. This example is a super simple behaviour, Hover, that will add/remove a class name (which you can specify) on mouse over/out:
Object.extend(Hover.prototype, {
initialize: function(className) {
this.className = className || 'over';
},
onmouseover: function() {
this.element.addClassName(this.className);
},
onmouseout: function() {
this.element.removeClassName(this.className);
}
});
Any on* methods are bound as event handlers to the element when the behavior is attached to the element. The scope of these methods is ‘corrected’ automatically so that this will always refer to the behaviour instance. The behaviour instance also has a magic property, this.element, which points to the element it is attached to. The final thing you need to know is that the initialize method is called as soon as the DOM is ready (as soon as the element is available). We can now use this behaviour in the manner discussed in the last post:
Event.addBehavior({
'.dongle': Hover('hover')
});
Aside from these special cases behaviour constructors are identical to any regular JavaScript ‘class’. You can add methods and properties as you see fit. Note that each element that has the behaviour attached to it gets its own instance of the behaviour so you can maintain the state of each behaviour independently without having to resort to setting expandos on the element, using closures or any other method which is messy and prone to causing those nasty circular references that cause memory leaks. As an alternative you can just pass your methods straight into the Behavior.create call so the following is equivilent to the above code:
var Hover = Behavior.create({
initialize: function(className) {
this.className = className || 'over';
},
onmouseover: function() {
this.element.addClassName(this.className);
},
onmouseout: function() {
this.element.removeClassName(this.className);
}
});
Working with multiple elements
Now this is all good for dealing with behaviours that work on a single element but in many cases we want a behaviour to operate on a number of elements. For example, you would want to be able to attach a Sortable behaviour to a list element but the behaviour itself would also need to deal with all the list element inside it. There are a number of ways to approach writing these kind of behaviours, all of which are demonstrated in the various test behaviours I’ve written.
Event delegation with behaviours
The first technique, and normally the best is using event delegation to capture all the events on elements inside the attached element with your behaviour and process them. The process is pretty simple…here’s part of the Calendar behaviour:
var Calendar = Behavior.create({
// other methods hidden for clarity
onclick : function(e) {
var source = Event.element(e);
Event.stop(e);
if ($(source.parentNode).hasClassName('day')) return this._setDate(source);
if ($(source.parentNode).hasClassName('back')) return this._backMonth();
if ($(source.parentNode).hasClassName('forward')) return this._forwardMonth();
}
});
Here, we define a single onclick method which will capture clicks on all the elements inside the calendar – Forward and back arrows, and the dates themselves. Firstly, we use Event.element to grab the source element, the element that was actually clicked, then we can just examine this element to work out what needs to happen to the calendar and call the relevent method. In this case, and in most cases, the most convenient way is to look at what class names the element has. In the calendar, each day has the class ‘day’ and the forward/back arrows have the classes ‘forward’ and ‘back’. This method is really nice for a number of reasons, firstly, we avoid attaching lots of event handlers for each sub element and secondly we have this central point where we can handle and dispatch events – a really pleasant way to work with complex interactions. Of course, event delegation isn’t always a good fit so…
Linking behaviours
In more complex cases when the sub elements of your behaviour need to maintain their own state or handle events in a more complex way you can have your main behaviour attach sub-behaviours to inner elements during initialisation and maintain a link between the two. An example of this can be seen in the Draggable behaviour:
Draggable = Behavior.create({
initialize : function(options) {
// code hidden for clarity
this.handle = this.options.handle || this.element;
new Draggable.Handle(this.handle, this);
}
// code hidden for clarity
});
Draggable.Handle = Behavior.create({
initialize : function(draggable) {
this.draggable = draggable;
},
onmousedown : function(e) {
// code hidden for clarity
}
});
Here, the initialize method of Draggable finds the element that it wants to be the ‘drag handle’ and attaches another behaviour, Draggable.Handle, to it. At this point we pass a reference to the Draggable instance to the Draggable.Handle instance so the drag handle can refer back to and control the main draggable behaviour. You can of course, if necessary, build up a complex component by building a number of linked behaviours that all comunicate back to the main behaviour.
Get it? Got it? Good
So that’s all for now. Any questions just drop me a comment and I’ll get back to you ASAP. At this point, if you are interested in writing your own behaviours then it would be worth referring to the behaviours in SVN. However, I’m just feeling this out for myself and I’m sure I’ve not explored any near all of the possibilities of working with behaviours so if you have any brain waves be sure to post a comment also. If there’s enough interest I might include a behaviours repository on the upcoming Low Pro site.