Events with MooTools - Element, Class, Delegation and Pseudos

Written by Arian Stolwijk on 28 March 2011 – Posted under all, tips

One of the most useful and common part of MooTools is its Events Type. There are two Event usages: Element and Class. Element.Events is probably the most known because that's probably one of the first things you've used when you started using MooTools. Furthermore, MooTools More 1.3 Events.Pseudos has been introduced to give even more power and control over Events and with Event Delegation can give your page a massive performance boost. This blog post will give you a deeper insight into all components.

Element.Events

Element.Events represents DOM node event abstraction. The most simple example is, of course, adding a click event to a DOM Element:

For the sake of completeness I'll explain this code step by step. First we grab the a element with the document.id method. Then we call the Element:addEvent method. The addEvent accepts two arguments: the event name, which is a string without the 'on' part (click, keydown, mouseover) and the callback, which is a function. This function will be called when the user clicks the element.

There are two interesting things you see there: first, the event argument of the callback function. This is an instance of the Event Type. This is a wrapper of the native event object so we do not have to worry about browser inconsistencies. In this case the stop method is called to stop all default behaviors, like following the link. The second interesting thing is that we can use the this variable in the event callback which refers to the clickme element.

Removing Events

Once you've added events to an Element, you might want to remove them some time later. Removing events is pretty straightforward. We have to store a reference to the function if we only want to remove this single event.

// store a reference to the callback
var myCallback = function(event){
    alert('hi');
};

// add the event
myElement.addEvent('click', myCallback);

// remove the event
myElement.removeEvent('click', myCallback);

You can remove all the click events when calling the removeEvents method.

myElement.removeEvents('click');

This is an interesting example what you can do when you only want to click an element twice:

Firing Events manually

It is possible to fire events manually with the fireEvent method. This is not a very common method but can be useful.

The following example is how you can call events you've added to an element manually:

It is important to note that the event callback of the clickme event checks if the event.stop method exists. When using fireEvent you're not calling the callback through the DOM but directly with pure JavaScript. You can pass additional arguments as a second argument of the fireEvent, e.g:

clickme.fireEvent('click', [{stop: function(){}}, 'MooTools'], 500);

What am I doing here? The second argument of the fireEvent accepts a single argument or an array of arguments. In this case, I pass an array of arguments, which contains an Object as the first item in the array. The Object helps get rid of the if (event && event.stop) condition in the former example because it mimics the Event object. In the forge there is a more sophisticated Event.Mock.

Additionally, I've passed a third argument to fireEvent, 500. This third argument is used to delay the execution of the event callback.

You don't necessarily have to use existing events like click or mouseenter. You can fire an event like grow as well. If you add an event grow to the element it will work fine:

clickme.addEvent('grow', function(height){
    this.tween('height', this.getStyle('height').toInt() + height);
});
clickme.fireEvent('grow', 30);

Clone Events

An even lesser known method is the cloneEvents method. This method can be used to clone the events from the first element into the other, for example, after using the clone method.

How Events Work Internally

The methods in Element.Events: addEvent, addEvents, removeEvent, removeEvents, fireEvent and cloneEvents, which are described above, are actually wrappers to native browser methods. Unfortunately those native methods are inconsistent across browsers. Most browsers use the W3C specification addEventListener except IE 8 and lower which uses the attachEvent method. A MooTools wrapper for the addEventListener is called addListener. This is a private method that will give us a cross-browser implementation for addEvent.

Another reason why we use our own methods is garbage collection. This happens when adding events to elements and the elements are removed from the DOM later. The events are still there, but the element is not; this might cause to memory leaks. Fortunately MooTools handles this problem and you don't need to manually fix this issue.

Finally MooTools stores all the event callbacks using the store and retrieve methods. You could retrieve the object with all event callbacks using myElement.retrieve('events'). This is private and not recommended because it could break your code sooner or later. The reason why MooTools stores all the callbacks is so you can easily remove, fire, or clone them without passing a reference to the callbacks each time.

Event Bubbling

An interesting thing about the DOM is that elements can be nested. So what will happen if you add events to an element and to the element's child element? What will happen is called 'Event Bubbling'.

This is a quick example:

In this example there are two different elements, where the a element is nested into the div element. Each element has its own click event. Now when you click the a element, both events are fired, first the event of the a element, then the event of the div element. Also note that we cannot use the Event:stop or Event:stopPropagation methods anymore because this will block bubbling, we should use Event.preventDefault in this case.

This is actually nothing special of MooTools, but this is how it's specified by the W3C. The W3C specification has a capture phase too, that will go the other way around. Unfortunately MooTools cannot support this because IE 8 and lower doesn't support it. The three phases are clearly explained in this diagram

Event Delegation

Event Delegation is a technique that uses event bubbling and the event.target property. The target property is always the element which is clicked (or hovered when using mouse* events). What you can do is add an event to one of the parent elements, which will fire too because of the bubbling, and check of the target property satisfies your constrains, for example if it matches a CSS selector.

A very basic example of event delegation is:

This example is very simple, but it can get more difficult with more complex delegates. That's why MooTools More has the Element.Delegation component. This component makes it easy to use delegation.

This example uses Delegation. You can see that instead of using just click, click:relay(tr td.click) is used. With the Event Pseudo (will be explained later) relay we tell MooTools to check if the target or one of its parents matches the passed selector tr td.click. If the element is matched the callback gets fired.

Event Delegation has some important advantages. It especially improves performance. Imagine a page with hundreds of links or table rows. Instead of adding an event to every element, you can delegate the parent element by adding only one event. Secondly Event Delegation is useful when working with dynamic pages. In case you have the same large table which will update regularly by XHR Requests, you don't have to add new events to the rows because the rows are added into the table which has the event. The click event will bubble up to the table element and when the new row is matched against the given selector the callback function is fired. This can save you a serious amount of code and logic.

Class Events

Now we have seen how Element Events work, we'll explore the Class Events. Class Events brings similar methods like addEvent, addEvents, removeEvent, removeEvents and fireEvent to MooTools Class.

First an example from the docs to illustrate how it works:

var Widget = new Class({
    Implements: Events,
    initialize: function(element){
        // …
    },
    complete: function(){
        this.fireEvent('complete');
    }
});

var myWidget = new Widget();
myWidget.addEvent('complete', myFunction);

So what does this do? First it implements the Events mixin. The Implements mutator will copy the methods of the Events mixin into the Widget Class. In the complete method it uses the fireEvent to fire all the added 'complete' events. Once the Widget class is instantiated the example uses the addEvent method to add an event 'complete' with the callback myFunction.

When you want to pass arguments to the callback function, you can use the second argument of the fireEvent method, which accepts any value for a single argument or an array for multiple arguments, just like Element:fireEvent.

addEvent Internal argument

This is a fairly unknown but useful feature. The third argument of the addEvent method is called the internal argument. This argument can be set to true when you do not want that the event can be removed.

Imagine you're building a datepicker class. You split it up in two classes, a Picker class and a DatePicker class. The Picker Class is a generic picker which could be used for a ColorPicker or whatever picker too. It has an open method which opens the picker and fires the 'open' event. Now the DatePicker has to do some extra things on open. Now it could overwrite the 'open' method, but it can add an 'open' event too. Though you do not want that the user can remove the event when using myDatePicker.removeEvents('open'). In this case you set the internal argument to true.

Adding Events with Class Options

If you've used Fx or Request you definitely have used this: events as options when instantiating a Class. This are functions which have property names like onEventname where Eventname (the first character should be uppercase) is the actual eventname and is prefixed by on.

new Fx({
    onComplete: function(){
        // Will get fired when the Fx is complete
    },
    onPause: function(){
        // The Fx is paused
    }
});

The setOptions method of the Options mixin will automatically detect this and use the addEvent method.

var myWidget = new Class({
    Implements: Options,
    options: { // Default options and events
        onComplete: function(){
            // Will get fired when the class is complete
        },
        onPause: function(){
            // The class is paused
        }
    },
    initialize: function(options){
        // use the setOption method 
        this.setOptions(options);
    }
});

Events Pseudos

We've seen already a glimpse of Events Pseudos when using Event Delegation. This is a new component in MooTools More 1.3. It allows you to use event names as CSS Pseudo Selectors, thus event:pseudo(value).

A quick example how to use the :once pseudo:

Behind the scenes

Because the Element and Class methods are so similar, Events Pseudos is actually implemented once for both. It is totally abstracted to a single (private) function Events.Pseudos. It returns an object with modified addEvent and removeEvent methods. These methods are implemented into Events and Element, so it monkey patches the old ones.

When using one of the methods it will detect if the eventname is a pseudo eventname, if that's the case the eventname is parsed by Slick.Parse to an object. Finally it will add an event to the Class or Element with a custom callback. That callback fires the corresponding pseudo function. That pseudo function is actually a function in between that can do some extra stuff.

Event Pseudo Functions

The 'function in between that can do some stuff' is actually the power of Events Pseudos. Like the :once pseudo function it will fire the passed callback function and remove itself, so the callback function can only be fired once. Another existing pseudo function for elements is :keys which waits till all passed keys are pressed until firing the callback. These psuedo functions can be defined by Events.definePseudo or Event.definePseudo. In MooTools More 1.3.1.1 the :pause and :throttle events were added.

At this point we come back at Event Delegation, because the :relay(selector) is a Pseudo Function too! That pseudo function basically uses Slick.match to check if the element matches the selector and should fire the callback function.

Creating your own Pseudo Functions

You can define your own pseudo functions. This can be done with the Events.definePseudo or Event.definePseudo functions. The first one for Class Events, the second one for Element Events.

This is an example how to create a pseudo event which only allows you to click an element with the right mousebutton based on the mousedown event:

This is a fairly simple example, but it is clear that you can do a lot of things with it while keeping your code clean and simple to read.

Another example that counts the number of clicks:

You can see that var event = args[0]; is used. The args argument is the argument that keeps the usual arguments passed into a normal event callback. Since we're working with Element Events it has only one, so args[0] is the Event instance. Of course this argument can be different when working with Class events or when using the fireEvent method with multiple arguments.

The split argument contains an object with the parsed event name: event:pseudo(value). You can use the split.value property for example if you use the ( and ) for passing an extra value (like :relay(selector) for Event Delegation). The split.original property contains the whole string, so event:pseudo(value) which can be used to remove the event in the pseudo function, which is actually what the :once pseudo function does.

Finally we use fn.apply(this, args) to call the callback function (which highligts the a element). We use apply to be sure that this refers to the element and it is called with the same arguments as usually. You can do something else of course, but in most cases you want to use fn.apply(this, args).

Conclusion

Events are very powerful and you almost cannot write MooTools code without using Events. For the DOM, MooTools saves you a lot of pain and brings you the easy cross-browser API. Because of the nature of the DOM, it brings Event Bubbling which can be used for Event Delegation which can bring you several advantages. A unique part of MooTools is its Class, which brings the same API of DOM Events to Class. To give you even more power to Events, Events Pseudos can be used.

Quick tip

Also have a look at Company by Keeto or the PubSub pattern with the MooTools-Channels as MooTools implementation.

comments powered byDisqus