Fun with Custom Events on Elements in MooTools

Tonight, I’ve been playing with the fantastic MooTools plugin, ReMooz by Harald Kirschner, with the hope of adapting it for my upcoming web portfolio. One of the pieces of functionality I was looking for was to close the zoom box when the user clicks outside of it. The solution I thought had the most Moo was to create a custom event1, clickout.

(update: I just discovered Jan Kassens blogged a similar custom event, outerClick. We took very different approaches, but I thought I’d give proper credit)

Element.Events.clickout = {
  base : 'click',  // attach click event to element
  condition : function(event) {
    event.stopPropagation();  // stop event from bubbling up
    return false;  // never run handler when clicking on element
  },
  onAdd : function(fn) {
    this.getDocument().addEvent('click', fn);
  },
  onRemove : function(fn) {
    this.getDocument().removeEvent('click', fn);
  }
};

(edit: fixed bug that prevented links from working inside the element, further updates will be made on GitHub here)

The condition of the custom event is executed each time the base event is fired to with a return value that determines if the handler should be run. The condition above prevents the click event from bubbling2 up to the body, where the handler will actually be attached in the onAdd callback. This effectively runs the handler passed into the clickout event when anywhere on the page is clicked except inside the element where the event was attached. For example, in ReMooz I wanted to add this event to the zoom box when it is opened.

ReMooz.assign('.thumb', {
  ...
  onOpen : function() {
    this.box.addEvent('clickout', this.close.bind(this));
  },
  onClose : function() {
    this.box.removeEvents('clickout');
  }
});

The event must be removed when the zoom box is closed so the click event attached to be body is also removed. If the zoom box didn’t have a close button in it, then I could have applied a singular event3 pattern like the jQuery.one() method, recreated in MooTools below:

Native.implement([Element, Window, Document, Events], {
  oneEvent : function(type, fn) {
    return this.addEvent(type, function() {
      this.removeEvent(type, arguments.callee);
      return fn.apply(this, arguments);
    });
  }
});

(edit: thanks to gonchuki for the idea of using arguments.callee, further updates will be made on GitHub here)

This allows you to attach an event that only will be run once and then automatically detached. The only problem with this method oneEvent() is that its implementation doesn’t allow you to remove a specific handler with the removeEvent() method; you must use removeEvents() instead to remove all events of a certain type the type you added with this method. jQuery doesn’t have this problem because it uses an event proxy, so you can still unbind a specific handler. With a little refactoring, MooTools could allow the same kind of functionality, though the real-world usage of this pattern is admittedly rare.

I deeply apologize for becoming so serious in this post, I’ll try to be funnier next time!

1 Check out the super cool demo and sample code for more info on custom events.

2 A quick refresher course on event bubbling, if desired.

3 I just made that term up. I hope it sticks.

About Me

I'm Scott Kyle, a MooTools developer and Apple addict. You can follow me on Twitter, fork me on GitHub, and check out some of my work on my portfolio.