MooTools for Beginners Part 7 - Creating Flexible Classes Using Options, Events, and Event Management

By Ryan Florence, published 2010-01-27

Part of the issue MooTools for Beginners (1.2).

Last time we compartmentalized some code into a nice little MooTools Class called BouncyMenu that we can use anywhere. However, there are some HUGE improvements to be made. In this article we’ll be talking about how to make your MooTools Class flexible by using Implements, Options, and Events. We’ll also talk about managing events added to elements and some discussion about binding. If you still consider yourself a beginner, understanding this article should be a big deal. Once you master Class your javascript will never be the same.

Implements

MooTools exposes the prototypal inheritence of javascript with Class. There are two properties of Class used regularly to create really modular code called Implements and Extends. There are some major differences between them (for another post) but they both are similar in that they essentially copy all the methods of one class into another.

var ImageGallery = new Class({
	showImage: function(){
		alert('check it out!');
	},
	hideImage: function(){
		alert("I'm out like brown trout");
	}
});

var AwesomeGallery = new Class({
	Implements: ImageGallery,

	beAwesome: function(){
		alert('Holy crap!');
	}
});

window.addEvent('domready',function(){
	var gallery = new AwesomeGallery();
	gallery.showImage(); // alerts 'check it out!'
});

Options, choices are good

There are two very common mixins (that’s what they’re called) that are used by a lot of mootools classes: Options and Events. First we’re going to drop Options into our BouncyMenu from last time. We’re going to put the person using our class in control of the duration and distance of the animation.

var BouncyMenu = new Class({

	Implements: Options,
		
		options: {
			duration: 500,
			distance: 30
		},
		
	...

This sets up the defaults. If the user doesn’t set any of these options then the class will use what you put there. It’s important to choose good defaults. Next we have to set the options in the initialize function (remember, initialize gets called when the class is instantiated.) Don’t forget to pass the options in as an argument.

initialize: function(options){
	this.setOptions(options);
}

setOptions is inherited from Options and figures out if there were any options defined or if the instance ought to just use the defaults. Now, to access the options:

this.options.duration; // 500 or whatever you declare when constructed

So here’s a mooshell showing off our new functionality. Notice how we can define our own options when we instantiate the class. We define the duration but not the distance. So the class will use the default distance but our defined duration.

var menu = new BouncyMenu('container',{
	duration: 250
});

A good indication that you should make something an option is when you hard code in stuff like strings and numbers. In setupElements we use one of the options where before we hard-coded in some number:

setupElements: function(){
	this.elements.each(function(element){
		element.store('originalPadding', element.getStyle('padding-top'));
	
		element.set('tween',{
			// duration: 500 // old
			duration: this.options.duration // new
		});

	}, this);
},

Bind: Binding the class instance to functions

Whenever you see this in a class it should always reference the class instance. Don’t let your thiss get out of control! Consider this code:

var sauce = 'awesome';

var someFunction = function(){
	console.log(this); // DOMWindow
};

var someFunction = function(){
	console.log(this); // sauce
}.bind(sauce);	

So we see that bind allows us to control what this is. And again, this, in a class, should always reference the class so that you’ve always got access to its methods and properties (including its options.)

It just so happens that each takes two arguments, a function, and binding, so we use it a bit differently in our setupElements method:

setupElements: function(){
	this.elements.each(function(element){ 
		// inside this function `this`
		// would refer to the element ...
		element.set('tween',{
			duration: this.options.duration // so this wouldn't work ...
		});

	}, this); // but `each` takes a final argument for binding
	// so we bind `this` (the class instance) so `this.options` is
	// the options of the class instance instead of element.options
	// which isn't what we want
},

But we use it more traditionally in our attach method:

attach: function(){
	this.elements.each(function(element){
		var events = {
			mouseenter: function(){
				element
				 .set('tween',{ transition: 'bounce:out' })
				 .tween('padding-top', this.options.distance); // another option
			}.bind(this), // so we have to bind the class instance again
...

To reiterate, we have to bind this to reference the class instance so we can access things like this.options or the other properties and methods of a class. Excluding your class methods, whenever you type function(){} you’re going to need to add .bind(this) on the end if anything in that function needs access to the class’s stuff.

Adding and removing events with attach and detach.

As a general rule, if your class adds any event, no matter how small, you ought to give yourself (and other developers) a way to remove the events–even if you don’t think you’ll ever need to, I’m surprised how regularly I use detach.

To remove an event from an element we have to point to the same function as when we added it:

var feedWife = function(){
	console.log('my wife is always hungry when she is pregnant');
};

$('element').addEvent('click', feedWife);
$('element').addEvent('click', otherFunction);

// later
$('element').removeEvent('click', feedWife); 

// clicking will still fire `otherFunction`;

Easy enough. Keep in mind that you can’t easily remove an event that has an anonymous function assigned to it:

$('element').addEvent('click',function(){
	console.log("Can't kill me without killing all click events!");
});

$('element').removeEvents('click'); // removes ALL click events

So that’s bad to do in your class. You’ll blow away any other events you may have added to the element. In order to properly remove events we need functions stored somewhere so we can add and remove the same function. When working with multiple elements it’s easiest to just store the function(s) with the element and retrieve them when we want to remove them. Let’s look at the entire attach method again.

attach: function(){
	this.elements.each(function(element){
		var events = {
			mouseenter: function(){ /* some stuff */ }.bind(this),
			mouseleave: function(){	/* some stuff */ }
		};
		element.store('menuEvents', events); 
		// store the functions with the element
		// which allows us to find the same functions
		// later to remove
		element.addEvents(events); // add them as events
	}, this);
	return this;
},

That’s pretty straightforward. Now the flexible part, detaching or removing the events:

detach: function(){
	this.elements.each(function(element){
		element.removeEvents(element.retrieve('menuEvents'));
	});
	return this;
}

Kaboom! Gone. However, by storing the functions with the element and then retrieving them to remove the events we don’t remove any other events that may be present on the element–only the ones we put on them in the class.

Should you always name these methods attach and detach? I think so.

Events make classes infinitely more useful

If you’ve used Fx much you’ve probably used some events like onStart, onComplete. We can have our own custom events in our menu class. Events make classes way way way more flexible because it allows you to do other things as the user interacts with the website, like communicate with other classes. Very few classes don’t deserve some events. To use them is dead simple. First implement it and then call fireEvent whenever you want that event to fire:

var Bomb = new Class({
	Implements: [Options, Events],
	
	detonate: function(){
		// do explosive stuff
		this.fireEvent('explode'); // do whatever else you want
	}
	
});

And finally when the class in constructed:

var bomb = new Bomb({
	onExplode: function(){
		bomb2.detonate();
	}
});

var bomb2 = new Bomb({
	onExplode: function(){
		alert('We all exploded!');
	}
});

Here’s a shell with some events (onMouseenter, onMouseleave) added to our BouncyMenu class. Then in the constructor we’ve decided we want to change the text in log to whatever the text in our menu is.

You may have noticed in the options we’ve got some stuff commented out:

options: {
	/* 
	onMouseenter: $empty,
	onMouseleave: $empty,
	*/
	duration: 500,
	distance: 50			
},

That just makes it easier to remember what events the class supports when you come back to it in a few months. You don’t need it, obviously, but it’s good practice. FYI, $empty is just an empty function, in case somebody uncomments it out.

To get the events to fire we just plop in the fireEvent method wherever we want. In this case, it’s in the attach method in the mouseenter and mouseleave functions.

var events = {
	mouseenter: function(){
		element
		 .set('tween',{ transition: 'bounce:out' })
		 .tween('padding-top', this.options.distance);
		this.fireEvent('mouseenter', element); // <-- here
	}.bind(this),
	mouseleave: function(){
		element
		 .set('tween',{ transition: 'circ:out' })
		 .tween('padding-top', element.retrieve('originalPadding'));
		this.fireEvent('mouseleave', element); // <-- here
	}.bind(this)
};

Note that mootools doesn’t care about the on business here. You can do it either way:

this.fireEvent('onDoinStuff'); // works
this.fireEvent('doinStuff'); // works

But:

new SomeClass({
	doinStuff: function(){} // doesn't work
	onDoinStuff: function(){} // works
});

So that’s it. Three tools to make your classes flexible and awesome: Options, Events, and event management (attach / detach.)

MooTools in this article

Hi, I'm Ryan!

Location:
South Jordan, UT
Github:
rpflorence
Twitter:
ryanflorence
Freenode:
rpflo

About Me

I'm a front-end web developer from Salt Lake City, Utah and have been creating websites since the early 90's. I like making awesome user experiences and leaving behind maintainable code. I'm active in the JavaScript community writing plugins, contributing to popular JavaScript libraries, speaking at conferences & meet-ups, and writing about it on the web. I work as the JavaScript guy at Instructure.