Up The Moo Herd III It's Classy, Not Classic

2009-07-23

It’s time for another episode of the Up the Moo Herd: MooTools Tips and Tricks series, where we’ll talk about classes, the backbone of MooTools. Other JS libraries thrive using plugins and extensions, but MooTools keeps its object-oriented focus by using classes and mixins to new functionality to the framework.

Due to length concerns, I split up the article on classes into two parts. This one tackles constructors, implement and extend, singletons, and private methods and variables. The next part will deal with mutators, mixins, inheritance and reusable code.

I went directly to the various tips and tricks that involve classes in this post rather than explain the basics. If you get lost, it might be good to get a firmer grasp on how classes work. The series introduction linked above contains links to tutorials and sites that would help you to do just that.

The title for this post is taken from Breathe Carolina‘s second album, which I was listening to while writing this article. They’re a really awesome band, and I admire them for being able to make Miley Cyrus’ “See You Again” palatable.

With that, let’s start getting classy!

Getting Classy

Class is the constructor used for creating classes in MooTools. There are two ways to create a class, the first being the standard way, which is available since version 0.87 (maybe even older–but that’s the version I could verify without digging too much). This involves passing an object that acts as the prototype of the class:

// standard way of creating classes: var Barn = new Class({ initialize: function(name){ this.fowl = name; }, getFowl: function(){ return this.fowl; }, setFowl: function(name){ this.fowl = name; return this; } }); var myBarn = new Barn('chicken'); myBarn.getFowl(); // returns 'chicken' myBarn.setFowl('turkey') myBarn.getFowl(); // returns 'turkey'

The second way comes from the new Class/Class.js, which was overhauled in MooTools 1.2.2. From this version onwards, Class will accept a function that will become your initialize method instead of an object:

// alternative, constructor based way: var Barn = new Class(function(name){ this.fowl = name; }); Barn.implement({ getFowl: function(){ return this.fowl; }, setFowl: function(name){ this.fowl = name; return this; } }); var myBarn = new Barn('chicken'); myBarn.getFowl(); // returns 'chicken' myBarn.setFowl('turkey') myBarn.getFowl(); // returns 'turkey'

This alternative way might look alien at first, but it opens up new possibilities that we’ll use later.

Implement and Extend

Classes have an implement method used to add new methods. Similar to the implement function in Natives, it can be used in two ways:

// key-value way.. Barn.implement('feedFowl', function(){...}); // hash way.. Barn.implement({ feedFowl: function(){ ... }, prepareFowl: function(){ ... } });

Always use the second syntax if you’re adding multiple methods to avoid repeated calls to implement.

Classes also have an extend method. While implement adds methods to the prototype of the class, extend adds methods to the class constructor itself. In other words, implement creates instance methods and members, extend creates class methods and variables:

var Barn = new Class(); // instance method using implement.. Barn.implement({ instanceMethod: function(){ return 'From an instance!'; } }); // class method using extend.. Barn.extend({ classMethod: function(){ return 'From the class itself!'; } }); var myBarn = new Barn(); typeof(myBarn.instanceMethod); // returns 'function' myBarn.instanceMethod(); // returns 'From an instance!' typeof(myBarn.classMethod); // returns 'undefined' typeof(Barn.classMethod); // returns 'function' Barn.classMethod(); // returns 'From the class itself!' typeof(Barn.instanceMethod); // returns 'undefined'

extend is a very useful method, especially if you want to keep your code clean. Instead of doing this:

var Barn = new Class(); Barn.fowlCount = 0; Barn.countFowls = function(){ return Barn.fowlCount; }; Barn.addFowl = function(){ Barn.fowlCount += 1; };

You can do this instead:

var Barn = new Class(); Barn.extend({ fowlCount: 0, countFowls: function(){ return this.fowlCount; }, addFowl: function(){ this.fowlCount += 1; } });

Your Class Just Changed His Status to Single(ton)

There are several available techniques for creating singletons, so let me share the ones I know and I’ve used before.

The first one is the easiest and the best solution: use an object:

// Create the singleton object.. var Singleton = { greeting: 'Howdy ', greet: function(name){ return [this.greeting, name, '!'].join(' '); } }; Singleton.greet('Mark'); // returns 'Howdy Mark!'; // this will throw an error because Singleton is not a constructor. var newSingleton = new Singleton();

But how about mixins like Events? Use the $extend function:

// Create the singleton object.. var Singleton = { name: 'Mark', greeting: 'Howdy' greet: function(name){ return [this.greeting, name || this.name, '!'].join(' '); } }; $extend(Singleton, new Events()); Singleton.addEvent('greet', function(){ this.greet('Joseph'); // returns 'Howdy Joseph!' this.greet(); // returns 'Howdy Mark!' }); Singleton.fireEvent('greet');

There’s also a way to get singletons by using vanilla MooTools classes. The way to do that is to keep your class declaration inside a closure, create a local variable to hold the instance to your singleton and use that for every call:

(function(){ // variable for holding the instance.. var instance = null; this.Singleton = new Class({ initialize: function(name){ if (instance) return instance; this.name = name; instance = this; }, getName: function(){ return this.name; } }); })(); var mySingleton = new Singleton('Mark'); mySingleton.getName(); // returns 'Mark'; var newSingleton = new Singleton('Joseph'); newSingleton.getName(); // returns 'Mark'; // both instances refer to the same object.. mySingleton == newSingleton;

The second one has the benefit of having a familiar syntax, and mixins and other class features work without having to use a different technique. But because it’s still a class, your singleton will have a public constructor available, so make sure that you document that it’s a singleton. It’ll be confusing for other people who’ll try to use your code when they create new instances and fail–so document!

Another way is the double new operator hack:

var Singleton = new new Class({...})

What happens here is that the second new operator is used to create an new Class, which returns a constructor function for the class. The first new is used to create a new instance of the class. To avoid confusion and to allow for passing constructor variables, you can group them into parentheses.

var Singleton = new (new Class({ initialize: function(name){ this.name = name; }, getName: function(){ return this.name; } }))('Mark'); Singleton.getName(); // returns 'Mark' // This throws an error.. new Singleton('Joseph');

Keeping Secrets

Closures are the easiest way to create private variables. You can wrap the class declaration in a closure and declare your private variables inside it to create a local scope:

(function(){ var secret = 'I like bacon.'; this.Secretive = new Class({ getSecret: function(){ return secret; }, setSecret: function(newSecret){ secret = newSecret; return this; } }); })(); var secret = new Secretive(); typeof(secret.secret); // returns 'undefined' secret.getSecret(); // returns 'I like bacon.' secret.setSecret('I like bacon too!'); secret.getSecret(); // returns 'I like bacon too!'

I mentioned a few paragraphs ago that MooTools 1.2.2 and above allows passing a constructor function to Class rather than a prototype object. This feature gives us another way to create private variables:

var Secretive = new Class(function(){ var secret = 'I like bacon.'; $extend(this, { getSecret: function(){ return secret; }, setSecret: function(newSecret){ secret = newSecret; return this; } }); }); var secret = new Secretive(); typeof(secret.secret); // returns 'undefined' secret.getSecret(); // returns 'I like bacon.' secret.setSecret('I like bacon too!'); secret.getSecret(); // returns 'I like bacon too!'

You might notice that we use $extend inside the function because this.implement wouldn’t be available inside the constructor function. You can still use implement after you declared your constructor, but make sure that any function that needs access to the private variable are declared inside the constructor. Otherwise, it’ll be out of scope and you won’t be able to access the variable.

Can’t Touch This!

The same techniques for creating private variables can be used to create private methods. Here’s one involving a closure:

(function(){ // our private method.. var secretFunction = function(){ return 'The cake is a lie.'; }; this.Secretive = new Class({ tellSecret: function(){ return secretFunction(); } }); })(); var secrets = new Secretive(); secrets.tellSecret(); // returns 'The cake is a lie.' typeof(secrets.secretFunction); // returns 'undefined'

And here’s one using the new MooTools feature of passing a constructor function:

var Secretive = new Class(function(){ // our private method.. var secretFunction = function(){ return 'The cake is a lie.'; }; $extend(this, { tellSecret: function(){ return secretFunction(); } }); }); var secrets = new Secretive(); secrets.tellSecret(); // returns 'The cake is a lie.' typeof(secrets.secretFunction); // returns 'undefined'

Fortunately, you don’t need to use the techniques above because of a new feature that arrived in MooTools 1.2.2. It solves the problem of creating private methods using a new Function method called protect. What it does is it sets some variables on your function that makes it impossible to call the method from the outside of a class. It will still be visible, but it can’t be invoked:

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.'

protect is an exciting addition to MooTools because we now get true protected methods without using complex closures. Awesome, right?

Class Just Ended

This concludes this episode of the Up the Moo Herd. I hope you learned some new tricks to add to your MooTools repertoire. If you have tips and tricks related to the ones I presented above, make sure you post it on the comments. It’ll be awesome to hear from you!

In the next part of the series, we’ll talk more about classes, with focus on mixins, mutators and reusable code, so don’t forget to subscribe to the RSS feed and follow me on Twitter (@keeto) for updates.