Here is the documentation page for Class.js. Mootools contains a robust Class creation and inheritance system. Creating a new class is actually pretty easy.
Classes let you:
Stand alone functions let you:
Functions are used sparingly with Mootools because Classes are so much more powerful. If you start off with a Class and try and keep things generic, and think of what you write as being reusable, you'll find that later you'll extend something you've already written and save yourself a lot of time and create a more robust solution for your problems. Others can make use of this as well.
Mootools itself is a great example of how using classes lets you create complex applications with less code. At the foundation of Mootools is the Class object. Almost every other piece of javascript in the library extends this object to create more complex functionality.
Classes can contain functions within them in the same way that they can contain variables (like "this.options"). Example:
var Animal = new Class({ initialize: function(options){ this.options = options; //save our options. //if the options specify that the animal //is asleep when we create it, call sleep() if(this.options.sleepOnStart) this.sleep(); //else it's awake else this.awake(); }, sleep: function() { this.isAsleep = true; }, awake: function(){ this.isAsleep = false; } });
Now we can instantiate our animal and have it go to sleep or wake up when we want:
var Cat = new Animal({ color: "black", sleepOnStart: true }); //kitty is asleep, let's wake her up Cat.awake(); alert(Cat.isAsleep); //-> alerts "false"
Classes can be extended to create more complex functionality. In previous examples we've created an object (Animal) and an instance of that object (Cat), but by extending the base class we can create more complex functionality while reusing our code. Example:
var Animal = new Class({ initialize: function(options){ this.options = options; this.isAlive = true; } }); var Mammal = Animal.extend({ initialize: function(options){ //this executes the initialize() function in the Animal Class //let's also pass the options to the parent's initialize function this.parent(options); this.isWarmBlooded = true; this.hasFur = true; this.producesMilk = true; }, sleep: function() { this.isAsleep = true; }, awake: function(){ this.isAsleep = false; } }); var Mouse = Mammal.extend(); var Cat = Mammal.extend({ initialize: function(options) { //call initialize of Mammal //pass along any options for //the Mammal and Animal classes this.parent(options) this.hasClaws = true; this.hasTail = true; this.isCarnivorous = true; }, catchMouse: function(mouse){ mouse.isAlive = false; return mouse; } });
Ok, so now we have an Animal class, a Mammal class (that extends Animal), and Cat and Mouse classes (that extend Mammal). The code above creates the classes but doesn't actually execute anything. So let's put this code to use:
var Kitty = new Cat({ color: "black" }); var Mickey = new Mouse({ color: "black", pants: true, shoes: true }); Kitty.catchMouse(Mickey); alert(Mickey.isAlive); //->false
By breaking things up into classes this way, at any point in the future we can implement more types of Animals, Mammals, or even different types of Cats and Mice. It also means that if we find a bug in, say, the Mammal class, fixing it in that one place means we don't have to fix it in all the instances of Mammal.
Note that in using .extend you extend the parent class into the child. It is possible to do this to the class itself. For example:
//from the example above... var Cat = Mammal.extend({ initialize: function(options) { //call initialize of Mammal //pass along any options for //the Mammal and Animal classes this.parent(options) this.hasClaws = true; this.hasTail = true; this.isCarnivorous = true; }, catchMouse: function(mouse){ mouse.isAlive = false; return mouse; } }); //... then later Cat = Cat.extend({ initialize: function(options){ this.parent(options); this.energy = 0; }, catchMouse: function(mouse){ this.parent(mouse); this.energy++; } });
Here we had a class for Cat, but later we wanted to add a bit of functionality to it. Rather than duplicating our code, and rather than creating another namespace ("BetterCat" or something), we just implement new code into the class (using .extend) and call this.parent to execute the functionality already in the class.
Extending classes allows you to take a base class (like Animal above) and create a new class with that one as a template (as in our Mammal class above). This doesn't alter the Animal class in any way.
This isn't always what we're after though. Sometimes you want to alter the parent class in a given context. Let's say, for example, that you're using the Animal class above, but you want to add a function to it for your environment. Extending the Animal class to create something like AnimalExtended doesn't work for you because you're still going to use Cat and Mouse and Mammal and you don't want to rewrite those.
Here's where Class.implement comes in handy. The implement method allows you to add functionality into a class, altering the class itself. You can't use the "this.parent()" function like you can with extend because you aren't creating a child - you're modifying the class. Additionally, if you use the same namespace as one that's already there, you'll overwrite it. Example:
//this is the same Animal class as above var Animal = new Class({ initialize: function(options){ this.options = options; this.isAlive = true; } }); //let's change it to include some additional functionality: Animal.implement({ eat: function() { if(typeof this.energy == "undefined") this.energy = 0; this.energy++; } });
Now Animal has a function called eat() that increments its energy value.
Let's say that later we want to add the ability to pass in a starting value in the options and we want to implement that in to the class as well. Using .implement, if we want to alter the .eat function, we'll have to write it all over again:
//let's let you pass in a starting value Animal.implement({ eat: function() { if(typeof this.energy == "undefined") { if(typeof this.options.startingEnergy != "undefined") this.energy = this.options.startingEnergy; else this.energy = 0; this.energy++; } });
Implement is used in Mootools mostly to extend classes that are in the "Native" group - specifically the String, Array, Function, and Element prototype. These classes are extended using Object.Native, so .extend takes the place of .implement for this special class of objects. This means that if you have this code:
String.extend({ alert: function() { alert(this); } });
Then any string would inherit that function:
"hey howdy!".alert();
In this way, Mootools gives you a lot of shortcuts for things you often do to things like Strings, Arrays, etc.
There's one other way to use implement that's pretty handy. You can create a class and then implement it into another, like so:
//this is the same Mammal and Cat class as above var Cat = Mammal.extend({ initialize: function() { this.parent() //call initialize of Mammal this.hasClaws = true; this.hasTail = true; this.isCarnivorous = true; }, function: catchMouse(mouse){ mouse.isAlive = false; return mouse; } }); var Carnivore = new Class({ isCarnivore : true, energy : 0, eat: function() { this.energy++; } }); Cat.implement(new Carnivore);
Now all the Cats that you create will have the functionality defined in Carnivore. This allows you to create more bite-sized (to play the pun) code that can be added and implemented where it's needed.
The main difference between .extend and .implement is that .implement changes the class's prototype, while .extend creates a copy. This means that if you implement a change into a class all instances of that class will inherit that change instantly, while if you use .extend then all existing instances will remain the same.
This is why we use .implement when we implement classes into each other. Here's a simple illustration:
var thingy = new Class({ go: function(){ alert('hi'); } }); var myClass = new thingy(); myClass.go(); /* alerts 'hi' */ thingy.implement({ go: function(){ alert('implemented'); } }); myClass.go(); /* alerts 'implemented' */ thingy = thingy.extend({ go: function(){ alert('extended'); } }); myClass.go(); /* alerts 'implemented' */

Note that in general you shouldn't use implement for this purpose (to retroactively change all the instances of a class). It's very hard to predict how such a change will affect everything else that's instantiated. It's up to you to figure out when it is best for your purposes to do this sort of thing, but be careful as it can be difficult to predict consequences.
Sometimes you need a placeholder for functions that will be defined later. This is useful for callback functions that you need to define and execute later. By using an empty function that does nothing, you don't have to worry if the member has been defined before it's executed. This will make more sense when you start using the Events and Options classes below. Anyway, Class has a member called empty that is just that - an empty function.
Here's an example of when to use Class.empty:
//this is the same Mammal and Cat class as above var Cat = Mammal.extend({ initialize: function() { this.parent() //call initialize of Mammal this.hasClaws = true; this.hasTail = true; this.isCarnivorous = true; }, function: catchMouse(mouse){ mouse.isAlive = false; return mouse; } }); var Carnivore = new Class({ isCarnivore : true, energy : 0, onEat: Class.empty, // <- this function won't do anything for now eat: function() { this.energy++; this.onEat(); } }); Cat.implement(new Carnivore); var Kitty = new Cat(); //let's define onEat functionality now Kitty.onEat = function(){ alert('yumm!!!'); }; Kitty.eat(); //energy++; alert 'yum!!!'
This file contains a few basic functionalities that are useful for many types of classes. You can add the functionality in these extras to any one of your classes easily by using Class.implement.
Chaining is a pretty clever way to set up a list of functions you want to execute in a given order without actually executing them yet. Then, each time you call the method .callChain(), the next function will execute. The Chain class can be used as a stand-alone class, but that's not what it's really for. Instead it is designed to be implemented into other classes.
Here's what it looks like on its own:
var x = new Chain(); var one = function(){alert('1')}; var two = function(){alert('2')}; x.chain(one); x.chain(two); x.callChain(); /*alert '1'*/ x.callChain.delay(2000, x); /*wait 2 secs, alert '2'*/

Chaining is much more useful when you call chain on an executed function, and then subsequent chains will fire automatically as the previous one completes.
var myFx = new Fx.Style('chainExample', 'opacity'); myFx.start(1,0).chain(function(){ myFx.start(0,1); }).chain(function(){ myFx.start(1,0); }).chain(function(){ myFx.start(0,1); });

In order for this to work, you must implement Chain into your own class and call the .callChain method within your class. In the example above, I'm using Fx.Style to demonstrate chaining, so I'll show you the relevant part of Fx.Base (which Fx.Style extends) that uses chain.
Fx.Base = new Class({ //...there's lots more to Fx.Base, but here's the part that calls the chain: step: function(){ var time = $time(); //get the current time if (time < this.time + this.options.duration){ //if the effect is still running //iterate forward and increase the pertinent values this.delta = this.options.transition((time - this.time) / this.options.duration); this.setNow(); this.increase(); } else { //the effect is over this.stop(true); //stop it this.set(this.to); //set the value to the end point in the options this.fireEvent('onComplete', this.element, 10); //fire an onComplete event this.callChain(); //EXECUTE THE CHAIN } } }); //this part is important, too: Fx.Base.implement(new Chain);
Ok, this may be a little hard to grok, so I'll walk you through it. Chain contains the member functions chain and callChain. The last line in the example above uses .implement to add the functionality of Chain to Fx.Base. So now Fx.Base has .chain and .callChain.
Fx.Base has a method called .step that iterates the effect forward and, when the duration set in the options has expired, it ends the effect and executes .callChain. This fires whatever function is next in the chain. So in my example that we started with (the opacity effect up above a bit) I call .chain and fade the element to 0 and back to 1, I'm chaining up instructions and as each effect completes the next function gets executed.
An "Utility" Class, its methods can be implemented with Class.implement into any other Class.
Events lets you add custom events to any class you create. An event, by convention, always starts with "on" - like "onComplete" or "onStart". You can have as many events as you like. I like to have events at the ends of things (like a long string of chained logic) and whereever the user can interact with something ("onDrag", "onClose", "onPick").
Mootools implements Events into a lot of its classes (these are always defined in the docs). Here's an example using Fx.Style:
var myFx = new Fx.Style('eventFxExample', 'opacity').addEvent('onComplete', function(){ alert('the effect is completed'); }).addEvent('onComplete', function(){ alert('I told you the effect is completed'); }); myFx.start(0,1); //upon completion it will display the 2 alerts, in order. //you can add events at any time myFx.addEvent('onComplete', function(){ alert('seriously, it\'s over'); }); myFx.start(0,1);

The Events class is something you can integrate into your own classes. You can define any event that you want, though typically you'll want to have 'standard' events like:
Technically, it's not a requirement that it begin with "on", but it's a good practice, and if you use the Options class (see below) then you'll have to use "on" as a prefix.
To integrate the Events class into your own class, you have to do two things: implement the Events class into your class, and pepper your Class with calls to the events to fire them.
When you fire an event, it can take three arguments:
Example:
var MyClass = new Class({ options: { defaultVal: 'something', onStart: Class.empty, onComplete: Class.empty }, initialize: function(element, options){ this.element = element; this.setOptions(options); this.run(); }, run: function(){ //THIS PART IS THE INTERESTING BIT this.fireEvent('onStart', this.element, 10); //..do some things //...now you're done this.fireEvent('onComplete', this.element, 10); } }); //now we have to implement the Options Class //and the Events class into our class: MyClass.implement(new Events, new Options); //if we want to allow people to chain things together //we might also want to implement the chain class here: MyClass.implement(new Chain);
Then, when this class is instantiated, you can pass in functions to execute when the events fire:
var instanceOfMyClass = new MyClass( "ABC", { defaultVal: 'my default val', onStart: function(element){alert('starting with ' + element);}, onComplete: function(element){alert('finished with ' + element);} } );
We can pass more than one argument to an event by using an array:
var myClass = new Class({ ... this.fireEvent('onComplete', [element, 'complete']); ... }); ... function alertStateChange(element, eventName){alert(eventName + ': ' + element);}; var instanceOfMyClass = new myClass({ defaultVal: 'my default val', onStart: alertStateChange, onComplete: alertStateChange });
You can set the default options of a class by just using $merge like so:
var myClass = new Class({ initialize: function(options){ this.options = $merge({ elements: [], startIndex: 0 }, options || {}); } });
In the above example, I have some default values - elements (an empty array) and startIndex at zero. If the user instantiates this object thusly:
var instanceOfMyClass = new myClass();
This instance will have these default values as this.options.elements and this.options.startIndex. If they instantiate the object this way:
var instanceOfMyClass = new myClass({ elements: $$('div p'), //all the paragraphs in divs startIndex: 3 //start on the 4th element });
...then they will overwrite these defaults.
The Options class does this for you:
var myClass = new Class({ options: { elements: [], startIndex: 0 }, initialize: function(options){ /* it doesn't matter if the user passes in no value for options */ this.setOptions(options); } }); myClass.implement(new Options);
But wait! There's more! The other thing that the Options class does is cycle through all your options looking for any option that begins with "on" and automatically add that event to your class. So if your class, instead, looked like this:
var myClass = new Class({ options: { elements: [], startIndex: 0, onStart: Class.empty }, initialize: function(options){ this.setOptions(options); this.fireEvent('onStart', this.element); } }); myClass.implement(new Options, new Events);
Then there would be an event fired for onStart using the this.fireEvent function in the Events class. In this example, I set onStart to Class.empty, which is an empty function. This is a very common convention, because then you don't need to worry if the user passes in an onStart event; if they don't, the empty function is executed instead.
mootorial/02-class.txt · Last modified: 2008/05/24 05:54 by xxf