For 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:
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!
Tags: geek, javascript
Current Mood:
geeky
Current Music: Ayreon, In The Garden of Emotions