Up The Moo Herd VI Down and Out
It’s the start of Keetology Week! What does this mean? Another edition of Up the Moo Herd of course!
While this was originally meant to be an article about DOM tips and tricks, the recent slew of people who have commented on how “long” my articles got to me, so I’m turning it into a shorter post about Elements
and the tricks behind common methods. I was supposed to include sections on native events and “subclassing” the Element native, but I don’t want people getting turned off due to the length of the article. Maybe I should just write a book..
Speaking of books, have you gotten yourself a copy of MooTools 1.2 Beginner’s Guide? If not, then you better get yourself one! It’s written by Jacob Gube together with my good friend Garrick Cheung. It’s packed with a lot of useful stuff and I recommend it to everyone who wants to learn (or relearn) the basics of MooTools.
I’d also like to thank Matti Schneider-Ghibaudo, who not only reads my blog but has also helped in making Keetology better with his bug reports.
As always, the title of the post is taken from a song and this time it’s track 9 from the first studio album of the ever fantastic The Academy Is…
And with that, let’s get to know the elements of MooTools.
Element and Elements
Element
and Elements
are the most important MooTools natives you’ll work with when dealing with the DOM. Almost all of the DOM-based capabilities of the library are centered around these two natives, and it’s important to understand what they actually are in order to unlock the secrets of the library.
The Element.js
(the file that contains the Element
and Elements
implementations) is one of the more complex parts of MooTools. Unfortunately, I cannot go into much detail regarding the implementation, primarily because you could fill an entire chapter of a book just talking about Element
and secondly because a lot of people have been complaining about how long my articles are.. Boo!
The gist of it though is that the MooTools Element
native is a reimplementation of the DOM Level 1 Element
constructor. Like other natives such as Array
or String
, you can add new methods to element objects by using the implement()
method:
Element.implement({
switchClass: function(first, second){
var which = this.hasClass(second);
this.removeClass(which ? second : first);
this.addClass(which ? first : second);
return this;
}
});
var myDiv = new Element('div', { 'class': 'yes' });
myDiv.get('class'); // 'yes';
myDiv.switchClass('yes', 'no').get('class'); // 'no'
myDiv.switchClass('yes', 'no').get('class'); // 'yes'
Always keep in mind that the MooTools utility document.id()
or $
always returns either an instance of Element
or null–which means that it’s already extended. If you want something reusable, store it in a variable rather than do multiple calls to document.id
.
Elements
, on the other hand, is the Element Collection native. It’s similar to the DOM Level 2 HTMLCollection
native, but unlike Element
that directly reimplements DOM Element
(ie, MooTools’ Element.prototype
is the original DOM Element.prototype
), Elements
is a new native that’s MooTools only.
An Elements Collection is actually a modified array–unlike HTMLCollection, which is only “array-like.” Each member of an Elements collection is an Element
object, and any method that’s available to Element
is also available to Elements
:
Element.implement({
switchClass: function(first, second){
var which = this.hasClass(second);
this.removeClass(which ? second : first);
this.addClass(which ? first : second);
return this;
}
});
var myLinks = new Elements(document.querySelectorAll('a'));
myLinks.switchClass('yes', 'no');
The Elements
constructor requires an array-like object as its first argument. This means you can use native HTMLCollections, like document.forms
or document.styleSheets
or, like in our example above, results from native methods like document.querySelectorAll()
or document.getElementsByTagName()
. And because Elements uses document.id
internally, you could actually pass id-strings to create an ad-hoc collection:
new Elements(['wrapper', 'content', 'sidebar']);
// [div#wrapper, div#content, div#sidebar];
The MooTools function $$
always returns an Elements
object.
One important thing to remember is that the Elements
native is a modified-array, so when implementing new methods for it, this
will refer to the whole collection:
Elements.implement({
count: function(){
return this.length;
}
});
new Elements(['wrapper', 'content', 'sidebar']).count(); // 3
When implementing methods for Element
, MooTools automatically creates a version for Elements
that involves applying the method for each member of the element collection, which is why you can call any Element
method on an Elements
instance.
Now that the preliminaries are done, let’s dive deeper into some of the more interesting methods and techniques related to the DOM.
You Set, I Get
The Element
methods set
and get
are powerhouse methods for manipulating elements in the DOM. You can do everything from change element attributes to set styles and even add events using the set
method, and get
exposes ways to retrieve a bulk of information from an element.
The signature for these methods are set(key, value)
and get(key)
–where key
is a string, and value could be anything (as long as it works with the key). For set
, you can also pass an object that contains key-value pairs. The key is important because it controls how these methods operate.
When you call on set
or get
, it takes the key and checks a special object called Element.Properties
where all the custom accessors (ie, getters and setters) are stored. This object is a hash with keys corresponding to a property or a custom method and an object value, which could contain 3 members: set
, a function used to set the property; get
, a function used to retrieve the property; and erase
, a function used to remove the property.
When you call set
, get
or erase
, it takes the key and checks Element.Properties
to see whether a custom accessor with that key is present. If it is, it then checks whether it has a member function corresponding the method (eg, set
checks whether Element.Propeties.keyName.set()
exists) and invokes it if it exists (bound to the current element), passing the arguments given. If no custom accessor is found or if the custom accessor doesn’t have a function corresponding to the method, it defaults to the attribute:
var main = $('main');
// This works because we have the Element.Properties.text accessor
main.set('text', 'yay');
main.get('text'); // 'yay'
// If no accessor is found, it defaults to attributes
main.set('booyah', 'woot'); // div booyah="woot"
main.get('booyah'); // 'woot'
Because Element.Properties
is public, you can use it to create your own custom accessors for set
and get
. Here’s a very trivial one that creates a shortcut for display
:
Element.Properties.display = {
set: function(type){
return this.style['display'] = type || 'block';
},
get: function(){
return this.getComputedStyle('display');
},
erase: function(){
return this.style['display'] = 'none';
}
};
var main = $('main');
main.erase('display');
main.get('display'); // 'none'
main.set('display', 'block');
main.get('display'); // 'block'
Either Pseudo or Pseudon’t
The MooTools $$
function could take a CSS selector as a string argument, for example #main a
. The work of transforming that CSS selector and using it to get all a
elements inside the element with the id main
is handled by the selector engine, which not only parses the CSS selector but also walks the DOM to fetch the appropriate elements. The MooTools Selector Engine, implemented in Selector.js
, is another large beast that we can’t go into detail here. However, we’ll discuss one interesting and highly useful part of it: pseudo-selectors.
In CSS, a pseudo-class is a special dynamic “class” applied to an element at various states, like how links get the :hover
pseudo-class when you move your mouse over them. On the other hand, a MooTools pseudo-selector is a function that checks against the properties of an element at a particular time. For example, we have the :checked
pseudo selector, which returns whether an element (particularly radio-buttons and checkboxes) are checked and the snippet $$('.myClass:checked')
returns all elements with the class myClass
and are checked.
The :checked
pseudo-selector, like all MooTools pseudo-selectors, is stored in a hash: Selectors.Pseudo
. When the selector engine encounters what looks like a pseudo-selector key in the selector string, it checks the Selectors.Pseudo
hash to see if a corresponding pseudo-selector function exists, and then uses the function to filter out elements. If the element passes the function’s filtering, the function returns a truthy value and the element is kept in the result; if not, the element is removed from the result.
Since pseudo-selectors are functions, they can also received arguments which are passed in a pair of parenthesis after the selector-key. For example, the :contains()
pseudo-selector checks whether the element has text that’s the same as the passed argument. In the case of $$('p:contains(woot)')
, it’ll match all paragraph elements that contains the word “woot” inside.
Of course, Selectors.Pseudo
is public, which means you can use to roll-out your own pseudo-selectors. A pseudo-selector function has only one requirement: it must return a truthy value when an element matches and a falsy value if not.
/*
:with-event pseudo-selector
checks whether the element has an event
*/
Selectors.Pseudo['with-event'] = function(type){
var events = this.retrieve('events');
if (events && !type) return true;
else return !!(events[type] && events[type].keys.length !== 0);
};
$$(':with-events'); // all elements with events
$$(':with-events(click)'); // all elements with click events
$$('a:with-events(click)'); // all anchors with click events
// use the :not pseudo-selector to negate
$$('a:not(:with-events(click))'); // all anchors without click events
D to the A to the T to the A
The pseudo-selector example above uses another Element
method, retrieve
. This method is another part of a pair of accessors, along with store
, called the storage accessors. The term “storage” here refers to the Element Storage, a special object where you can store and retrieve data for elements.
This Element Storage is an object kept outside the DOM and lives in the JavaScript space. There’s no way to access the storage but through an element, and an element can only access part of the storage that it owns. And because the storage is an object, you can store anything javascript value you want inside it–strings, arrays and even references to other objects.
var main = $('main');
main.store('elements', {button: new Element('button') });
var elements = main.retrieve('elements');
elements.button.inject(document.body);
The Element Storage is useful because it allows you to store values that can be used in any part of your code that has access to an element. This can eliminate the use of global variables: just store your data in your elements and access them later.
MooTools uses store in a lot of places, for instance in element events (as seen in the example above). The core Fx classes also use this: the main Fx.Tween
and Fx.Morph
instances for an element are actually stored via storage–with special Element.Properties accessors.
Another way to store data, particularly strings, is through HTML5 data-attributes. This new feature allows you to embed data into your html markup by creating special attributes prefixed by data-
, like <div data-name=”mark” />
. This is a very nifty feature, especially if you want to pass data from the server-side to the client-side.
While the Element.dataset
property is not yet implemented in most browsers, you can already use data-attributes now in your markup, since the native setAttribute
and getAttribute
methods treat data-attributes like normal attributes. This also means that you can use the MooTools getProperty
and setProperty
methods, as well as the shorter get
and set
methods (since they default to the former pair):
var main = $('main');
main.set('data-name', 'Main Div');
main.get('data-name'); // "Main Div"
We can also create shortcuts so that we don’t have to prefix all our data-attributes with data-
all the time. Here’s an implementation for Element.data
shortcuts:
Element.implement({
setData: function(key, value){
if (value == undefined) this.removeAttribute('data-' + key);
else this.setAttribute(key, value);
return this;
},
getData: function(key){
return this.getAttribute('data-' + key) || null;
},
removeData: function(key){
this.removeAttribute('data-' + key);
return this;
}
});
var main = $('main');
main.setData('name', 'Main Div');
main.getData('name'); // "Main Div"
main.removeData('name');
main.getData('name'); // null
And We’re Done!
And that concludes this edition of Up the Moo Herd. I hope you learned a thing or two with this post, and I’m urging all of you to post your own tips and tricks, comments or question in the reaction section above.
The next part of the series will deal with the built-in Classes and the tricks involving them, as well as some other tricks that I’ve forgotten to mention on the section on classes.
Keetology has an RSS Feed for those of you who want to keep updated and I also have a twitter account for those who like to know what I had for lunch…
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).