Advertisement

Make your MooTools Code Shorter, Faster, and Stronger

by

Twice a month, we revisit some of our readers’ favorite posts from throughout the history of Nettuts+. This tutorial was first published in February, 2010.

MooTools is one of the most flexible, modular, and well written JavaScript frameworks available. So many people use it but many of them don't optimize their code. This post will provide you with fifteen simple tips for making your MooTools code shorter, faster, and stronger.


1.Create Your Own MooTools Build or Pull From Google AJAX Libraries

One of the great advantages to using MooTools is that it's incredibly modular. What does that mean? Almost nothing is required unless you need it. The advantage of MooTools' modularity is that your limited, custom MooTools build can keep your JavaScript load time short.

MooTools Core Builder

Want to create a custom MooTools build for your next project? Follow these steps:

  • Go to http://mootools.net/core (and/or http://mootools.net/more if you'd like additional plugins)
  • Select the plugins of your choosing. Don't worry about accounting for dependencies -- the plugin builder does that for you!
  • Select the compression option of your choosing -- the YUI Compressor will provide the you with the smallest possible build of MooTools

That's it! Sometimes, however, your project requires the entire MooTools Core library. In that case, your website can save itself thousands
of requests per day by using the Google AJAX Libraries complete build of MooTools. You may do this two ways:

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/mootools/1.2.4/mootools-yui-compressed.js"></script>

This first method simply includes MooTools into the page per normal. The second method allows more functionality and performance:

<script src="http://www.google.com/jsapi"></script>
<script type="text/javascript">
google.load("mootools", "1.2.4"); //older versions also available
</script>

What's great about using the Google AJAX Libraries API is that if another website uses the AJAX Library API, that version of MooTools is already cached within their browser and the site will load faster!


2. Use jQuery and MooTools Together

While it's best to stick to one library in a given page to avoid a bunch of overhead, sometimes you can't avoid needing multiple frameworks.
Luckily MooTools can coexist with any non-prototype-based JavaScript frameworks. Here's how you can use jQuery and MooTools in the same page:

<!-- jquery gets the "$" method -->
<script type="text/javascript" src="jquery-1.4.js" />
<!-- mootools doesn't steal the "$" method; instead, document.id will be used -->
<script type="text/javascript" src="mootools.js" /> 
<!-- let's use them -->
<script type="text/javascript">
//with jquery, grab all links, make then red
$('a').css('color','red');
//with mootools, get the content div, set it's background color to pink
document.id('content').setStyle('background','pink');
//with mootools, get the content div, set it's background color to pink
//this time, we'll give mootools the "$" method
(function($) {
	$('content').setStyle('background','pink');
})(document.id);
</script>

Thanks to MooTools' Dollar Safe Mode, MooTools no longer assumes the "$" method if it's already taken!


3. Save Elements and Element Collections

Developers often need to collect one element or a collection of elements. For example, you may need to grab all A elements within the page, change their color, and create tooltips from them.

//grab links, change color */
$$('#footer a').setStyle('color','#f00');
//make links tooltips
var tippers = new Tips($$('#footer a'));

The code above is grossly inefficient. Why query the DOM twice (with $$) if you can collect all of the elements once? Let's make this more efficient:

//"save" links into a variable
var links = $$('#footer a');
//grab links, change color */
links.setStyle('color','#f00');
//make links tooltips
var tippers = new Tips(links);

You could make this even shorter, but it's not as readable:

var tippers = new Tips($$('#footer a').setStyle('color','#f00'));

Readability is important, so I wouldn't recommend coding this way if you work with a team.


4. Use Element Methods on Element Collections

Cycling through an array of elements is not unique to any JavaScript framework:

//for every link...
$$('a').each(function(a) {
	//add link nudging to the element
	a.addEvents({
		mouseenter: function() { //animate to the right
			if(!a.retrieve('oPad')) { a.store('oPad',a.getStyle('padding-left')); }
			a.tween('padding-left',30);
		},
		mouseleave: function() { //animate back to the left
			a.tween('padding-left',a.retrieve('oPad'));
		}
	});	
});

What many developers aren't aware that Element collections have the same methods as Elements, so there's no need to cycle through them -- simply apply the desired functionality to the collection:

$$('a').addEvents({
	mouseenter: function() { //animate to the right
		if(!this.retrieve('oPad')) { this.store('oPad',this.getStyle('padding-left')); }
		this.tween('padding-left',30);
	},
	mouseleave: function() { //animate back to the left
		this.tween('padding-left',this.retrieve('oPad'));
	}
});

Note that the "this" keyword is used to reference the "current" element within the collection, not the collection itself.


5. Use MooTools Alias

MooTools' "alias" method allows you to rename or alias an existing method. Take the following snippet of code which is currently in the MooTools Core source:

Array.alias('forEach', 'each');

The above code lets you call the each method instead of forEach. Using each is more readable, a quiet standard between most JavaScript frameworks, and it even saves you a few bytes in your code. If you prefer to give MooTools' Native or Class methods a custom name, feel free to!

For example, the Element Class' method for removing an Element form the DOM is:

$('myElement').dispose();

Suppose your web app is about a given topic and you'd like to stay within that terminology for your code. Here are a few examples:

Element.alias('dispose','can'); //career site?
Element.alias('dispose','shank'); //prison site?

Whatever your reasons are for calling a method by a different name, just don't be afraid to do so!


6. Create Custom Pseudo Selectors

Accessing a collection of Elements in the DOM is a core responsibility of any JavaScript framework. Unfortunately it can also be taxing and the pseudo selectors you want aren't always available. Luckily MooTools allows you to easily implement your own pseudo selectors! Let's
create a pseudo selector named "disabled" that returns an element if it's disabled.

//grab disabled elements
Selectors.Pseudo.disabled = function() {
	return this.disabled;
}

//how you use it
var disabledInputs = $$('input:disabled');

Simply add your selector to the Selectors.Pseudo object. If the new pseudo's function returns "true", the element is a match and will be returned.

Defining you own pseudo selectors is a great way to take control of your selectors!


7. Implement Methods on Existing Objects

MooTools' philosophy is that it's acceptable, even encouraged, to modify Native (String, Function, Number, etc.) prototypes when needed.
Implementing new methods on these Natives will empower them even more. Let's create a String method that will turn any string of text into
"tweet" format (add links for @reply's, links, etc.):

String.implement({
	toTweet: function() {
		 return this.replace(/(https?:\/\/\S+)/gi,'<a href="$1">$1</a>').replace(/(^|\s)@(\w+)/g,'$1<a href="http://twitter.com/$2">@$2</a>').replace(/(^|\s)#(\w+)/g,'$1<a href="http://search.twitter.com/search?q=%23$2">#$2</a>');
	}
});

Now you can call "toTweet" on any string and you'll get the string back as a "tweet". Here are a few examples:

//set an element's html to a tweet value
var el = $('myElement');
el.set('html',el.get('html').toTweet()); //sets the element's html to a linked, tweet value.

//alert the tweeted value
alert('Yo @NetTuts, check out my #MooTools website: http://davidwalsh.name'.toTweet());
//alerts:  Yo <a href="http://twitter.com/nettuts">@NetTuts</a>, check out my <a href="http://search.twitter.com/search?q=%23MooTools">MooTools</a> website: <a href="http://davidwalsh.name">http://davidwalsh.name</a>

Implementing custom methods on Objects strengthens every existing and future instance of that object.


8. Extend Existing Classes

MooTools' OOP philosophy allows for a super-powerful inheritance model. Extending existing classes allows you to avoid repeating code, empower existing objects, and leverage existing functionality. MooTools Core, More, and your custom classes extend existing functionality. Consider the Request class:

var Request = new Class({

	Implements: [Chain, Events, Options],

	options: {/*
		onRequest: $empty,
		onComplete: $empty,
		onCancel: $empty,
		onSuccess: $empty,
		onFailure: $empty,
		onException: $empty,*/
		url: '',
		data: '',
		headers: {
			'X-Requested-With': 'XMLHttpRequest',
			'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
		},
		async: true,
		format: false,
		method: 'post',
		link: 'ignore',
		isSuccess: null,
		emulation: true,
		urlEncoded: true,
		encoding: 'utf-8',
		evalScripts: false,
		evalResponse: false,
		noCache: false
	},

	initialize: function(options){
		this.xhr = new Browser.Request();
		this.setOptions(options);
		this.options.isSuccess = this.options.isSuccess || this.isSuccess;
		this.headers = new Hash(this.options.headers);
	},

	onStateChange: function(){
		if (this.xhr.readyState != 4 || !this.running) return;
		this.running = false;
		this.status = 0;
		$try(function(){
			this.status = this.xhr.status;
		}.bind(this));
		this.xhr.onreadystatechange = $empty;
		if (this.options.isSuccess.call(this, this.status)){
			this.response = {text: this.xhr.responseText, xml: this.xhr.responseXML};
			this.success(this.response.text, this.response.xml);
		} else {
			this.response = {text: null, xml: null};
			this.failure();
		}
	},

	isSuccess: function(){
		return ((this.status >= 200) && (this.status < 300));
	},

	processScripts: function(text){
		if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return $exec(text);
		return text.stripScripts(this.options.evalScripts);
	},

	success: function(text, xml){
		this.onSuccess(this.processScripts(text), xml);
	},

	onSuccess: function(){
		this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain();
	},

	failure: function(){
		this.onFailure();
	},

	onFailure: function(){
		this.fireEvent('complete').fireEvent('failure', this.xhr);
	},

	setHeader: function(name, value){
		this.headers.set(name, value);
		return this;
	},

	getHeader: function(name){
		return $try(function(){
			return this.xhr.getResponseHeader(name);
		}.bind(this));
	},

	check: function(){
		if (!this.running) return true;
		switch (this.options.link){
			case 'cancel': this.cancel(); return true;
			case 'chain': this.chain(this.caller.bind(this, arguments)); return false;
		}
		return false;
	},

	send: function(options){
		if (!this.check(options)) return this;
		this.running = true;

		var type = $type(options);
		if (type == 'string' || type == 'element') options = {data: options};

		var old = this.options;
		options = $extend({data: old.data, url: old.url, method: old.method}, options);
		var data = options.data, url = String(options.url), method = options.method.toLowerCase();

		switch ($type(data)){
			case 'element': data = document.id(data).toQueryString(); break;
			case 'object': case 'hash': data = Hash.toQueryString(data);
		}

		if (this.options.format){
			var format = 'format=' + this.options.format;
			data = (data) ? format + '&' + data : format;
		}

		if (this.options.emulation && !['get', 'post'].contains(method)){
			var _method = '_method=' + method;
			data = (data) ? _method + '&' + data : _method;
			method = 'post';
		}

		if (this.options.urlEncoded && method == 'post'){
			var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : '';
			this.headers.set('Content-type', 'application/x-www-form-urlencoded' + encoding);
		}

		if (this.options.noCache){
			var noCache = 'noCache=' + new Date().getTime();
			data = (data) ? noCache + '&' + data : noCache;
		}

		var trimPosition = url.lastIndexOf('/');
		if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition);

		if (data && method == 'get'){
			url = url + (url.contains('?') ? '&' : '?') + data;
			data = null;
		}

		this.xhr.open(method.toUpperCase(), url, this.options.async);

		this.xhr.onreadystatechange = this.onStateChange.bind(this);

		this.headers.each(function(value, key){
			try {
				this.xhr.setRequestHeader(key, value);
			} catch (e){
				this.fireEvent('exception', [key, value]);
			}
		}, this);

		this.fireEvent('request');
		this.xhr.send(data);
		if (!this.options.async) this.onStateChange();
		return this;
	},

	cancel: function(){
		if (!this.running) return this;
		this.running = false;
		this.xhr.abort();
		this.xhr.onreadystatechange = $empty;
		this.xhr = new Browser.Request();
		this.fireEvent('cancel');
		return this;
	}

});

Then consider Request.JSONP, which extends Request:

Request.JSON = new Class({

	Extends: Request,

	options: {
		secure: true
	},

	initialize: function(options){
		this.parent(options);
		this.headers.extend({'Accept': 'application/json', 'X-Request': 'JSON'});
	},

	success: function(text){
		this.response.json = JSON.decode(text, this.options.secure);
		this.onSuccess(this.response.json, text);
	}

});

You see how small the Request.JSONP class is? By adding Extends: Request, the Request.JSONP class gets all of the Request Class' methods. Essentially, this small snippet of code becomes a powerhouse because it extends Request. You can even add extensions to extensions. Now consider Request.JSONP and then Scott Kyle's Request.Twitter class:

//Request.JSONP
/*
---

script: Request.JSONP.js

description: Defines Request.JSONP, a class for cross domain JavaScript via script injection.

license: MIT-style license

authors:
- Aaron Newton
- Guillermo Rauch

requires:
- core:1.2.4/Element
- core:1.2.4/Request
- /Log

provides: [Request.JSONP]

...
*/

Request.JSONP = new Class({

	Implements: [Chain, Events, Options, Log],

	options: {/*
		onRetry: $empty(intRetries),
		onRequest: $empty(scriptElement),
		onComplete: $empty(data),
		onSuccess: $empty(data),
		onCancel: $empty(),
		log: false,
		*/
		url: '',
		data: {},
		retries: 0,
		timeout: 0,
		link: 'ignore',
		callbackKey: 'callback',
		injectScript: document.head
	},

	initialize: function(options){
		this.setOptions(options);
		if (this.options.log) this.enableLog();
		this.running = false;
		this.requests = 0;
		this.triesRemaining = [];
	},

	check: function(){
		if (!this.running) return true;
		switch (this.options.link){
			case 'cancel': this.cancel(); return true;
			case 'chain': this.chain(this.caller.bind(this, arguments)); return false;
		}
		return false;
	},

	send: function(options){
		if (!$chk(arguments[1]) && !this.check(options)) return this;

		var type = $type(options), 
				old = this.options, 
				index = $chk(arguments[1]) ? arguments[1] : this.requests++;
		if (type == 'string' || type == 'element') options = {data: options};

		options = $extend({data: old.data, url: old.url}, options);

		if (!$chk(this.triesRemaining[index])) this.triesRemaining[index] = this.options.retries;
		var remaining = this.triesRemaining[index];

		(function(){
			var script = this.getScript(options);
			this.log('JSONP retrieving script with url: ' + script.get('src'));
			this.fireEvent('request', script);
			this.running = true;

			(function(){
				if (remaining){
					this.triesRemaining[index] = remaining - 1;
					if (script){
						script.destroy();
						this.send(options, index).fireEvent('retry', this.triesRemaining[index]);
					}
				} else if(script && this.options.timeout){
					script.destroy();
					this.cancel().fireEvent('failure');
				}
			}).delay(this.options.timeout, this);
		}).delay(Browser.Engine.trident ? 50 : 0, this);
		return this;
	},

	cancel: function(){
		if (!this.running) return this;
		this.running = false;
		this.fireEvent('cancel');
		return this;
	},

	getScript: function(options){
		var index = Request.JSONP.counter,
				data;
		Request.JSONP.counter++;

		switch ($type(options.data)){
			case 'element': data = document.id(options.data).toQueryString(); break;
			case 'object': case 'hash': data = Hash.toQueryString(options.data);
		}

		var src = options.url + 
			 (options.url.test('\\?') ? '&' :'?') + 
			 (options.callbackKey || this.options.callbackKey) + 
			 '=Request.JSONP.request_map.request_'+ index + 
			 (data ? '&' + data : '');
		if (src.length > 2083) this.log('JSONP '+ src +' will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs');

		var script = new Element('script', {type: 'text/javascript', src: src});
		Request.JSONP.request_map['request_' + index] = function(){ this.success(arguments, script); }.bind(this);
		return script.inject(this.options.injectScript);
	},

	success: function(args, script){
		if (script) script.destroy();
		this.running = false;
		this.log('JSONP successfully retrieved: ', args);
		this.fireEvent('complete', args).fireEvent('success', args).callChain();
	}

});

Request.JSONP.counter = 0;
Request.JSONP.request_map = {};

...and now Request.Twitter:

Request.Twitter = new Class({

	Extends: Request.JSONP,

	options: {
	  linkify: true,
	  url: 'http://twitter.com/statuses/user_timeline/{term}.json',
	  data: {
	    count: 5
	  }
	},

	initialize: function(term, options){
	  this.parent(options);
	  this.options.url = this.options.url.substitute({term: term});
	},

	success: function(data, script){
	  if (this.options.linkify) data.each(function(tweet){
	    tweet.text = this.linkify(tweet.text);
	  }, this);
  
	  // keep subsequent calls newer
	  if (data[0]) this.options.data.since_id = data[0].id;
  
	  this.parent(data, script);
	},

	linkify: function(text){
	  // modified from TwitterGitter by David Walsh (davidwalsh.name)
	  // courtesy of Jeremy Parrish (rrish.org)
	  return text.replace(/(https?:\/\/[\w\-:;?&=+.%#\/]+)/gi, '<a href="$1">$1</a>')
	             .replace(/(^|\W)@(\w+)/g, '$1<a href="http://twitter.com/$2">@$2</a>')
	             .replace(/(^|\W)#(\w+)/g, '$1#<a href="http://search.twitter.com/search?q=%23$2">$2</a>');
	}
  
});

You see how a waterfall effect of extending objects can make the smallest of classes an absolute beast of a class?
Experiment with MooTools' inheritance model and don't repeat code!


9. Create Custom Events

I've already explained how flexible the MooTools selector engine is, the class system is, and how modular the framework is.1 Why would you expect anything different from MooTools' event system? Creating custom events within MooTools is as simple as it gets. Here's a basic outline of your MooTools custom event:

Element.Events.altClick = {
	base: 'click', //the "base" event
	condition: function(event) {
		return event.alt; // alt key?
	},
	onAdd: function() {
		//do something when the event is added
	},
	onRemove: function() {
		//do something when the event is removed
	}
};

Here's a great example of a custom event -- listening for "alt" and "click" at the same time:

//alt click
Element.Events.altClick = {
	base: 'click',
	condition: function(event) {
		return event.alt; // alt key?
	}
};

//usage
$(document.body).addEvent('altClick',function() {
	alert('You alt-clicked me!');
});

Or you can simply define a custom event so that a specific function executes any time that type of event is assigned. In my next example, any time a click event is assigned to an element, that element's cursor will be automatically changed to the "pointer" cursor.

/* update cursor on add/remove click event */
Element.Events.click = { 
	base:'click',
	onAdd: function() {
		if(this.setStyle) {
			this.store('original-cursor',this.getStyle('cursor'));
			this.setStyle('cursor','pointer');
		}
	},
	onRemove: function() {
		if(this.setStyle) {
			this.setStyle('cursor',this.retrieve('original-cursor'));
		}
	}
};

You'll notice that if the click event is removed, the original cursor will be restored.


10. jQuery-Style Events

While the MooTools event sytax is different from jQuery's, it doesn't have to be! With a minimal amount of JavaScript you can make MooTools' event syntax reflect jQuery's.

MooTools holds all of its events in the Element.NativeElements object:

Element.NativeEvents = {
	click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons
	mousewheel: 2, DOMMouseScroll: 2, //mouse wheel
	mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement
	keydown: 2, keypress: 2, keyup: 2, //keyboard
	focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, //form elements
	load: 1, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window
	error: 1, abort: 1, scroll: 1 //misc
};

Essentially all you need to do is cycle through each element type and implement a method on the Element class, named like the event type,
that simulates what addEvent does:

//hash the element.natives so you can do stuff with it
var hash = new Hash(Element.NativeEvents);
//remove items that need to be replaced, add their replacements
hash.erase('mouseover').erase('mouseout').erase('DOMMouseScroll');
hash.include('mouseenter',1).include('mouseleave',1);
//initialize this
var eventHash = new Hash({});
//for every event type, add to the hash
hash.getKeys().each(function(event){
	eventHash[event] = function(fn) {
		this.addEvent(event,fn);
		return this;
	};
});
//make it happen
Element.implement(eventHash);

Now you can listen for events like:

$('myElement').click(function() {
	//do stuff
});

11. Add Events During Element Creation

If you have experience coding with MooTools, at some point you've no doubt created an element and subsequently added events to it:

var myElement = new Element('a',{
	href: 'mypage.php',
	text: 'Click here!'
});

myElement.addEvent('click',function(e) {
	//stop the event
	if(e) e.stop();
	//do stuff
});

There's nothing wrong with the above, per say, but you could just add those events during element creation:

var myElement = new Element('a',{
	href: 'mypage.php',
	text: 'Click here!',
	events: {
		click: function() {
			//stop the event
			if(e) e.stop();
			//do stuff
		}
	}
});

12. Implement Events Within Classes

Extending classes was discussed in tip #8 above. Now let's explore the *implement* functionality within MooTools classes. What's the difference? MooTools contributor Mark Obcena says it best in his article titled Up The Moo Herd IV: There's A Class For This:

MooTools has two built-in mutators: Extends and Implements. The Extends mutator takes the class name passed on to it and makes the new class inherit directly from it, while Implements takes the class (or classes) passed and adds their methods to the new class (or mixes them in—thus mixin).

With the difference between extending and implementing, let's get back to it. Implementing events within your MooTools classes can make your classes much more flexible. Consider the following simple Overlay class:

var Overlay = new Class({
	
	Implements: [Options,Events],
	
	options:  {
		id: 'overlay',
		color: '#000',
		duration: 500,
		opacity: 0.5,
		zIndex: 5000
	},
	
	initialize: function(container,options) {
		this.setOptions(options);
		this.container = document.id(container);
		this.overlay = new Element('div',{
			id: this.options.id,
			opacity: 0,
			styles: {
				position: 'absolute',
				background: this.options.color,
				left: 0,
				top: 0,
				'z-index': this.options.zIndex
			},
		}).inject(this.container);
		this.tween = new Fx.Tween(this.overlay,{ 
			duration: this.options.duration,
			link: 'cancel',
			property: 'opacity',
			onStart: function() {
				this.overlay.setStyles({
					width: '100%',
					height: this.container.getScrollSize().y
				});
			}.bind(this)
		});
	},
	open: function() {
		this.tween.start(this.options.opacity);
		return this;
	},
	close: function() {
		this.tween.start(0);
		return this;
	}
});

Sure the class does what it's supposed to but it isn't nearly as flexible it could be. Now let's implement onClick, onClose, onHide, onOpen, and onShow events:

var Overlay = new Class({
	
	Implements: [Options,Events],  // EVENTS IMPLEMENTED HERE!
	
	options:  {
		id: 'overlay',
		color: '#000',
		duration: 500,
		opacity: 0.5,
		zIndex: 5000/*,
		onClick: $empty,
		onClose: $empty,
		onHide: $empty,
		onOpen: $empty,
		onShow: $empty
		*/
	},
	
	initialize: function(container,options) {
		this.setOptions(options);
		this.container = document.id(container);
		this.overlay = new Element('div',{
			id: this.options.id,
			opacity: 0,
			styles: {
				position: 'absolute',
				background: this.options.color,
				left: 0,
				top: 0,
				'z-index': this.options.zIndex
			},
			events: {
				click: function() {    // CLICK EVENT
					this.fireEvent('click');
				}.bind(this)
			}
		}).inject(this.container);
		this.tween = new Fx.Tween(this.overlay,{ 
			duration: this.options.duration,
			link: 'cancel',
			property: 'opacity',
			onStart: function() {
				this.overlay.setStyles({
					width: '100%',
					height: this.container.getScrollSize().y
				});
			}.bind(this),
			onComplete: function() {
				this.fireEvent(this.overlay.get('opacity') == this.options.opacity ? 'show' : 'hide');  // SHOW OR HIDE EVENT 
			}.bind(this)
		});
	},
	open: function() {
		this.fireEvent('open');  // OPEN EVENT
		this.tween.start(this.options.opacity);
		return this;
	},
	close: function() {
		this.fireEvent('close');  // CLOSE EVENT
		this.tween.start(0);
		return this;
	}
});

What's great about adding events to a class is that events allow your to give more options and trigger functionality when our class methods execute. In the above example, you can execute any functionality when the overlay opens, closes, shows, hides, or gets clicked.
Essentially you added two tiny snippets of code to the class:

Implements: [Events]

...and the following wherever you'd like an event to be signaled...

this.fireEvent('someEvent',[argument1, argument2]);

So how can you control these events when you create an instance of the class? Add them in the options like this:

var overlay = new Overlay({
	onClick: function() {
		this.hide();
	},
	onOpen: function() {
		alert('Thank you for opening!');
	}
});

You'd be hard pressed to find a class that wouldn't benefit from implementing events!


13. Use Event Delegation

Event delegation is the process of adding an event to a parent for all of its children instead of assigning the event to each individual child. The advantage of event delegation is that you may add child elements to the parent element without needing to assign the event to that new element. If you choose to remove the event, you need only remove it from one element.

So, instead of:

$$('a').addEvent('click',function() {
	//do stuff -- individually assigned
});

...you do this:

$('myContainer').addEvent('click:relay(a)',function() {
	//assigned to the parent of all A elements (in this case, #myContainer), to listen to A element click event
})/

Don't let the ":relay()" pseudo-syntax fool you; Element.Delegation rewrites the event methods to accommodate for :relay.


14. Use Class.toElement

One hidden gem within MooTools' Class is the Class.toElement method. Class.toElement plays a small role but can help you out when it comes to accessing the primary element within a class, especially if you don't know what that element is otherwise. Implementing toElement on your class is easy:

var myClass = new Class({

	Implements: [Options],
	
	initialize: function(container,options) {
		this.container = $(container);
	},
	
	toElement: function() {
		return this.container;
	}
});

Once you have toElement defined, you can use your class just like an element:

var myInstance = new MyClass('myElement');
myInstance.setStyle('color','#f00').set('html','This is my element!');

Look at that -- a class virtually manipulated by Element methods.


15. "return this" Within Methods For Chainability

So we've all seen how the JavaScript frameworks allow you to chain the hell out of methods. Chaining looks like this:

$('myElement').setStyles('color','#f00').set('html','Click Here').fade('out').addClass('cssClass').addEvent('click',function(e) {
	if(e) e.stop();
	alert('Clicked'!);
});

Holy chaining Batman! Want your classes to chain forever? No problem -- all you need to do is return "this":

var myClass = new Class({
	
	//options, initialize, implements, etc.
	
	doSomething: function() {
		//do a whole bunch of functionality here and...
		return this;
	},
	doAnotherThing: function() {
		//do a whole bunch of functionality here and...
		return this;
	},
	doYetAnotherThing: function() {
		//do a whole bunch of functionality here and...
		return this.
	}
});

Since you placed return this in each method, now you can do:

var klass = new myClass();
klass.doSomething().doAnotherThing().doYetAnotherThing();

Make sure to return this wherever it makes sense. Doing so can make your Class much easier to work with and your code will be shorter!


BONUS! Use Fx Shortcuts on Elements

MooTools effects are unarguably the smoothest of any JavaScript framework. The Fx library also provides loads of control through numerous options. Let's take a look at a basic Tween which fades an element to 50%:

var myTween = new Fx.Tween('myElement',{
	duration: 500,
	fps: 200,
	//a bunch of options here
});
//fade to 50%
$('myElement').addEvent('click',function() {
	myTween.start('opacity',0.5);
});

Did you know you didn't need to type out all of this? You could use element shortcuts like:

$('myElement').fade(0.5); //fading: Fx.Tween
$('myElement').tween('width',300); //tweening: Fx.Tween
$('myElement').morph({
	width: 200,
	height: 300
}); //morph:  Fx.Morph

The above snippets, of course, rely on you wanting to use the default options. You can actually set custom options for these shortcut methods per element:

$('myElement').set('tween',{ duration:500, fps: 200 }).tween('width',300);

Save your self a few bytes by using Fx shortcuts!


MooTools FTW!

Hopefully I've given you some tips to improve your MooTools JavaScript code, making it shorter, faster, and stronger. Have some of your own tips to share? Place them in the comments below!

Advertisement