30 days of MooTools

Troy at consideropen.com has started a 30 part tutorial about MooTools 1.2. They want to go from easy to more difficult parts of the library. The first post is a rough overview of the library. Could be interesting what they'll write about in further post. Definitely worth to pay attention to if they find enough topics for the 30 posts.

Using Ads for Geolocation

Yuffster had a nice idea to read ad content without actually displaying it to fetch the location of the user using a nifty trick: override document.write. Read the full article in her new blog.

Custom Pseudo Selectors

I've wrote about how to extend the class creation (Binds and Exposes mutator) and implement a custom Event (outerClick). In this post I'll show you the creation of custom CSS pseudo selectors to use in any MooTools CSS based selector (for example with $$ or getElement). Note, that these Selectors are fully cross browser. (Sure thing you can't use them inside your CSS files.)

MooTools already includes most of the CSS3 pseudo selectors (:nth-child, :contains, and :not to name a few examples).

To add your own pseudo selector you have to add it as a function to Selectors.Pseudo. Two arguments will be passed to the function. First argument is the parameter given to pseudo selector in the form :pseudo(parameter) or undefined if none given. The second argument is an object in which you can store temporary data for following elements visited during the traversing of one selector. This is used for example in the :nth-child pseudo selector. The actual element being filtered is available as the scope of that function (this). The function should return true when the element is matched by the pseudo or false otherwise.

To show you the usage I'll create a simple pseudo selector, :random, with probably little practical use. It will have an optional parameter for the probability to select the element with a default value of 0.5 (50%).

  1. Selectors.Pseudo.random = function(probability, local){
  2. return Math.random() < (probability || .5).toFloat();
  3. };
  4.  
  5. // selects half of the divs on average
  6. $$('div:random');
  7. // selects 20% of the h2 elements with class article on average
  8. $$('h2.article:random(.2)');

Note, that the element bound to the filter is not extended for performance reasons. That means if you want to use a Element method inside the filter, say getStyle, this.getStyle(style) doesn't work. You'd have to extend the this with $. For example: $(this).getStyle(style).
But this would cause each element inspected to be extended. To increase the speed of the pseudo selector generics can be used on the unextended Element: Element.getStyle(this, style).

Exposes Class Mutator

Valerio came up quite a time ago with another class mutator called Exposes. (I already wrote a post about the Binds mutator a few posts ago). I rewrote the mutator a way I think the usage is cleaner.

The Exposes mutator is about getters and setters, which means its incompatible with IE and reduces it's practical use to internal websites, experimental use, or AIR apps. The mutator converts methods named like getFoo to getters and setFoo to setters. That is myObj.foo = "bar" calls the setter and alert(myObj.foo); the getter. Note that this means you can't store the property internally under the same name, as setting the property with this.foo = newFoo; in the setter calls the setter again, which results in an endless recursion. The same is true for getters. (MDC about getters and setters)

The mutator:

  1. Class.Mutators.Exposes = function(self, exposes){
  2. if (!exposes) return;
  3. for (method in self) {
  4. var match = method.match(/^(get|set)([A-Z])(\w*)$/);
  5. if (match) {
  6. var prop = match[2].toLowerCase() + match[3];
  7. self[(match[1] == 'get') ? '__defineGetter__' :
  8. '__defineSetter__'](prop, self[method]);
  9. }
  10. }
  11. };

The mutator iterates over all attributes and if one begins with set or get followed by an uppercase letter it sets it as an attribute setter or getter respectively using __defineGetter__ and __defineSetter__.

A slightly more complex Exposes mutator which accepts a list of attributes to convert is available here.

To see the usage I'll make an example. Say we have a Person with a first, last and full name. The full name being a combination of the last and first name.

  1. var Person = new Class({
  2. Exposes: true,
  3.  
  4. initialize: function(first, last){
  5. if (!last) {
  6. // last name not given, first argument is the full name
  7. // using the setter to set the full name
  8. this.name = first;
  9. } else {
  10. this.first = first;
  11. this.last = last;
  12. }
  13. },
  14.  
  15. getName: function(){
  16. return this.first + " " + this.last;
  17. },
  18.  
  19. setName: function(name){
  20. name = name.split(' ');
  21. this.first = name[0];
  22. this.last = name[1];
  23. },
  24.  
  25. getInitials: function(){
  26. return this.first[0] + ". " + this.last[0] + ".";
  27. }
  28. });

The class should be pretty self explanatory. We can't just update the initials without changing the name, hence only a getter for initials.

With the Exposes mutator there is no need now to call the setter and getters by hand and we can just do things like:

  1. var me = new Person("Jahn Kassens");
  2. // oops, someone misspelled my first name, let's fix that
  3. me.first = "Jan";
  4. alert(me.name); // Jan Kassens
  5. alert(me.initials); // J. K.
  6.  
  7. var test = new Person("Max", "Mustermann");
  8. alert(test.name); // Max Mustermann
  9. test.name = "John Doe";
  10. alert(test.last) // Doe

toElement Method [update]

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.

  1. var User = new Class({
  2.  
  3. initialize: function(name){
  4. this.name = name;
  5. },
  6.  
  7. toElement: function(){
  8. if (!this.element) {
  9. this.element = new Element('div', {'class': 'user'});
  10. var avatar = new Element('img', {
  11. src: '/avatars/' + this.name + '.jpg'
  12. });
  13. var description = new Element('div', {
  14. 'class': 'username',
  15. 'text': this.name
  16. });
  17. this.element.adopt(avatar, description);
  18. }
  19. return this.element;
  20. }
  21.  
  22. });
  23.  
  24. window.addEvent('domready', function(){
  25. var alice = new User('Alice');
  26. var bob = new User('Bob');
  27.  
  28. $(alice).inject('header', 'after');
  29. // let's inject bob after alice
  30. $(bob).inject(alice, 'after');
  31. });

I think this code is pretty self-explanatory. Only one thing is probably worth mentioning: 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.

Update
JMG 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:

  1. var User = new Class({
  2. // code from above
  3.  
  4. setName: function(name){
  5. // store it to the class
  6. this.name = name;
  7.  
  8. // we're done when the element is not yet created.
  9. if (!this.element) return;
  10.  
  11. // update the element:
  12. this.element.getElement('div').set('text', name);
  13. this.element.getElement('img')
  14. .set('src', '/avatars/' + name + '.jpg');
  15. }
  16.  
  17. });
  18.  
  19. 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.

Page:  1 2

About

I'm a MooTools core developer and this is the place where I publish some snippets and other mootools related stuff. I study computer science in Darmstadt, Germany.

Idea for a Topic?

If you've got an idea for a topic to write about, let me know!

Other Blogs

User