Low Pro Behaviours 101: Part 2

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.

13 Comments (Closed)

Dude, this is flippin sweet. I have been throwing things around and so far haven’t come up with anything elegant to do this. I feel like this is a great step in the right direction. Really nice work!

John NunemakerJohn Nunemaker at 18.07.07 / 18PM

Dan, this is a really interesting framework… I have been experimenting with how to attach some events in an unobtrusive way, and yours seems very clean and makes a lot of sense. I plan to experiment with this a good bit. Please keep the writing about ‘remote’ and ‘lowpro’ flowing, as it is very helpful towards adopting this ‘unobtrusive’ methodology. Many thanks-

Mark HoltonMark Holton at 19.07.07 / 19PM

Dan,

This is really amazing stuff… I think you’re my new hero! I feel like there is a rumbling in the web community as folks grabble with question “where is state in a web application?”.

Some folks (like seasiders ) are deciding that state lives in the backend app and is communicted to the frontend user via cookies / session.

Lowpro seems like a clear move in the other direction: the state of data the user is working with is – the html data in front of the user (possibly with periodic checks to the backend where the data are persisted).

Correct me if I’m wrong – but does this make the view ‘object’ a combination of data (HTML elements on the page) and behaviours (low pro Behaviours added using javascript)?

TrekTrek at 20.07.07 / 16PM

Great work. I’ve been looking for a good prototype behaviour framework for a while for my current project (xero.com), and this looks to fit the bill nicely.

AdamAdam at 22.07.07 / 04AM

Very nice.

Typos: “comunicate” -> “communicate” “If the idea of that doesn’t bore your rigid” -> “…doesn’t bore you rigid”, I should think. ;)

Henrik NHenrik N at 28.07.07 / 19PM

Dan: Seems your comment preview displays single line-breaks as being honored, though they don’t make it to the published comment.

Henrik NHenrik N at 28.07.07 / 19PM

Hey dan, any idea when you’ll be updating lowpro to take advantage of some of the new Dan-Webb-inspired stuff in prototype 1.6?

TrekTrek at 16.08.07 / 19PM

Thanks for very helpful tutorial now I know how to do it in a very fast time! Keep up the great work. Regards Tomasz Gorski

Tomasz GorskiTomasz Gorski at 19.08.07 / 00AM

I love Low Pro and would like to contribute an idea I had.

You know what I’d like? If I could create event handlers like this:
element.observe("click|focus", function(){...});
or maybe
element.observe({"click","focus"}, function(){...});
To handle any number of events in one line. That would be sweet. Something for Low Pro maybe?

Mats LindbladMats Lindblad at 23.08.07 / 08AM

Great framework ! Just one question, is it possible to add behaviors to inline svg in the xhtml page ? That would be awesome.

BastienBastien at 30.08.07 / 16PM

Bastien: I’ve never tried but there’s nothing immediately that I could think of that would stop it working. Definitely worth a try.

Dan WebbDan Webb at 07.09.07 / 16PM

This is top notch. I can tell already that this is going to be a new standard in my toolbox.

Why is it that though everyone knows the JS side of the whole web technology cluster f. so obviously needs better treatment and yet you seem the lone voice on the prairie? The one beacon of hope.

Keep it up Dan, this is top quality and oh so needed. Thanks!

Loren JohnsonLoren Johnson at 14.09.07 / 06AM

The lowpro library is great. I remember seeing a behavior.js library a long time ago that did a similar thing, allowing you to add event to dom selectors. But this one is much more powerful. Im checking out your code and can see you have some tests. Ive experimented with selenium before so know the general idea, but can’t get the test to run. When I load up the testsuite I only see one ExampleTest. Was wondering how I could get the tests to run? I would love to mess around with the library and contribute back.

Tung NguyenTung Nguyen at 21.09.07 / 00AM

About This Article