2009-07-02

In this first part of the “Up the Moo Herd: MooTools Tips and Tricks“ series, we’re going to talk about the fetcher functions and methods. These are the most used functions and methods in MooTools, and mastering them is essential for controlling the DOM.

Again, let me remind you that this is not an introductory tutorial. I won’t go over the basics for this series, so make sure you know the basics so you won’t get lost. There’s a list of resources available for beginners from the earlier blog post, so be sure to check them out.

And with that, we start going up the herd.

The Dollar Function

Most people think that doing this in native Javascript:

var myElement = document . getElementById ( 'myElement' ); var link = myElement . get ( 'href' );

is just the same as doing this in MooTools:

var myElement = $ ( 'myElement' ); var link = myElement . get ( 'href' );

Zing! Try the first and you’ll probably get an error (something like myElement.get is not a function). That’s because aside from fetching the particular element from the DOM tree, the dollar function also extends the element, which means it adds additional useful methods to the Element–all of which are documented in here.

The dollar function also allows you to pass a native HTMLElement object instead of the id string. If you do this, MooTools will no longer search for the element in the DOM, but it will still extend your object so you can use all the nifty methods.

// this will return an Option object (a native HTMLElement object). var myOption = new Option (); // returns undefined because native HTMLElements don't have a get method. console . log ( typeof ( myOption . get )); // Pass the original myOption object to $() to extend it. myOption = $ ( myOption ); // outputs 'function'--hooray extension! console . log ( typeof ( myOption . get ));

If the dollar function is passed an element that’s already extended, it simply returns the element. This is very useful when it comes to creating custom classes where you allow users to pass either a string id or an actual element object.

var Item = new Class ({ initialize : function ( element ){ this . element = $ ( element ); } }); // Now you can do: var myItem = new Item ( 'myItemElement' ); // or.. var myItemElement = $ ( 'myItemElement' ); var myItem = new Item ( myItemElement );

Another thing to remember is that the dollar function is a do or die function: if it finds an element, it will return the element; if it doesn’t, it returns null . This is the most common culprit for the element.XXX is not a function error because you’re trying to call a method to something that’s actually null. So be sure you’re passing the correct id or the correct object to the dollar function before calling any Element method.

Safe-Dollar Since version 1.2.3, a “safe-dollar” mode was implemented that allowed interoperability between MooTools and other non-prototype extending libraries. The full details of this new feature is documented here, but the gist is this: the old dollar function, $() , has been renamed to document.id() . During loading, MooTools checks if there’s already a $() function available: if it doesn’t find one, it declares that the $() function be used as an alternative syntax to document.id() . But if it does find a separate $() function, it doesn’t overwrite it. What does this mean to you? Well, it just means that there might be some cases where your scripts might not work because you’re using the dollar function of another library. Ouch! But to make life easier, here are some simple guidelines: If you’re sure that you won’t be using any other library aside from MooTools, then it’s safe to use $() . If you’re not sure whether there will be other libraries loaded and you want to be safe, use document.id() (or use a closure; details for that are found in the link above). If you’re writing a script that you’ll be releasing publicly and you don’t know whether the users will use other libraries aside from MooTools, use document.id() (This should be a standard for all MooTools plugins, by the way). If you want to force the $() function to be equivalent to document.id() no matter what libraries may be loaded, put $ = document.id; at the start of all your scripts.

The Double Dollar

If the dollar function returns a single element, the double-dollar function, or $$() , returns as many elements as it can. Colloquially, we say that the double-dollar function returns an “array of elements”, but the proper term is that it returns an Element Collection.

What’s the difference between an array of elements and an Element Collection?

var arrayOfElements = [ $ ( 'elA' ), $ ( 'elB' ), $ ( 'elC' )]; // array of elements var elementCollection = $$ ( '#elA, #elB, #elC' ); // element collection console . log ( typeof ( arrayOfElements . addEvent )); // undefined console . log ( typeof ( elementCollection . addEvent )); // function

The difference is that an Element Collection, aside from having the normal array methods, also has all the Element methods. So anything you can do to an element fetched via $() , you can do to all the elements inside an Element Collection fetched by $$() .

This little feature is overlooked by many, so they tend to write code like this:

$$ ( '.button' ). each ( function ( button ){ button . addEvent ( 'click' , function (){ console . log ( 'Hey there!' ); }); });

when they could just have done it like so:

$$ ( '.button' ). addEvent ( 'click' , function (){ console . log ( 'Hey there!' ); });

Because an Element Collection returned by $$() already has all the Element methods, there’s no need to iterate over all items in the collection and apply an Element method, because that’s already available for the collection as a whole.

With Element Collections, methods that return something will return an array of somethings:

$ ( 'myInput' ). get ( 'name' ); // returns 'firstname', but.. $$ ( 'input' ). get ( 'name' ); // returns ['firstname', 'email', 'comment'];

The double-dollar method always returns an Element Collection–even if there were no elements found. This is a common error for many, which makes them scratch their heads when something like this happens:

var myItems = $$ ( 'xxx' ); // returns [] if ( myItems ){ // will always be true because an empty // Element Collection, like an empty array, is a truthy value. }

What you want to do is check the length member of a collection to check whether it really has elements:

var myItems = $$ ( 'xxx' ); // returns [] if ( myItems . length > 0 ){ // Problem solved. }

Another source of confusion is what actually do you pass as an argument to the double-dollar function? The short answer is a CSS Selector. While this is clear enough for most people, some fail to see that CSS Selectors aren’t limited to ids, classes and tags.

For example, take a look at this code:

$$ ( '.items' ). each ( function ( item ){ if ( item . get ( 'name' ) == 'orange' ) { if ( item . get ( 'checked' ) == 'checked' ) { item . highlight (); } else { item . destroy (); } } });

Sure, that will work, but it doesn’t use the full power of selectors. Taking that code and simplifying it, you may end up with something like this:

$$ ( '.items[name="orange"][checked="checked"]' ). highlight (); $$ ( '.items[name="orange"][checked!="checked"]' ). destroy ();

The double-dollar function makes it easier to fetch items from the DOM by allowing you to use complex selectors. Which is why it pays to know your CSS selectors! There are many good resources for learning about CSS Selectors (let me google that for you), and the MooTools-Core documentation for Selectors also lists some other pointers regarding them.

By the way, the code above could further be optimized for performance:

var items = $$ ( '.items' ); items . filter ( '[name="orange"][checked="checked"]' ). highlight (); items . filter ( '[name="orange"][checked!="checked"]' ). destroy ();

Safe Double-Dollar? With regards to the new safe-dollar mode, you don’t have to worry about $() getting overwritten, because MooTools still retains this function, even if safe-dollar mode is turned on.

Segue: querySelector and querySelectorAll

Sidenote: now that the querySelector and querySelectorAll methods are available in the latest versions of all the major browsers (albeit implemented differently in some of them), you might be wondering how to deal with them. With the old getElementById , you could simply do something like this to extend an element:

var myElement = document . getElementById ( 'myElement' ); var myExtendedElement = $ ( myElement );

But how about these new methods? The good news is that you can do something similar with them:

var myElement = document . querySelector ( '#myElement' ); var myExtendedElement = $ ( myElement ); var myCollection = document . querySelectorAll ( 'a.withClass' ); var myExtendedCollection = $$ ( myCollection );

So if you ever find yourself in a situation where you want to use the Element methods for items fetched via these new methods, remember that you could simply extend them using the good-ole dollar and double-dollar functions.

Other Fetchers

Finally we have to talk about the other fetcher methods available: getElement and getElements . These methods are available to any Element object and are useful for fetching elements within those elements using CSS selectors.

Sounds confusing? Don’t fret. They’re really simple methods to filter out the contents of your elements:

// fetches a div with the id main. var myElement = $ ( 'main' ); // fetches the first anchor inside #main with a .yes class var linkWithinMain = $ ( 'main' ). getElement ( 'a.yes' ); // fetches all anchors inside #main. var allLinksWithinMain = $ ( 'main' ). getElements ( 'a' );

Why Would I Want to Do That? People might suggest that instead of doing that, just use a complex selector with the double-dollar function. Yes, you could always do this instead of the code above: var firstLink = $ ( '#main a.yes' )[ 0 ]; var allLinks = $ ( '#main a' ); But for performance reasons, it is advised that you fetch the parent element first via $() and then fetch the child elements via getElement or getElements . For simple things, this might go unnoticed, but if speed is a concern, do use the $(…).getElements(…) style.

And lastly, there’s a filter method available to an Element Collection, which was used in an example above. It also accepts a CSS selector for its argument and it returns a new collection containing all items from the original collection that matches that selector.

// Fetch all links on the page.. var allLinks = $$ ( 'a' ); // Filter the collection: return only links with an .outgoing class.. var linksClassedOutgoing = allLinks . filter ( '.outgoing' );

Go Fetch

So that’s the end of the first installment of the series. If you have any other tips and tricks you’d like to share, do post them on the comments below.

And be sure to subscribe to the RSS feed and follow me on Twitter (@keeto) for updates on the next part of the series.