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.

Binds Class Mutator

When writing a Class you often want to bind a class method to an event and access this as the instance of the class. You could say this isn't a big deal using .bind(). That's right until it comes to removing the event again, since Function::bind works in that way that it returns a new function wrapping the old one. Hence you need to store that very function when you want to remove it again (without removing all events from that element, which isn't good inside a portable class).

What I've seen a couple of times is an object holding all bound functions and created in the constructor, example:

  1. var MyClass = new Class({
  2.  
  3. initialize: function(){
  4. this.bound = {
  5. sayHello: this.sayHello.bind(this),
  6. sayGoodbye: this.sayGoodbye.bind(this)
  7. }
  8. // now only using this.bound.sayHello and
  9. // this.bound.sayGoodbye inside the class
  10. },
  11.  
  12. sayHello: function() { /* ... */ },
  13.  
  14. sayGoodbye: function() { /* ... */ }
  15.  
  16. });

I wasn't quite happy with this solution, because it's too verbose: why always use this.bound.myFn when I always want the bound one? Secondly, I don't want to do the binding of all these functions by hand. After some discussions on different solutions this is what I come up with as the optimum between speed (don't worry its faster than the solution above) and usability.

A new so called "class mutator" named Binds. Most of you probably didn't hear of class mutators before, but you sure have used a class mutator before when you've written one or another class. Built in mutators are Implements and Extends and here is the code of the Binds mutator:

  1. Class.Mutators.Binds = function(self, methods) {
  2.  
  3. $splat(methods).each(function(method){
  4. var fn = self[method];
  5. self[method] = function(){
  6. return fn.apply(self, arguments);
  7. };
  8. });
  9.  
  10. };

What it does is overriding all methods passed to it as a string with a version bound to the instance. Binds: 'foo' would just bind foo to the class, Binds: ['foo', 'bar'] binds foo and bar. Pretty simple, huh?

Now let's see how the class can be simplified using the Binds mutator:

  1. var MyClass = new Class({
  2.  
  3. Binds: ['sayHello', 'sayGoodbye'],
  4.  
  5. initialize: function(){
  6. // now only using this.sayHello and
  7. // this.sayGoodbye inside the class
  8. },
  9.  
  10. sayHello: function() { /* ... */ },
  11.  
  12. sayGoodbye: function() { /* ... */ }
  13.  
  14. });

I hope you learned a bit again or can find use of it.

If you have any wishes or ideas for further articles let me know.

Page:  1

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