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

Wrap it up!

September 14th, 2007 by kangax

Today I want to talk about new function method, introduced in latest Prototype (1.6). Function#wrap is simply amazing when it comes to flexibility and elegance of code. You will never go back once started using it. Essentially, Function#wrap allows us to augment default function behavior by “wrapping” it with our own logic. Not only we could “wrap” it, but pretty much do anything we want – change in any way we need.

Here’s what 1.6rc0 release notes say about it:

Function#wrap distills the essence of aspect-oriented programming into a single method, letting you easily build on existing functions by specifying before and after behavior, transforming the return value, or even preventing the original function from being called…
Don’t worry, it’s not that complicated and makes perfect sense when explained, so enough theory, let’s see what makes Function#wrap so good in pactice.

Stripping tags

stripTags is a quite useful String method (defined through String.prototype) which simply strips any tags from a given string. The basic syntax is:

'<a>Hello wonderful world!</a>'.stripTags(); //=> 'Hello wonderful world!'

This might be sufficient 90% of the time, but what if we want to strip only particular set of tags? It seems as if the only solution would be to write our own function or change prototype’s source code (which is a really terrible thing to do). This is where wrap comes into play. By wrapping the prototype’s stripTags we keep the same logic in the same place which is a good thing. One of the beauties of javascript’s dynamic nature is that we can define different behavior depending on whether any arguments were passed to the function or not. Let’s say we want out stripTags to be defined like so:

String.prototype.stripTags(); // => Looks just like regular stripTags and should act the same - strip all tags
String.prototype.stripTags(tag1, tag2, ... , tagN); // => Should have augmented behavior to ONLY strip specified tags (tag1, tag2, ... , tagN)

Now, if we needed to strip all <p> and <span> tags, we would simply do:

'<p><a>ninjas gone wild... <span>or javascript on steroids</span></a></p>'.stripTags('p', 'span'); // => '<a>ninjas gone wild... or javascript on steroids</a>'

Ok, looking good so far. Let’s see how this is done, and then I’ll explain what exactly is going on:

String.prototype.stripTags = String.prototype.stripTags.wrap(function(){
	var args = $A(arguments), proceed = args.shift();
	if (args.length > 0) {
		return this.replace(new RegExp('<\/?(' + args.join('|') + ')[^>]*>', 'gi'), '');
	}
	return proceed();
})

1

We change stripTags by assigning same function, but with “wrap” invoked on it. Function passed to “wrap” is where we define all the behavior. First argument of this anonymous function references original function (the one that we’re wrapping – in this case – stripTags). Other arguments correspond to those that are defined within our original function (in this case, though, stripTags does not have any named arguments)

String.prototype.stripTags = String.prototype.stripTags.wrap(function(){
...

2

What we have here is a common technique used internally in prototype when working with unknown number of arguments. First, we extend “arguments” variable with Array’s enumerable methods, then we reference first argument from that array, shifting it from the array at the same time. What we have now is our first argument (which, as you remember, references original function) and the rest of the arguments, which in our case are just tag names.

...
var args = $A(arguments), proceed = args.shift();
...

3

Here comes the modified part. If the function had any arguments passed into it, we replace the string using regular expression (which matches all those tags) and return modified string.

...
if (args.length > 0) {
	return this.replace(new RegExp('<\/?(' + args.join('|') + ')[^>]*>', 'gi'), '');
}
...

4

If this is not the case – there are no arguments passed – we simply invoke original function, as if nothing had ever happened : )

...
return proceed();

That’s all there is to it. Simple, effective and sort of mind-blowing, don’t you think?

Later, I will post some more examples on what becomes possible using this technique, but as for now, I hope this one clears some potentially confusing parts and you can start experimenting with it right now.

Enjoy!