Sunday, February 10, 2008

Binded Callbacks with Prototype

This article is all about using of the functions binding in callbacks when you're coding with the Prototype JavaScript Library.

We all know, that Prototype have got the Ruby's Enumerable module port in JavaScript. The idea is pretty simple, you've got a method where you put your callback function and when an object iterates through its stuffs it calls the function. Like that


var foo = function() {
var two = [1, 2, 3, 4].find(function(item) {
return item == 2;
})
};


That's fine, but there's a problem. When you write code like that, each such callback function creates an own scope and when you try to write a code like below it won't work


var proc = {
make_it_double: function(list) {
return list.collect(function(item) {
return this.double_item(item);
});
},
double_item: function(i) {
return i * 2;
}
};
alert(proc.make_it_double([1,2,3]));


it will say you that there's no double_item function, and that's right, case it won't look for the proc.double_item function, it will search for the double_item method in the scope of the function object which you've sent as the attribute into the .collect method. (Which in the case will be the window object).

For such cases in the Enumerable module of Prototype defined an ability to pass another, optional attribute which will represent the scope in which the callback function should be applied.


.................
return list.collect(function(item) {
return this.double_item(item);
}, this);
// ^
// +---- here it is
...................


With such a change the code will work, and you'll get the [2,4,6] alert.

That's nice. But if say you're implementing your own function which accepts a callback, and you don't want to pass the scope attribute. Something like that.


var my_proc = {
handle: function(elements, callback, something) {
.............................
for (var i=0; i < elements.length; i++) {
var something = callback(elements);
}
.............................
}
};
var my_starter = {
foo: function() {
my_proc.handle([1,2,3,4,5], function(item) {
return this.update(item);
},....);
},
update: function(item) {
return item * 2;
}
};


How can you handle that? That's easy. Prototype defines an additional method .bind(scope_object) for the functions, which returns another function which has the same interface but will execute the original function in the correct scope. Say you may write it like this.


................................
foo: function() {
my_proc.handle([1,2,3,4,5], this.update.bind(this),....);
},
................................


Looks prettier, but this is not all again. What if you need not just to bind an existing method, but to make some additional handling, like that


................................
foo: function() {
var something = 'foo';
my_proc.handle([1,2,3,4,5], function(item) {
return something == 'foo' ? this.update(item) : 'bla';
},....);
},
................................


What if you want a dumn function, like you used to have it with blocks in Ruby? That's easy, and kinda beauty. You can do it with that trick.


................................
foo: function() {
var something = 'foo';
my_proc.handle([1,2,3,4,5], (function(item) {
return something == 'foo' ? this.update(item) : 'bla';
}).bind(this),....);
// ^
// +------------ Here you go
},
................................



That's simple and maybe strange looking but it's handy. You may bind your functions on fly, and this's a quite often case.

And if you look closer you'll see the idea is in calling construction like (function() {}), and actually you may go further and use it more like in the functional programming style. Say like that.


var caMeL = (function(str) {
var parts = str.split('_'), camelized = parts.shift();
for (var i=0; i < parts.length; i++)
camelized += parts[i].charAt(0).toUpperCase + parts[i].substr(1);
return camelized;
})('ca_me_l');


This is probably a quite dummy example and I should make it recursive to feel myself like a true hacker, but it shows the idea. You may apply functions on fly just in the time of their creation. Browse the Prototype's source code and you'll find lots of such tricks in there.

No comments: