Up The Moo Herd V Evident Utensil
Howdy folks! It’s that time again when we share MooTools tips and tricks! Yes, it’s another Up The Moo Herd post, and this time we tackle those little known utility functions in MooTools. These little buggers might be short and sweet, but these functions power a bulk of the MooTools machinery, and by learning to use them you can improve your own code.
Before we begin, let me apologize for the delay in the series. The past few months has been (and still is) taxing for me as I try to arrange various parts of my life. And as much as I love to keep on churning more and more posts on MooTools, I still have other priorities to consider. And yes! If you’re looking for a frontend developer or a JavaScript guy to work with, I’m available! (nudge, nudge, wink, wink).
And as in the previous two parts of this series, the title of this post is again taken from a song–this time from the debut album of Chairlift, “Does This Inspire You,” one of my favorite albums to listen to on a gloomy, rainy afternoon.
So with those things said, let’s start getting useful with utility functions!
$ Makes The World Go Round
You’ll notice that there are a few functions in MooTools that start with the $
(dollar) symbol. Some people refer to them as the dollar-functions, but that’s confusing because we already have two dollar-functions–$()
and $$()
–that are the two most used functions when working with the DOM.
So let’s call them utility functions instead.
A few utility functions you might already know, and a few you might have already used yourself. Take for instance the highly loved $type()
function. It takes a single argument and returns the type of that argument.
// JavaScript's typeof operator..
typeof "a"; // 'string'
typeof 1; // 'number'
typeof {}; // 'object'
typeof []; // 'object'.. wait, what?
typeof /m/; // 'object' in some, 'function' in others..
typeof $('id'); // 'object'.. you're not helping!
// $type..
$type("a"); // 'string'
$type(1); // 'number'
$type({}); // 'object'
$type([]); // 'array'.. nice!
$type(/m/); // 'regexp'.. perfect!
$type($('id')); // 'element'.. now you're just showing off..
So $type()
, with its ability to differentiate between 17 different types, is one of the most useful functions in MooTools. But there are other ones you might have not heard of…
Extend and Merge
I’ve often used one of my favorite utility functions in the past parts of this series: $extend()
. This function takes 2 objects as arguments, original
and extension
, and takes the members from extension
and adds them to original
:
var original = {firstName: 'Conan'};
var extension = {lastName: 'OBrien'};
$extend(original, extension);
original.firstName; // 'Conan'
original.lastName; // 'OBrien'
So why didn’t I place a variable declaration on line 4? That’s because $extend()
makes the changes in-place–new members are added directly to the original object (and any existing member with the same key is overwritten). Because we already defined our original object in line 1, we can reference the variable in the next lines. This is why I have lines like $extend(Singleton, new Events)
from previous post.
However, if you’re gonna pass object literals to $extend()
, make sure it’s part of a variable definition, so you’ll be able to use the object:
// Passing in literals..
var myObj = $extend({firstName: 'Conan'}, {lastName: 'OBrien'});
myObj.firstName; // 'Conan'
myObj.lastName; // 'OBrien'
Now there’s another utility function similar to $extend()
called $merge()
. Similar because they act the same way (ie. take members from an object and add it to another), but they are totally different functions for different purposes.
The first difference is that you can pass multiple objects to $merge()
–you’re not limited to two per invocation. Second is that it doesn’t make changes in-place. Instead, it creates a new object and adds members from all objects passed:
var original = {firstName: 'Conan'};
var extension = {lastName: 'OBrien'};
var another = {codename: 'Coco'};
var merged = $merge(original, extension, another);
original.firstName; // 'Conan'
original.lastName; // undefined
merged.firstName; // 'Conan'
merged.lastName; // 'OBrien'
merged.codename; // 'Coco'
As you can see, $merge()
didn’t touch the original
object, unlike $extend()
, but returned a new object with the members from all the objects passed.
Breaking Ties That Bind
There’s a bigger difference between $extend()
and $merge()
, one that touches another utility method. Consider the following snippet using $extend()
:
var person = {name: 'Conan OBrien'};
var show = {name: 'Tonight Show', host: person};
var tonightShow = {};
$extend(tonightShow, show);
// So far so good..
tonightShow.name; // 'Tonight Show'
tonightShow.host.name; // 'Conan OBrien'
// Let's screw things..
person.name = 'Jay Leno';
tonightShow.host.name; // 'Jay Leno'... shit.
Now, with $merge
:
var person = {name: 'Conan OBrien'};
var show = {name: 'Tonight Show', host: person};
var tonightShow = $merge({}, show);
tonightShow.name; // 'Tonight Show'
tonightShow.host.name; // 'Conan OBrien'
// Can we screw things?
person.name = 'Jay Leno';
tonightShow.host.name; // 'Conan OBrien'... hooray!
With the $extend()
code, when we changed the value of the name
property of our person
object, the value is also reflected on our tonightShow.host
object. This happens because on line 2, we reference the person
object as the value of the host
property of our show
object. With $extend()
, all references are kept: any changes in an object will be reflected on any property that points to that object.
However, this does not happen with $merge()
because this function dereferences objects. This means that if one of the members in an object references or points to another object, it creates a new object and copies all members from that referenced object so that no links are kept. In other words, it kind of clones objects so that your new object is really new–there’s nothing that links it from the old object.
In MooTools, we call this process unlinking, and we have a utility function called $unlink()
which does this for us:
// Linked..
var person = {name: 'Mark'};
var human = person;
person.name; // 'Mark'
human.name; // 'Mark'
human.name = 'Joseph';
person.name; // 'Joseph';
// Unlinked..
person = {name: 'Mark'};
human = $unlink(person);
person.name; // 'Mark'
human.name; // 'Mark'
human.name = 'Joseph';
person.name; // 'Mark';
$unlink
is deep-recursive, so if you have objects within objects, it copies and unlinks all of these until it reaches the end. It’s a very useful function especially when you’re cloning objects but don’t wanna keep references.
Functuations
There are three useful functions included in core that deal with functions. The first is $empty
which is just what it name implies: an empty function, just like function(){}
. Why does this function even exists if it does nothing? Well, it’s used as a placeholder function and saves you from typing function(){}
too much.
In the old days of MooTools, $empty
was used as a placeholder in -core class definition of the event handlers, like so:
var Car = new Class({
Implements: Options,
options: {
onDrive: $empty,
onStart: $empty
},
initialize: function(options){
this.setOptions(options);
}
});
This is no longer considered good style, and you’ll notice that in current versions, these old lines are now commented out (but retained for documentation).
Another use for $empty
is for object mocking, especially event objects. Say for instance you have this code:
$('myLink').addEvent('click', function(e){
e.stop();
// do more things..
});
// further on..
$('myLink').fireEvent('click');
The last line above will throw an error (“Undefined ‘e’ does not have a method ‘stop’”), because the handler event for #myLink:click
expects an event object. In order to make it work, you can pass a second argument to fireEvent()
–a mock object, which is an object that pretends to be another kind. In this case the event handler expects an event object with the method stop()
, so let’s give it one:
$('myLink').addEvent('click', function(e){
e.stop();
// do more things..
});
// further on..
$('myLink').fireEvent('click', {stop: $empty});
Instead of actually creating a complex stop
method for our mock object, we just use $empty
and be done with it. So if you need mock objects with methods that really don’t matter if they return anything, $empty
is a handy function.
The second utility function is called $lambda()
, and this simple function creates a function that returns whatever value is passed:
$lambda(1)(); // returns 1;
$lambda('test')(); // returns 'test';
Again, you might be wondering how this function is useful–it only creates a function that returns a value, what’s so great about that? Well, aside from the fact that it could be used to create quick functions on the fly, one thing about $lambda()
is that if the value passed is a function, it will return that exact same function. This is very useful in cases where you’d want to accept either functions that return native values or actual native values for your methods.
// Without $lambda()..
var Dict = new Class({
storage: {},
set: function(key, value){
// Check whether the value is a function,
// if yes, call it and then use the return
// value..
var isFunc = ($type(value) === 'function');
this.storage[key] = isFunc ? value() : value;
return this;
},
get: function(key){
return this.storage[key];
}
});
var dict = new Dict();
dict.set('name', function(){
return 'markee';
});
dict.get('name'); // returns 'markee';
// With $lambda()..
var Dict = new Class({
storage: {},
set: function(key, value){
this.storage[key] = $lambda(value)();
return this;
},
get: function(key){
return this.storage[key];
}
});
var dict = new Dict();
dict.set('name', function(){
return 'markee';
});
dict.get('name'); // returns 'markee';
The two implementations work the same, but the second is cleaner and removes the need for explicit checks on your part.
Finally, we have $try()
, an awfully useful function that seems to be overlooked by many. You probably already know that we have a try/catch
statements in JavaScript, and $try()
is really just a spiced up version of this statement. The function accepts multiple functions and runs them; the return value of the first function that doesn’t error out or raise an exception will be returned by the $try()
function. If all functions fail, then it returns null.
An important example for the use of $try()
MooTools’ implementation of Browser.Request
, which is the base for XMLHttpRequest
classes such as Request
. In order to make XMLHttpRequest
code cross-browser, you’ll have to take note of the fact that Internet Explorer uses two different constructor for XHRs (while all other browsers implement the same–nice going IE!). For instance, one of the first books on Ajax that I’ve read in 2007 had a code similar this to implement cross-browser XHRs:
var XHR = null;
// Check for IE..
try {
// Newer JS version..
XHR = new ActiveXObject("MSXML2.XMLHTTP");
} catch(e) {
try {
// Older version..
XHR = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) {
XHR = null;
}
}
// Non-IE browsers..
if (!XHR && typeof XMLHttpRequest != 'undefined') {
XHR = new XMLHttpRequest();
}
Yes, those are two try/catch
blocks you’re seeing there (and they’re nested!). Now let’s see how it would look like using $try()
:
var XHR = $try(function(){
// Non-IE browsers..
return new XMLHttpRequest();
}, function(){
// Newer IE JS version..
return new ActiveXObject("MSXML2.XMLHTTP");
}, function(){
// Older IE version..
return new ActiveXObject("Microsoft.XMLHTTP");
});
The above code is almost how MooTools implements Browser.Request
. And it’s certainly more readable than the first one.
Checkmates
There are two simple checker functions in MooTools, $defined()
and $chk()
. Before we explain why these functions exists, you’ll need to learn a few things about falsy values in JavaScript.
There are six falsy values in JavaScript: false
, null
, undefined
, 0
, NaN
(not a number) and ""
(an empty string). They are called falsy values because when used in logical statements, their values are treated as false
.
Easy enough to understand, right? Now let’s remember that in JavaScript, we have certain idiomatic expressions that make our code cleaner. For instance, we have the use of the ||
operator for setting default values instead of using if/else
statements:
// using if statements..
var Car = function(name){
// if the name value is passed,
// use it..
if (name){
this.name = name
// if not, use a generic name..
} else {
this.name = 'Generic Car';
}
};
new Car('Kart').name; // returns 'Kart'
new Car().name; // returns 'Generic Car'
// using the || operator..
var Car = function(name){
// same as above, but cleaner..
this.name = name || 'Generic Car';
};
new Car('Kart').name; // returns 'Kart'
new Car().name; // returns 'Generic Car'
The two snippets are essentially the same, but the second one is cleaner. Another thing to note is the use of the ternary operator, which is like a compact if/else
statement. The same code above can be written in a ternary operator style, like so:
// using the ternary operator..
var Car = function(name){
this.name = (name) ? name : 'Generic Car';
};
new Car('Kart').name; // returns 'Kart'
new Car().name; // returns 'Generic Car'
It seems that all’s well in the JavaScript world. But wait–what about cases where some of the values passed are falsy? For example, you have a property in your Car class called automatic
, which could either be true
or false
. If the user doesn’t pass a value, it should default to true
. Because false is a falsy value, using the ||
or the ternary operator won’t suffice:
// using the || operator..
var Car = function(automatic){
this.automatic = automatic || true;
};
new Car(true).automatic; // returns true
new Car(false).automatic; // returns true.. doh!
// using the ternary operator..
var Car = function(automatic){
this.automatic = (automatic) ? automatic : true;
};
new Car(true).automatic; // returns true
new Car(false).automatic; // returns true.. double doh!
We have to remember that we’re just dealing with simple rules here: if the user passes a value to automatic
, we want our code to use the value (whether it’s true
or false
) and if not, we want it to be true
by default.
We remedy this by using the $defined()
function. It’s a really simple function that checks whether a function’s value is equal to undefined
and returns false
if it is not and true
if it is. This is useful for allowing the other falsy values in your code:
var Car = function(automatic){
this.automatic = $defined(automatic) ? automatic : true;
};
new Car(true).automatic; // returns true
new Car(false).automatic; // returns false.. hoorah!
You’ll have to remember that $defined()
actually checks whether the value is undefined
, and will return true
for all falsy values (even false
), except for undefined
, null
and NaN
. The second one is tricky because if you have var x = null
and you do $defined(x)
, it will return false
even if x
is defined–because its value is null
.
The function $chk()
is used to allow for 0
. Because 0
is a falsy value, using it with the ||
and ternary operator statements will also fail:
// using the || operator..
var Car = function(miles){
this.miles = miles || 100;
};
new Car(120).miles; // returns 120
new Car(0).miles; // returns 100.. bleh!
// using the ternary operator..
var Car = function(miles){
this.miles = (miles) ? miles : 100;
};
new Car(120).miles; // returns 120
new Car(0).miles; // returns 100.. blaaargh!
So we use $chk()
:
var Car = function(miles){
this.miles = $chk(miles) ? miles : 100;
};
new Car(120).miles; // returns 120
new Car(0).miles; // returns 0.. w00t!
You should only use $chk
where you want to allow 0
but not other falsy values. Also, remember that the two functions discussed here return booleans, not the value itself (don’t worry, it’s a common mistake).
Dancing With Data
Finally, we have the functions that can be used for working with data.
The first one is the $each
function. Again, you might be wondering why this function even exists–we already have Array.forEach/each
and you can always do a Hash.each
for key-value pairs. But you’ll have to understand that $each()
is iterator extreme. It’s not just for arrays and objects–you can use it for any iterable object. Arrays, hashes, function arguments, collections–it works with them all:
// function arguments
(function(){
$each(arguments, function(value, index){
console.log(index + ': ' + value);
});
})('a', 'b', 'c', 'd', 'e');
// a dom collection..
var items = document.getElementsByTagName('div');
// items is not an Elements object, you can't do each..
items.each; // returns undefined
// but you can use $each
$each(items, function(div){
console.log(div.innerHTML);
});
// heck! let's throw in stylesheets!
var styles = document.styleSheets;
// nekkid is fun..
$each(styles, function(style){
style.disabled = true;
});
Actually, $each()
is just a small wrapper for both Array.each
and Hash.each
–but hey, it’s shorter!
The next function is called $splat()
which I nominate as the most weirdly named function in MooTools. This function takes any value and turns it into an array. If it’s already an array, it just returns the value. Simple right? Useless? Of course not! It’s very useful in cases to simplify your code by making sure that you always have an array. For instance, look at the following snippet:
var say = function(message){
if ($type(message) == 'array') console.log(message.join(' '));
else console.log(message);
};
say('mark'); // logs 'mark'
say(['mark', 'haz', 'baconz']) // logs 'mark haz baconz'
For our say
function above, we accept both a string value or an array of strings, which we then log. Because we accept two kinds of values, we need to do a type check on what kind of argument our function received and then do whatever processing we have to do. But wouldn’t it be easier if we didn’t have to do a type check? What if we just assume that it’s an array in the first place? Well, we can do that–by using $splat()
:
var say = function(message){
console.log($splat(message).join(' '));
};
say('mark'); // logs 'mark'
say(['mark', 'haz', 'baconz']) // logs 'mark haz baconz'
$splat()
makes sure that you’re always working with an array. If you have a function where you’d want to accept both a specific type or an array of that type, you can use $splat()
to make sure that it’s always an array. Then, you can get rid of the type checking and type specific code for non-array values. This is how the Implements
mutator (discussed here) actually works–it can accept both a class or an array of classes and it doesn’t care, because it splats them anyway.
A Moment for $
Sit down. This might be shocking news for you, and I don’t want to be the harbinger of bad news, but I have to tell you this: the $ utility functions are slated for removal from the next major version of MooTools Core.
Sad isn’t it? Well, not really. While it is true that the dollar utility functions will probably be gone by the next major version of MooTools-Core, the good news is that they aren’t really going to be eradicated–just moved.
In keeping up with the philosophy of keeping code cleanliness, it’s been decided to move the utility functions into their proper, corresponding objects for the next major version of MooTools. The decision has already been made to move the functions, but until the final code is released, please do take this with a grain of salt.
How is it moved, you ask? Well, take for instance $splat()
. In the current code-base for the next major release, this function has been moved into the generic Array.from()
, which is agreeably more idiomatic and expressive than the old code. $extend()
and $merge()
are now Object.append()
and Object.merge()
and $try()
is more menacing than ever as Function.stab()
. All other functions have been moved to their own objects, and new ones have been added as well.
So let’s take a moment to remember $
for all the great things it has brought us.
Utilitarians Unite!
So that’s it folks! Another episode of Up The Moo Herd done! Did you learn anything new? Or do you have something else to share? If you did and if you do, don’t hesitate to post a comment below and share you’re thoughts!
Don’t forget to subscribe to the RSS feed and follow me on Twitter (@keeto) for updates on the next part of the series, where we will be taking a deeper dive into the DOM.
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).