Archive for the ‘MooTools’ Category

MooTools toElement Method and ToElement Mixin

Tuesday, September 28th, 2010

The toElement Method

Classes like Calendar, Slider, AutoCompleter etc. are all containing elements or responsible for elements.

There are several approaches for how to handle the element in the class:

  • Pass a container element as an argument to the constructor of the class, and append to it new elements that the class creates
  • Have the actual behavior-less element(s) on the HTML and pass them to the class constructor as an argument, the class will apply its behaviors on them
  • Create all elements in the class and have them available for appending

Each approach is good for a different situation, but the toElement method is meant mostly for the last one.

Example of a plain class, and how its element is being appended to the document:

var AutoCompleter=new Class({
	initialize:function () {
		this.input=new Element("input");
		// autocomplete magic
	}
});
var autoCompleter=new AutoCompleter();
$("container").grab(autoCompleter.input);

This is quite ugly to use the public property input, which its name can be changed someday and then the code breaks. Moreover, in this way, each class declares its own property and this can lead to inconsistency between the property names.

This is where the toElement method comes in handy: it’s a MooTools convention for a method that simply returns the element.

var AutoCompleter=new Class({
	initialize:function () {
		this.input=new Element("input");
		// autocomplete magic
	},
	toElement:function () {
		return this.input;
	}
});
var autoCompleter=new AutoCompleter();
$("container").grab(autoCompleter.toElement());

But wait, there’s more: MooTools $ / document.id function knows that if it gets an object that has the toElement method, the method should be invoked and the returned element will be returned out of the document.id function. Luckily, all of the MooTools Element methods use document.id inside them, so, for instance, when grab or adopt are called with any object as an argument, it will be passed thru the document.id function which will translate it to the toElement result. Hence, we can use this:

$("container").grab(autoCompleter);

The ToElement Mixin

The ToElement Mixin is supposed to make the toElement method even simpler, and make it lazy-load the element, i.e. create it only when needed.

var ToElement=new Class({
	ui:{},
	toElement$ensureElement:function () {
		var element=this.toElement$getElement();
		if (!element && this.build) this.build();
	},
	toElement$getElement:function () {
		return (this.ui ? this.ui.element : this.element) || this.element;
	},
	toElement:function () {
		this.toElement$ensureElement();
		return this.toElement$getElement();
	}
});

Usage:

var AutoCompleter=new Class({
	Implements:[ToElement],
	initialize:function () {
	},
	build:function () {
		this.ui.element=new Element("input");
		// autocomplete magic
	}
});
var autoCompleter=new AutoCompleter();
// element isn't ready yet
// the grab calls document.id which calls toElement, where the element is prepared
$("container").grab(autoCompleter);

First, it adds a ui hash to every class instance. That’s my convention to save elements in classes of this kind. Second, it adds a toElement method, which is responsible of calling the build method if exists, and return this.ui.element or this.element out of the instance.

Benefits are:

  • Lazy load the element – it’ll be created only when the document asks for it
  • ui hash to contain all other elements, and a build method as a simple convention

MooTools: Track Class Instances With The TrackInstances Mutator

Monday, July 19th, 2010

(Script also on GitHub and MooTools Forge)

It’s great to track all of your classes’ instances for debugging purposes or mass altering.

Huh? Mass Altering?

  • For instances of some Audio/Video player classes: stop/pause all players
  • For instances of positioned elements: recalculate position of all elements upon window resize
  • Call hide method of all instances of a class upon an event

I needed it more than once and I used to track instances from within any class I needed it:

var MyClass=new Class({
	initialize:function () {
		MyClass.instances.push(this);
	},
	recalcPosition:function () {
		// something that recalculates position or any other task that should happen on all instances
	}
});
MyClass.instances=[];
// from another code:
MyClass.instances.each(function (instance) {
	instance.recalcPosition();
});

However, I got too many classes with this behavior. I ended up writing a MooTools Class Mutator to automate the process:

/*
---
script: Class.Mutators.TrackInstances.js
description: Allows a class to track its instances by having instances array as a class property
license: MIT-style license
authors:
- Elad Ossadon ( http://devign.me | http://twitter.com/elado )
requires:
- core:1.2.4
provides: [Class.Mutators.TrackInstances]
...
*/
Class.Mutators.TrackInstances=function (allow) {
	if (!allow) return;
	// save current initialize method
	var oldInit=this.prototype.initialize;
	var klass=this;
	// overwrite initialize method
	klass.prototype.initialize=function () {
		(klass.instances=klass.instances || []).push(this);
		oldInit.apply(this,arguments);
	};
};

Usage:

var MyClass=new Class({
	initialize:function () {
	},
	TrackInstances:true,
	recalcPosition:function () {
		// something that recalculates position or any other task that should happen on all instances
	}
});
var x=new MyClass();
var y=new MyClass();
MyClass.instances; // [x, y]
MyClass.instances.length; // 2
// from another code:
window.addEvent("resize",function () {
	MyClass.instances.each(function (instance) {
		instance.recalcPosition();
	});
});

The only constraint is that the “initialize” declaration in an object should be prior to the TrackInstances:true. That’s because mutators are called in the same loop as all other methods, and if the loop didn’t get to a certain method, then the mutator won’t know it. According to these tickets, the MooTools team won’t fix it anytime soon.