Another new feature of MooTools 1.2 is the toElement method you can add to any of your classes (or objects). This method is inspired by the Prototype framework which added it recently. I considered it very useful in streamlining classes so I adopted it and added it to the core.
Note: its not yet documented but its already in the 1.2.0 version.
toElement is supposed to return an Element which represents the object in some way. Consider a User class for example. Wouldn't it be cool, if you can inject a user in the page like userList.grab(joe); or $(joe).inject(userList);?
All we need to get this functionality is to add a toElement to our user class. Whenever an object is passed to $ it will try to call its toElement method to get an element representation.
Now enough of this theory and lets have a look at an example.
I think this code is pretty self-explanatory. Only one thing is probably worth mentioning:
var User = new Class({ initialize: function(name){ this.name = name; }, toElement: function(){ if (!this.element) { this.element = new Element('div', {'class': 'user'}); var avatar = new Element('img', { src: '/avatars/' + this.name + '.jpg' }); var description = new Element('div', { 'class': 'username', 'text': this.name }); this.element.adopt(avatar, description); } return this.element; } }); window.addEvent('domready', function(){ var alice = new User('Alice'); var bob = new User('Bob'); $(alice).inject('header', 'after'); // let's inject bob after alice $(bob).inject(alice, 'after'); });
toElement doesn't create a new element each time it's called, but stores the element in this.element and only creates it if it's not already created.UpdateJMG pointed out that the element wont update when the user instance is changed. Say we want to be able to change the name of the user. We can do this by adding a method to the User class:
var User = new Class({ // code from above setName: function(name){ // store it to the class this.name = name; // we're done when the element is not yet created. if (!this.element) return; // update the element: this.element.getElement('div').set('text', name); this.element.getElement('img') .set('src', '/avatars/' + name + '.jpg'); } }); bob.setName('bobby');
What the average developer eye should see is the duplication for setting the element values. We could create a method to update the element values or store the description and image elements to the element. (Since we probably have more attributes than just a name). I've left that out for the sake of simplicity.
Kind of limited in the use (the object cannot change once the element has been created), but it gives some ideas. Interesting.
JMG I've updated the the post to address that.
Thanks for the update. I'm starting to understand what can be achieved with this concept -- for instance keeping the object and the DOM representation synchronized. Add a bit of AJAX to update the object's data, and you can start building some rich applications.
Very neat ideas! The toElement mechanism is hugely useful -- I'm already building classes that correspond closely to collective on-screen objects anyway, so formalizing this link is clearly the right thing to do. Glad to hear that it's already in 1.2: I've made a note to myself to start switching over to it -- it should help clean up the code nicely.
Great blog -- it's now added to my daily reading list...
Grrr, why isn't that documented? That one piece of information is extremely useful to a lot of people! :-) Thanks for bring it into the light.
In the spirit of the toElement method, I also like to use my own class mutator (Uses) that allows you to call some common element methods on the object itself without a $ function.
Class.Mutators.Uses = function(self, methods) {
$splat(methods).each(function(method) {
self[method] = function() {
this.element[method].run(arguments, this.element);
return this;
};
});
};
var El = new Class({
Uses : ['inject', 'adopt', 'dispose', 'wraps', 'grab', 'toQueryString'],
toElement : function() { return this.element; }
});
var Form = new Class({
Implements : [Options, Events, El],
Uses : ['reset', 'disable', 'enable'],
...
hmm, i wasted a lot of time trying to Extend the Element "Class" (i've read that it's not a real class).
PLZ, document it... There it should say what classes are extendable and implementable (i don't know if every class can be implemented).
NICE post, Thanks.
Hello.
I was looking for something like that for a while. So thanks a lot.
But I would like your opinion for that :
What's the best practice to have the methodes of your class accessible from the element.
For exemple $("idOfMyElement").setName("John")
And if I whant to implement event where must I write it ?
The objective is to create a Class witch create complex Dom Element with method, event etc...
Before to read you I was using $extend on my freash created Element to add my method. But now I don't know what to think.
(If you don't inderstand me, sorry. My english is not very good and I begin with Mootools...)
Here's how I use toElement - which solves some of the questions posted before me...
var Select = new Class({
initialize: function(){
/* Intelligently decipher what was passed in when the object was instanced. Select can be either a string or a dom element. */
var parameters = Array.link(arguments, {properties: Object.type, select: $defined});
var properties = parameters.properties || {};
var select = $(parameters.select) || false;
/* Either use the passed in ID, the selects dom id, or randomly create an id */
properties.id = $pick(properties.id, select.id, 'Select_' + $time());
/* Extend a new Element with this class */
this.select = $extend(new Element(select || 'select', properties), this);
},
add: function(opts){
$splat(opts).each(function(opt){
var opt = new Element('option', opt).inject(this);
},this);
},
toElement: function(){
return this.select;
}
});
window.addEvent('domready', function(){
$(new Select({id: 'my_select', name: 'my_select'})).add({value: 'five', text: 'FIVE'}).inject($('test'));
/* Create a brand new select from scratch */
var mySelect = new Select({id: 'my_select', name: 'my_select'});
/* Inject it into the DOM - note that mySelect has all of the custom mootools element methods like inject... */
$(mySelect).inject($('test'));
/* mySelect also has the custom methods from the select class. These methods work no matter how the element is accessed */
mySelect.add([{value: 'one', text: 'ONE'}, {value: 'two', text: 'TWO'}]);
$('my_select').add([{value: 'three', text: 'THREE'}, {value: 'four', text: 'FOUR'}]);
$(mySelect).add([{value: 'five', text: 'FIVE'}, {value: 'six', text: 'SIX'}]);
document.getElementById('my_select').add([{value: 'seven', text: 'SEVEN'}, {value: 'eight', text: 'EIGHT'}]);
/* You can also grab a select element that already exists on the page */
var mySelect = new Select('my_select_2');
/* And then use the custom select methods to modify it... */
mySelect.add({value: 'b', text: 'B'});
});
ignore the first line in the window domready function, I was just experimenting and forgot to remove it before copying & pasting...