Up The Moo Herd II Native Flora and Fauna
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:
- It adds an
implement
method that enables you to easily and safely add methods to existing native types. - It adds members to the native datatypes that makes it easier to deduce the datatype via
$type
andConstructor.Type
- 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?
- The Native API is private–you’re not expected to use it because it will break in the next version of MooTools.
- Natives are more appropriate for creating new types that are similar to the built-in types.
- Natives don’t offer object-oriented features that Classes offer, like mutators and mixins.
- 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.
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).