Perfection kills

Exploring Javascript by example

Archives Posts

Refactoring with Prototype

October 5th, 2007 by kangax

I’m not surprised to see prototype code that sucks. Been there, done that (and I’m sure still doing it) – nobody is perfect. What surprises me though is how ridiculous some explanations (of writing crappy code) sound. “I don’t really know javascript. I do PHP professionally” is my favorite. Don’t get me wrong, I understand that experience and deep understanding of language is essential for writing smarter code. It could be a real challenge to wrap your mind around some of the dynamic concepts of such languages as javascript. That I can understand. What I don’t understand, though, is how people don’t follow some of the fundamental principles of programming. Do we really need to know javascript to prevent repetition, unnecessary constructions or poor optimization? I seriously doubt it…

I want to take a look at few examples of real life applications and see what we can do to make them… better. Aiming for a smaller footprint, faster execution and prettier code is what makes it a real challenge, so let’s dive in…

Repetition

function hideall() {
    hidediv('dropdown1');
    hidediv('dropdown2');
    hidediv('dropdown3');
    hidediv('dropdown4');
    hidediv('dropdown5');
    hidediv('dropdown6');
    hidediv('dropdown7');
}

Very basic, ugly (and unfortunately real) example. The only part that varies here are numbers so all we need to do is loop through them. Prototype provides us with a quite convenient utility function for generating ranges – $R takes 2 parameters – starting and ending values and creates a range object by calling succ method on each value. What’s really cool about this object is that it extends prototype’s Enumerable methods – iteration has never been easier:

$R(1,7).each(function(n) {
    hidediv('dropdown' + n);
})

Not only we decreased the overall size of this snippet, but notice how more generic and flexible it has become.

Repetition 2

function resetnotes() {
    $('left1').removeClassName('learn');
    $('left1').removeClassName('play');
    $('left1').removeClassName('grow');
    $('left1').removeClassName('join');
}

This one is similar but is even worse, as we’re calling $ function multiple times, decreasing overall performance. What varies here are plain strings and that’s when $w helper could come in handy. Inspired by Ruby, $w takes a string of space-separated values and creates an array out of it. Remember that arrays are automatically extended with Enumerable methods:

// storing element's reference for faster access
var left1 = $('left1');
// creating array of strings and iterating over them
$w('learn play grow join').each(function(s){
    left1.removeClassName(s);
})

Another solution would be to wrap (augment) removeClassName to accept multiple arguments (classNames).
I don’t suggest to use the following snippet in a production environment as I haven’t tested it thoroughly. The idea is to show you basic way of augmenting prototype’s existent methods.

Element.addMethods({
    removeClassName: Element.Methods.removeClassName.wrap(function() {
        var args = $A(arguments), proceed = args.shift(), element = $(args.shift());
        if (args.length > 1) {
            // this is our modified behavior (only if more than 1 argument was passed)
            element.className = element.className.replace(new RegExp("\s*(" + args.join('|') + ")\s*"), '');
            return element;
        }
        // and this is a default one
        return element.proceed();
    })
})

Attention: Try not to fall into a trap of “generic-madness”. Yes, it’s beautiful. Yes, it encapsulates certain logic into its own layer and creates reusable chunks of code, but no matter how great it is, think twice before taking this approach. Does it pay to spend time creating another abstraction layer? Will it actually be used in the future? If it doesn’t simplify, just drop it. Keep it simple and move on.

Using Element.addMethods we redefine prototype’s removeClassName to suit our needs. Notice how unobtrusive yet powerful this solution is. By wrapping already existent method with or own behaviour, we keep same logic in same place, and making sure augmented method does not break anything (Take a look at a previous post about Function#wrap if you’re not familiar with it)

Ternary operators

if (typeof this.prevbutton == 'object') {
	$(prevmonth).appendChild(this.prevbutton);
}
else {
	$(prevmonth).update(this.prevbutton);
}

Using “if … else … ” for only two conditions is usually a bad thing to do. Selecting one of two actions is even worse.
Ternary operator, on the other hand, is a perfect replacement for “if … else … ” clause. Remember, that we can use bracket notation to call one of two object’s methods:

$(prevmonth)[typeof(this.prevbutton) == 'object' ? 'appendChild' : 'update'](this.prevbutton);

Chaining

$(nextmonth).addClassName('calcontrol');
$(nextmonth).addClassName('calnextmonth');
$(nextmonth).observe('click',this.nextmonth);

Taking advantage of chaining is easy – most of the methods in prototype return reference to the element they were invoked on. Another thing to note is that addClassName is simply appending it’s argument to the className of an element. There’s really no need invoking it more than once:

$(nextmonth).addClassName('calcontrol calnextmonth').observe('click', this.nextmonth);

Those are very simple yet essential tips for writing more concise, elegant code and I would really want to see more people using them.
Enjoy your ride and happy prototyping…

Archives Posts

Extending the limits

August 28th, 2007 by kangax

One of the beauties of prototype is in the way it allows us to extend its functionality. Building custom snippets of code has never been easier. The philosophy behind it is to keep core-level things at a minimum, but provide a convenient way to extend them.
The magic method is Element.addMethods

The syntax is quite simple:

Element.addMethods({
  method1: function() { ... },
  method2: function() { ... },
  ...
})

When defining a custom method, make sure that:

  • First passed argument is an element
    Element.addMethods({
      myAwesomeMethod: function(element, ... ) {
        element = $(element);
        ...
      }
    })
  • That same element is returned from the method (and is therefore chain-friendly)
    Element.addMethods({
      anotherAwesomeMethod: function(element, ... ) {
        element = $(element);
        ...
        return element;
      }
    })

We can also skip “var” when assigning element reference (since it’s passed as a first argument).

As an example, here’s a little helper, that I use quite often to display notifications (usually when form verification fails). We will update element with content, make it appear, wait couple of seconds and fade it out.

Note: The following example uses prototype version 1.6.0_rc0 and requires Scriptaculous’ effects module:

Element.addMethods({
  flash: function(element, content) {
    element = $(element);
    new Effect.Appear(element, {
      beforeStart: function() {
        element.update(content);
      },
      afterFinish: function() {
        Effect.Appear(element, {to: 0, delay: 3,
          afterFinish: function(){
            element.hide().setOpacity(1);
          }})
      }
    })
    return element;
  }
})

Now we can simply do:

$('errorBox').flash('login field should not be empty');

Try it out! Pretty convenient, huh?

Just to get you started, here are few more examples that might be convenient in every day use:

1) Form#populateFrom

Ever wanted to populate form via ajax? All it takes is a few lines of “magic”:

  • Description: Fills form with data via json (requires: v1.6.0_rc0+, ‘Content-type: application/json’ header)
  • Usage: $(‘myForm’).populateFrom(‘blah.php’);
  • Invoked on: Form
  populateFrom: function(element, url) {
    element = $(element);
    new Ajax.Request(url, {
      onSuccess: function(response) {
        var data = response.responseJSON;
        element.getElements().each(function(el) {
          el.setValue(data[el.readAttribute('name')])
        })
      }
    })
    return element;
  }

If your server-side script returns something like:

{
  "firstName": "Fluffy",
  "lastName": "Horse",
  "email": "far@far.away"
}

then invoking this method

$('myPrecious').populateFrom('myScript.php')

on a form with the same structure

<form action="foo.bar" id="myPrecious">
<input name="firstName" type="text" />
<input name="lastName" type="text" />
<input name="email" type="text" />
</form>

will populate JSON data into a form

2) Element#__extend

This one is a real gem and I find myself using it all the time. The idea is to be able to extend element with arbitrary number of methods/properties in a chain friendly manner.

  • Description: Extends element with a hash of properties
  • Usage: $$('input#firstName')[0].__extend({initialValue: 'John'}); $$('form').invoke('__extend', {counter: 0})
  • Invoked on: Any
__extend: function(element, hash) {
  return Object.extend($(element), hash);
}

As you can see this one is just a simple one-liner so we can easily skip explicit element assignment.
As an example of a real-life case, here’s how I used it in a Proto.Menu class:

new Element('a', {
      href: '#',
      title: item.name,
      className: item.className || ''})
  .observe('click', this.onClick.bind(this))
  .update(item.name)
  .__extend({
    _callback: item.callback,
    _disabled: item.disabled ? true : ''
  })
)

As you can see, it’s really convenient to store such things as callbacks, identifiers and boolean values as custom element properties. It’s also worth mentioning that Prototype 1.6+ extends observed elements with _eventID (preventing duplicate observers) in a similar manner.

Element#setUniqueClassName

Another common use case is when we need to assign a class to an element, removing it from its siblings at the same time. Most trivial example is setting “selected” or “active” class on navigation links

  • Description: Sets className, removing className from all siblings
  • Usage:$$('#nav li a')[0].setUniqueClassName('selected')
  • Invoked on: Any
setUniqueClassName: function(element, className) {
  var element = $(element),
  if (!element.hasClassName(className)) {
    collection = element.next() || element.previous() ? element.siblings() :
      $A(element.up(1).getElementsByTagName(element.tagName));
    collection.invoke('removeClassName', className);
    element.addClassName(className);
  }
  return element;
}

This method only starts iteration if current element does not have a specified className (in case already selected link was clicked). It’s also smart enough to iterate over child nodes of parent node siblings (since semantically-correct markup should contain links inside of list items). In this case, it will try to collect all <a> elements first and then iterate over them.

I hope you enjoyed this tutorial and are inspired by all the possibilities of prototyping : )