Up The Moo Herd II Native Flora and Fauna

2009-07-20

In this second installment of the Up the Moo Herd: MooTools Tips and Tricks series, we’re going to tackle Natives, a topic included in the list of lesser-known things about MooTools.

Before anything else, I’d like to apologize for the delay in posting this article. I’ve been a little busy with a MooTools related project (which we will be releasing for public consumption soon), so I wasn’t able to finish of the rest of the articles. But the good news is that the next parts of the series are already done (whew!), and they’ll be posted by Friday.

I’d also like to thank Valerio Proietti, the lead developer of MooTools, for taking some time to answer some of my questions on this topic as well as for the idea of the Table native (which will be available in Moo2).

With that said, let’s explore the Native flora and fauna as we climb up the herd…

Going Native

In the current versions of MooTools-Core, the first constructor function you’ll encounter is a curious one called Native. Curious because it’s not documented anywhere, even though it’s one of the most important functions in the library.

To put it simply, the Native constructor simplifies the process of extending built-in native types by adding several useful methods. It actually does three things:

  1. It adds an implement method that enables you to easily and safely add methods to existing native types.
  2. It adds members to the native datatypes that makes it easier to deduce the datatype via $type and Constructor.Type
  3. It automates the process of adding generics.

There’s really not much to say about the Native constructor, primarily because it’s non-public API and secondly because most people will rarely use it. MooTools already processes most of the native datatypes and what the core-library implements is enough for most people.

Of course, MooTools also allows us to implement other natives aside from wrapping the built-in ones. Let’s implement a trivial native called Table, which is essentially a Hash, but it keeps the information in a storage member within the object instead on the object itself.

var Table = new Native({ name: 'Table', initialize: function(){ this.storage = {}; }, afterImplement: function(property, value){ console.log(property, value); } }); Table.implement({ get: function(key, fallback){ return this.storage[key] || fallback; }, set: function(key, value){ this.storage[key] = value; return this; }, erase: function(key){ if (this.storage[key]) delete this.storage[key]; return this; }, forEach: function(fn, bind){ for (var key in this.storage) { fn.apply(bind, [this.storage[key], key, this]); } }, empty: function(key){ Table.each(this, function(k, v){ delete this.storage[key] }, this); return this; } }); Table.alias('forEach', 'each');

First, we construct the new native by passing an object with a name and an initialize member to the Native constructor. The name is used for the $type() function so that we can do $type(myTable) == 'table'. The initialize function is the actual constructor for our native, and in this example, it only sets the storage object.

After the actual creation of the natives, we implement the actual methods of the native (basic things like get, set, each, etc). And to finalize things, we add an alias, so that you can do both a myTable.each and myTable.forEach.

As you might have noticed, creating a native is very similar to creating a class, and for the most part that’s true. So why are we using classes instead of creating natives?

  1. The Native API is private—you’re not expected to use it because it will break in the next version of MooTools.
  2. Natives are more appropriate for creating new types that are similar to the built-in types.
  3. Natives don’t offer object-oriented features that Classes offer, like mutators and mixins.
  4. Classes give you a cleaner syntax: you only have to pass a single object to the Class constructor.

But of course, there’s nothing stopping you from creating your own Natives. In fact, you might even consider using natives if you want more control via afterImplement, or if you want an easier way to add generics…

Mr. Ruffles and Sport? Nope, Cat and Dog!

Sometimes, you want to use a method from another data type on an object of another data type. Like here:

var myString = 'this_is_a_string'; for (var x = 0; x < myString.length; x++) { var char = myString[x]; checkIfCharIsLegal(char); }

Wouldn’t it be easier if we could just do myString.each() like an array? But because strings don’t have an each() method, we’re stuck. This is where generics come in.

A Generic Method, or generics for short, is a method of a data type that can be applied indirectly instead of calling it from an instance. Strings don’t have an each method, but arrays do—so let’s use it!

var myString = 'this_is_a_string'; Array.each(myString, function(char){ checkIfCharIsLegal(char); });

A generic works just like the type method, but instead of using an instance of a type as the this, you pass another object you want to act as this for that function. The following lines are the same:

// as a method: $(document.body).getElements('a'); // as a generic: Element.getElements(document.body, 'a');

Almost all methods for both native datatypes and MooTools specific datatypes are available as generics. In fact, the Native constructor keeps another trick up its sleeve: all implemented methods are also made into generics. So in our Table code above, we also get the following generics automatically:

Table.get(table, key); Table.set(table, key, value); Table.erase(table, key); Table.each(table, function, bind); Table.empty(table);

I’m Creating New Species..

So what’s the big deal about Natives anyway? If it’s not public API, why should I care? Well, you should care because MooTools uses Native to drive one of its more powerful features: language modification. One of the things that make MooTools different from other Javascript libraries is the fact that it allows you to easily extend the language itself and add more features that aren’t available.

As you might know, the object-oriented nature of Javascript enables you to modify the language and add new features to native datatypes via modifying the prototypes. MooTools did this in older versions, so you might have seen declarations like Array.prototype.each = function().... This, of course, was both cumbersome and too destructive, and so the process was streamlined via the Native constructor.

All MooTools natives have a special method called implement, which allows you to add new methods to data types. You can use it in two ways:

// key/value way.. Array.implement('count', function(){... }); // or hash way... Array.implement({ count: function(){ ... } });

The second one is preferred in most cases because you’ll be able to implement several methods at once.

An example: I recently needed two methods that aren’t available in strings, rtrim and parsePath. If I was to do it using prototype, I would have ended up with something like this:

// native way... String.prototype.rtrim = function(str){ if (this.lastIndexOf(str) == this.length - 1) { return this.substring(0, this.lastIndexOf(str)); } return this; }; String.prototype.parsePath = function(){ return this.replace(/({appDir})/, system.getcwd()).rtrim('/') + '/'; };

If I wanted generics, I’d have to add this too:

// generics: native way... String.rtrim = function(item, str){ return String.prototype.rtrim.apply(item, str); }; String.parsePath = function(item){ return String.prototype.parsePath.apply(item); };

Yikes! That’s too much compared to this chunk of MooTools code implements the methods and creates generics at the same time:

// MooTools way.. String.implement({ rtrim: function(str) { if (this.lastIndexOf(str) == this.length - 1) { return this.substring(0, this.lastIndexOf(str)); } return this; }, parsePath: function(){ return this.replace(/({appDir})/, system.getcwd()).rtrim('/') + '/'; } }); // Nifty!

If you want to implement a method in several data types at once, you can use the Native.implement generic:

// Implement for multiple types Native.implement([String, Array, Hash], { myMethod: function(){ // ... }, anotherMethod: function(){ // ... } });

MooTools also makes sure that it’s not destructive. For instance, take the forEach method. Mozilla and Webkit already have forEach methods for arrays, but IE doesn’t. If you declare the forEach method directly using Array.prototype, you’ll be overwriting the native methods from Webkit and Mozilla if you’re not careful. On the other hand, MooTools protects native methods, making sure that you don’t overwrite those methods if they’re already present.

Sometimes, this protection scheme will make you scratch your head. If you already implemented a method in one of your scripts, but you want to change it again in another script, doing something similar to this will not work:

String.implement('yes', function(){ return 'yes'; }); var yes = 'myString'.yes(); // yes == 'yes' String.implement('yes', function(){ return 'no'; }); var no = 'myString'.yes(); // no == 'yes'

This is because String.yes() is protected. In order to modify this method, you’ll have to force MooTools to replace the method by passing true to the additional parameter of implement (aptly named force):

String.implement('yes', function(){ return 'no'; }, true); var no = 'myString'.yes(); // no == 'no'. Hooray!

It’s a Nativity

I hope you learned some things in this post, especially about extending the language. Personally, I believe that when you stop looking for a feature and implement it yourself, you’ve crossed the threshold from being a casual MooTools hobbyist to a MooTools power-user. It’s this ease of being able to shape Javascript to a form that fits your needs that really separates MooTools from all other libraries that offer nothing but DOM-manipulation.

In the next installment of this series, we’ll learn more tips and tricks, focused on Classes. Be sure to subscribe to the RSS feed and follow me on Twitter (@keeto) for updates on the next part of the series.