Up The Moo Herd IV There's A Class For This
Gather round boys and girls! It’s time for another episode of Up the Moo Herd: MooTools Tips and Tricks! In the previous installment, we talked about some of the tips and tricks involving classes. In this part, we’re gonna pick up from where we left off last time, and talk about several topics still related to classes: mutators, inheritance and mixins.
In case you haven’t read the announcement post I wrote a few days ago, there will be several changes to the Moo Herd series. First, the part discussing the Fx classes will be removed and replaced by one about some DOM tips and tricks. Another change is that I removed the section on “Reusable Code” for this article due to both length concerns and because it sounded too preachy for my taste.
Continuing the music-flavouring from last time, this post’ title is courtesy of the second song from Cute is What We Aim For‘s first studio album, “The Same Old Blood Rush with a New Touch.”
And with that, let’s continue our exploration of Classes. Ready? Go!
Mutants Arise!
Mutators are special functions that change the behaviour or structure of your classes. They’re powerful tools that facilitate special class features and streamline the process of inheritance and adding mixins.
It takes two parts to create a mutator: the mutator key and the actual mutator function. The mutator key corresponds to both the name of the mutator function and the keyword used in constructing classes, while the mutator function is the function called when the class constructor comes across the mutator key.
MooTools stores mutators in the Class.Mutators
object. When you pass an object to the Class
constructor, MooTools checks each of the keys of the object to see if a mutator function of that name is present in the Class.Mutators
object. If it finds one, it calls the function and passes the value of the key for processing.
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). Inheritance and mixins are explained in the next sections.
Aside from the mutators included in MooTools-Core, there are a few other mutators written by MooTools users for different purposes. But don’t be fooled by the seemingly complex nature of mutators–it’s actually very easy to create your own mutators.
So let’s implement a very simple one! In the last post, I talked about the extend
method that is used to create class methods, like so:
var Barn = new Class();
Barn.extend({
fowlCount: 0,
countFowls: function(){
return this.fowlCount;
},
addFowl: function(){
this.fowlCount += 1;
}
});
Barn.fowlCount; // returns 0;
Barn.addFowl();
Barn.fowlCount; // returns 1;
But what if we want to incorporate this into the class declaration itself? In PHP, we have the static
keyword that’s used to create class methods, so let’s make a new mutator for static methods as well:
// Our 'Static' mutator..
Class.Mutators.Static = function(items){
this.extend(items);
};
// Using out 'Static' mutator..
var Barn = new Class({
Static: {
fowlCount: 0,
countFowls: function(){
return this.fowlCount;
},
addFowl: function(){
this.fowlCount += 1;
}
},
initialize: function(name){
this.name = name;
}
});
Barn.fowlCount; // returns 0;
Barn.addFowl();
Barn.fowlCount; // returns 1;
When Class
goes through the object, it sees the key named Static
. Because we have our new mutator of the same name, Class
calls the mutator function and passes the value of the key (in this case an object containing properties and methods). Our mutator is a very simple one and it uses this.extends()
to turn the properties and methods of the passed object into class properties and methods. (Mutator functions are always bound to the class itself, so this
will refer to your class).
Let’s create a more complex one! In the previous post, I also mentioned a new Function
method called protect()
which is used to create protected methods for our classes:
var Secretive = new Class({
secretFunction: (function(){
return 'The cake is a lie.';
}).protect(),
tellSecret: function(){
return this.secretFunction();
}
});
var secrets = new Secretive();
secrets.tellSecret(); // returns 'The cake is a lie.'
typeof(secrets.secretFunction); // returns 'function'
secrets.secretFunction(); // Error: 'The method secretFunction cannot be called.'
Instead of calling protect()
on every method we’d like to protect, let’s create a Protected
mutator that will do this for us:
// Our 'Protected' mutator...
Class.Mutators.Protected = function(items){
for (var fn in items) {
if (items[fn] instanceof Function && !items[fn]._protected){
items[fn] = items[fn].protect();
}
}
this.implement(items);
};
// Using our 'Protected' mutator..
var Secretive = new Class({
Protected: {
secretFunction: function(){
return 'The cake is a lie.';
},
anotherSecret: function(){
}
},
tellSecret: function(){
return this.secretFunction();
}
});
var secrets = new Secretive();
secrets.tellSecret(); // returns 'The cake is a lie.'
typeof(secrets.secretFunction); // returns 'function'
secrets.secretFunction(); // Error: 'The method secretFunction cannot be called.'
It’s Not All Genes
Inheritance in Javascript is different because there are no native implementation of classes, but instead we rely on prototypes and constructor functions. Instead of a class inheriting from a superclass, Javascript allows object to inherit directly from other objects.
// Superclass..
var Super = function(){};
Super.prototype = {
whoIs: function(){
return 'Super';
}
};
var sup = new Super();
sup.whoIs(); // returns 'Super'
// Subclass..
var Sub = function(){};
Sub.prototype = new Super();
var sub = new Sub(); // returns 'Super'
sub.whoIs();
// Subclass changes..
Sub.prototype.whoIs = function(){
return 'Sub';
};
sub.whoIs(); // returns 'Sub'
Because MooTools implements a way to mimic the structure of traditional class-based languages, it’s only fitting that it also implements a way to effectively streamline the inheritance process. This is done using the Extends
Mutator, which effectively does the same thing as above, but in a cleaner way that doesn’t involve having to touch prototypes:
// Superclass..
var Super = new Class({
whoIs: function(){
return 'Super';
}
});
var sup = new Super();
sup.whoIs(); // returns 'Super'
// Subclass..
var Sub = new Class({
Extends: Super,
});
var sub = new Sub(); // returns 'Super'
sub.whoIs();
// Subclass changes..
Sub.implement({
whoIs: function(){
returns 'Sub';
}
});
sub.whoIs(); // returns 'Sub'
Now one of the problems you’ll come across when working with Javascript is how to access a superclass’ overwritten methods. If you’re working with prototypes, then you might do something like this:
// Superclass..
var Super = function(){};
Super.prototype = {
whoIs: function(){
return 'Super';
}
};
var sup = new Super();
sup.whoIs(); // returns 'Super'
// Subclass..
var Sub = function(){};
Sub.prototype = new Super();
// Subclass changes..
Sub.prototype.whoIs = function(){
var parent = Super.prototype.whoIs.apply(this);
return 'Sub, from ' + parent;
};
sub.whoIs(); // returns 'Sub, from Super'
Because you’re inheriting directly from objects, you have no direct access to overwritten methods without having to access the superclass’ prototype.
Fortunately, MooTools makes this easier by adding a nifty little method to all classes called parent
:
// Superclass..
var Super = new Class({
whoIs: function(){
return 'Super';
}
});
var sup = new Super();
sup.whoIs(); // returns 'Super'
// Subclass..
var Sub = new Class({
Extends: Super,
});
var sub = new Sub(); // returns 'Super'
sub.whoIs();
// Subclass changes..
Sub.implement({
whoIs: function(){
returns 'Sub, from ' + this.parent();
}
});
sub.whoIs(); // returns 'Sub'
parent
is a real method added to all classes that use Extends
–it’s not just a special construct. What happens is that when you call this.parent()
inside any of your class methods, it takes the name of the method that called it and checks to see if your superclass has a method of the same name. If it does, then it calls that method but bound to your subclass. If it doesn’t find a method in your superclass, it throws an error.
Because it is a real method rather than a construct, it’s available for all your methods–even your constructor. This keeps your code DRY.
var Tea = new Class({
initialize: function(){
this.ingredients = ['water', 'tea leaves'];
},
prepare: function(){
var last = this.ingredients.pop();
var msg = [
'Your tea is made up of',
this.ingredients.join(', '),
'and', last].join(' ');
this.ingredients.push(last);
return msg;
}
});
new Tea().prepare();
// returns 'Your tea is made up of water and tea leaves.'
var MilkTea = new Class({
Extends: Tea,
initialize: function(){
this.parent();
this.ingredients.push('milk');
},
prepare: function(){
var msg = this.parent();
return msg + ' Enjoy!';
}
});
new MilkTea().prepare();
// returns 'Your tea is made up of water, tea leaves and milk. Enjoy!'
Remember our discussion about protected methods via protect
? The great thing about inheritance via Extends
is that you also have access to your superclass’ protected methods:
var Secret = new Class({
secretMethod: (function(){
return 'Shh!';
}).protect(),
tellSecret: function(){
return this.secretMethod();
}
});
var secret = new Secret();
secret.secretMethod(); // throws error..
secret.tellSecret(); // returns 'Shh!'
var SubSecret = new Class({
Extends: Secret,
divulge: function(){
return 'Keep it a secret, ' + this.secretMethod();
}
});
var another = new SubSecret();
another.secretMethod(); // throws error..
another.divulge(); // returns 'Keep it a secret, Shh!'
I’d Mix It
One of the more interesting features that MooTools implements is Mixins. A Mixin is a special class that adds functionality to other classes, but is not necessarily usable on its own. Mixins are a great way to add more functionality to your classes and they encourage code reuse.
For instance, take a look at this code:
var Phone = new Class({
sound: 'phone.ogg',
initialize: function(number){
this.number = number;
},
call: function(from){
this.ring();
new Notification('Call from ' + this.from);
},
ring: function(sound){
sound = sound || this.sound;
new Sound(sound).play();
}
});
var AlarmClock = new Class({
sound: 'alarm.ogg',
initialize: function(alarmTime){
this.time = alarmTime;
},
alarm: function(time){
if (time == this.time) {
this.ring();
new Notification('Wake up sleepy head!');
}
},
ring: function(sound){
sound = sound || this.sound;
new Sound(sound).play();
}
});
As you can see, these two classes share two members: a sound
property and a ring
method. Right now, it’s not really DRY, because you have to write down these members twice. So what can we do? Maybe we could create a superclass from which these two items could inherit. How about RingableObjects
? Hmm, that doesn’t sound right…
Mixins come in handy in situations like this:
// A Mixin..
var Ringer = new Class({
sound: 'ring.ogg',
ring: function(sound){
sound = sound || this.sound;
new Sound(sound).play();
}
});
var Phone = new Class({
Implements: Ringer,
initialize: function(number){
this.number = number;
this.sound = 'phone.ogg';
},
call: function(from){
this.ring();
new Notification('Call from ' + this.from);
}
});
var AlarmClock = new Class({
Implements: Ringer,
initialize: function(alarmTime){
this.time = alarmTime;
this.sound = 'alarm.ogg';
},
alarm: function(time){
if (time == this.time) {
this.ring();
new Notification('Wake up sleepy head!');
}
}
});
To add a mixin to your class, you use the handy Implements
mutator. This mutator takes either a single class object, or multiple class objects in an array:
// A single mixin..
new Class({
Implements: MyMixin
});
// Multiple mixins..
new Class({
Implements: [MyMixin, Events, Options]
});
When the class constructor sees the Implements
key in your class constructor object, it checks the value (or values) if they’re classes. If they are, a new instance of the class is created and then all methods and properties are copied to your class.
Using mixins, you could implement small, related methods and properties in a new class and add them to your own classes. And because there’s no limit to the amount of mixins you could add to your class, you could collect additional functionality from other classes this way. This is a variant of multiple inheritance that certainly has a lot of uses.
Because a mixin is just a class, in theory, you can use any other class as a mixin. But take note that there are some quirks that are brought about by the newer versions of MooTools. One of the biggest is that from version 1.2.2 onwards (including MooTools 2), the initialize
method of a mixin never gets called. So doing something like this won’t work:
var Ringer = new Class({
initialize: function(){
this.sound = 'ring';
},
ring: function(){
return this.sound;
}
});
var Mobile = new Class({
Implements: Ringer,
call: function(){
return this.ring();
}
});
new Mobile().call();
// returns 'ring' in 1.2.1 below.
// returns undefined in 1.2.2 up.
The solution is to define any members that your mixin needs in the class definition itself and ditch your initialize
method.
var Ringer = new Class({
sound: 'ring',
ring: function(){
return this.sound;
}
});
Another quirk is related to how mixins are implemented. Like I said before, for each mixin you add into your class, the Class
constructor initializes an instance of that class and copies all methods and properties into your own class. Because they’re copied into your class directly, changes in the original mixin are not reflected to your class.
var Meme = new Class({
hitMe: function(){
return 'HOW IS BABBY FORMED?';
}
});
var MyMeme = new Class({
Implements: Meme
});
Meme.implement({
hitMe: function(){
return 'Is this gonna be forever????';
}
});
new MyMeme().hitMe(); // returns 'HOW IS BABBY FORMED?'..
new Meme().hitMe(); // returns 'Is this gonna be forever????'
In order to make sure that any new changes to your mixin are propagated, you’ll have to implement it on your class too. There’s no other way to do this, but fortunately we could use the Native.implement
method I discussed before to make the process easier:
var Meme = new Class({
hitMe: function(){
return 'HOW IS BABBY FORMED?';
}
});
var MyMeme = new Class({
Implements: Meme
});
new MyMeme().hitMe(); // returns 'HOW IS BABBY FORMED?'..
Native.implement([Meme, MyMeme],{
hitMe: function(){
return 'Is this gonna be forever????';
}
});
new MyMeme().hitMe(); // returns 'Is this gonna be forever????'..
new Meme().hitMe(); // returns 'Is this gonna be forever????'..
And finally, there’s a gotcha related to the one above: you can’t use this.parent
to call the original method from a mixin. Again, because mixins are implemented directly in your class, you aren’t really inheriting from them but rather copying their methods, so this.parent
fails:
var Meme = new Class({
hitMe: function(){
return 'HOW IS BABBY FORMED?';
}
});
var MyMeme = new Class({
initialize: function(){}
});
MyMeme.Lolz = new Class({
Extends: MyMeme,
Implements: Meme,
hitMe: function(){
// do other suff;
return this.parent();
}
});
new MyMeme.Lolz().hitMe(); // throws an error..
So how will you call overriden methods? The short answer is that you’ll have to use the prototype of your mixin and call it via apply
:
var Meme = new Class({
hitMe: function(){
return 'HOW IS BABBY FORMED?';
}
});
var MyMeme = new Class({
initialize: function(){}
});
MyMeme.Lolz = new Class({
Extends: MyMeme,
Implements: Meme,
hitMe: function(){
// do other suff;
return Meme.prototype.hitMe.apply(this);
}
});
new MyMeme.Lolz().hitMe(); // returns 'HOW IS BABBY FORMED?'..
Ugly right? In the second part of this series about Natives, I talked about generics. Wouldn’t it be better if we could have generics for our mixins so we could do Meme.hitMe
instead?
Unfortunately, MooTools doesn’t have a Class.genericize method. So here’s one for MooTools 1.2.2 and above, enjoy:
/*
Class.genericize
Creates generics for class methods.
*/
Class.genericize = function(klass){
if (klass instanceof Function) {
var proto = Class.instantiate(klass);
for (var i in proto) Native.genericize(klass, i);
}
};
var Meme = new Class({
hitMe: function(){
return 'HOW IS BABBY FORMED?';
}
});
Class.genericize(Meme);
var MyMeme = new Class({
initialize: function(){}
});
MyMeme.Lolz = new Class({
Extends: MyMeme,
Implements: Meme,
hitMe: function(){
// do other suff;
return Meme.hitMe(this);
}
});
new MyMeme.Lolz().hitMe(); // returns 'HOW IS BABBY FORMED?'..
Of course, after all those gotchas, you need some good news: do you remember our protect
method for function? Well, the good news is that they work for mixins too!
var Secret = new Class({
tellSecret: (function(){
return 'ssssh!'
}).protect()
});
var Teller = new Class({
Implements: Secret,
divulge: function(){
return this.tellSecret();
}
});
var teller = new Teller();
teller.tellSecret(); // throws an error..
teller.divulge(); // returns 'ssssh!'..
Quick Quiz X
Now here’s a quick quiz: what’s the value of x?
var A = new Class({
tell: function(){
return 'A!';
}
});
var B = new Class({
tell: function(){
return 'B!';
}
});
var C = new Class({
Extends: A,
Implements: B
});
var x = new C().tell();
If you answered ‘A!’, then you’re totally wrong because it’s ‘B!’! No cookie!
Huh? You might be wondering: “Hey wait! C inherits from A! B is just a mixin! How did that happen?”
The answer is that MooTools implements methods and properties in the order they’re declared in your class. In the code above, we extended class C
first with A
, so it inherits method tell
from A
. But right after that, we implement class B
into the class, copying its tell
method and therefore overwriting the original one from A
. So if we do it in reverse, the return value would be ‘A!’ rather than ‘B!’:
var C = new Class({
Implements: B
Extends: A
});
var x = new C().tell(); // returns 'A!'
This in turn, leads to another gotcha:
var A = new Class({
tell: function(){
return 'A!';
}
});
var B = new Class({
tell: function(){
return this.parent();
}
});
var C = new Class({
Extends: A,
Implements: B
});
var x = new C().tell();
Because B
is implemented right after A
, we get access to the this.parent
method and we can call it from our mixin. There are valid reasons why you’d want to do this–but the rule of thumb is don’t! Mixins should be standalone and shouldn’t depend too much on other classes, otherwise they lose their usefulness.
Class Dismissed!
Well boys and girls, that’s it for this episode of Up The Moo Herd. I hope you learned a trick or two from this post, and feel free to share your comments, suggestions or questions below.
The next part of the series will be posted next Tuesday, and we’ll be talking about the various utility functions and undocumented methods that are very useful, yet rarely used by many.
And don’t forget to subscribe to the RSS feed and follow me on Twitter (@keeto) for updates!
Comments
Post a Comment
You can no longer comment on this post. If you have something important to say, reach out via Twitter or via email (remove the nospm part).