Extending Prototype.js
May. 3rd, 2006 08:18 amFor javascript programmers, Prototype.js is probably the coolest thing around; easy to learn, but not so easy to wrap your head around all at once. Probably the hardest thing to grasp about it is the way it hybridizes Javascript's lisp-like internal structure with more traditional OO constructs like classes, construtors, and interators, then turns around and re-exposes those lisp-like capabilities with functional utilities like detect() and map().
But Prototype is not always complete. A noteable example can be found in PeriodicalExecuter, which takes two arguments: a function to callback, and an interval, and on the interval it calls the callback. The problem is that there's no utilitarian way to pause a PeriodicalExecuter; you can only stop and restart it. If the callback function has some state (which is possible in Javascript, now that closures in Javascript have become commonplace), tough.
So I wrote a PeriodicalExecuterToggled, which allows you to stop and start the PeriodicalExecuter at will and keep the callback unchanged.
Prototype provides the methed Object.extend(), which takes two arguments: the object to be extended (the destination), and the object with which to extend it (the source). It does this in a simple way: it simply copies every reference of the source class to the destination class.
Prototype also provides Class.create(), which creates class-like closures: collections where the variable "this" refers to the local object and which can be created at will with the constructor initialize().
Puting this all together, my class definition looks like this:
The first thing to notice is the first two lines: First, I create an empty class called PeriodicalExecuterToggled. Then, with that inner Object.extend(), I copy all of the methods of PeriodicalExecuter's prototype onto it. Object.extend() returns the source reference, so its return value can then be used in the outer Object.extend() to further decorate the prototype with methods.
Because prototype is object-based but not object-oriented, it is not possible to access the overridden methods of a parent class: you can only replace them. To add a reference to the timer object itself I had to duplicate almost all of PeriodicalExecuter's initialize() method.
Likewise, PeriodicalExecuter did not record the reference to the timer handle, so I had to override registerCallback.
The next two methods are new: clearCallback() does the opposite of registerCallback(): it pauses the execution. The name is misleading, but it fits in with the rest of the naming convention of prototype.js. I suppose I could have called it "pause" and "unpause" would just call registerCallback() again. And setFrequency allows the user to change the rate at which things happen, along with stopping and restarting the timer if it is, in fact, currently running.
About the only thing I didn't replace is the onTimerEvent from the originial PeriodicalExecuter.
Combined with something like the Scriptaculous Slider, this version of PeriodicalExecuter can provide for some interesting user-controlled experiences with javascript animation, regular updates from back-end servers, and other Web 2.0 services.
Sorry about this article bouncing around like this: I never realized just how frakkin' hard it is to post source code to LJ!
But Prototype is not always complete. A noteable example can be found in PeriodicalExecuter, which takes two arguments: a function to callback, and an interval, and on the interval it calls the callback. The problem is that there's no utilitarian way to pause a PeriodicalExecuter; you can only stop and restart it. If the callback function has some state (which is possible in Javascript, now that closures in Javascript have become commonplace), tough.
So I wrote a PeriodicalExecuterToggled, which allows you to stop and start the PeriodicalExecuter at will and keep the callback unchanged.
Prototype provides the methed Object.extend(), which takes two arguments: the object to be extended (the destination), and the object with which to extend it (the source). It does this in a simple way: it simply copies every reference of the source class to the destination class.
Prototype also provides Class.create(), which creates class-like closures: collections where the variable "this" refers to the local object and which can be created at will with the constructor initialize().
Puting this all together, my class definition looks like this:
PeriodicalExecuterToggled = Class.create();
Object.extend(Object.extend(PeriodicalExecuterToggled.prototype, PeriodicalExecuter.prototype),
{
initialize: function(callback, frequency) {
this.callback = callback;
this.frequency = frequency;
this.currentlyExecuting = false;
this.timer = null;
this.registerCallback();
},
registerCallback: function() {
this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
},
clearCallback: function() {
clearInterval(this.timer);
this.timer = null;
},
setFrequency: function(f) {
this.frequency = f;
if (this.timer != null) {
this.clearCallback();
this.registerCallback();
}
}
});
The first thing to notice is the first two lines: First, I create an empty class called PeriodicalExecuterToggled. Then, with that inner Object.extend(), I copy all of the methods of PeriodicalExecuter's prototype onto it. Object.extend() returns the source reference, so its return value can then be used in the outer Object.extend() to further decorate the prototype with methods.
Because prototype is object-based but not object-oriented, it is not possible to access the overridden methods of a parent class: you can only replace them. To add a reference to the timer object itself I had to duplicate almost all of PeriodicalExecuter's initialize() method.
Likewise, PeriodicalExecuter did not record the reference to the timer handle, so I had to override registerCallback.
The next two methods are new: clearCallback() does the opposite of registerCallback(): it pauses the execution. The name is misleading, but it fits in with the rest of the naming convention of prototype.js. I suppose I could have called it "pause" and "unpause" would just call registerCallback() again. And setFrequency allows the user to change the rate at which things happen, along with stopping and restarting the timer if it is, in fact, currently running.
About the only thing I didn't replace is the onTimerEvent from the originial PeriodicalExecuter.
Combined with something like the Scriptaculous Slider, this version of PeriodicalExecuter can provide for some interesting user-controlled experiences with javascript animation, regular updates from back-end servers, and other Web 2.0 services.
Sorry about this article bouncing around like this: I never realized just how frakkin' hard it is to post source code to LJ!
no subject
Date: 2006-05-26 04:41 pm (UTC)I'm just now picking up AJAX for a project and came accross your little snipit here. Me being a n00b to web 2.0 event stuff, this has helped me understand a little more about how this stuff fits together.
For posting source code I use VI text replacement. I suppose Word and Wordpad have this but then, I'm a unix guy :D
I've added you as LJF too.
How to add functionality to Event.observe
Date: 2009-04-14 06:56 pm (UTC)