Perfection Kills

by kangax

Exploring Javascript by example

← back 722 words

Wrap it up!

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!'.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:


// Looks just like regular stripTags and should act the same - strip all tags
String.prototype.stripTags();

// Should have augmented behavior to ONLY strip specified tags (tag1, tag2, ... , tagN)
String.prototype.stripTags(tag1, tag2, ... , tagN);

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


'<a>ninjas gone wild... <span>or javascript on steroids</span></a>'.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!

Did you like this? Donations are welcome

comments powered by Disqus