This week I've made a lot of progress on rails' JavaScriptGenerator. JavaScriptGenerator basically offloads a lot of the work to the underlying prototype js library, and it's meant to be an expressive means of speaking javascript with ruby code. To maintain this flexibility, I've been implementing a lot of prototype functionality in our drails javascript package.
There are 2 new classes, drails.Element and drails.ElementCollection, and one new interface, drails.Enumerable.
drails.Element is a stripped down version of prototype's Element, but rather than extending a DOMNode with additional functionality, it wraps it in a class and provides the basic API that rails explicitly exposes in JavaScriptGenerator. I say explicitly, because a neat feature of JavaScriptGenerator is that if you call a method that isn't explicitly defined in it, it tries to pass it down the chain to the underlying javascript.
The implementation of drails.Element is the opposite of the prototype Element. prototype declares a bunch of methods that look like this:
and then "methodizes" them each time a DOM Node is extended, with code that looks something like this:
for (property in Element.Methods) {
value = Element.Methods[property];
if (Object.isFunction(value) && !(property in element))
element[property] = value.methodize();
}
Function.prototype.methodize = function() {
if (this._methodized) return this._methodized;
var __method = this;
return this._methodized = function() {
return __method.apply(null, [this].concat($A(arguments)));
};
Instead of this, I define the methods as instance methods of the drails.Element class, but then provide the same "static" style interface as prototype by instantiating a new instance of drails.Element for the "static" style interface:
function functionize(method){
return function(){
var args = drails.toArray(arguments);
return method.apply(new drails.Element(args[0]),args.slice(1));
};
}
functions = {};
for(prop in drails.Element.Methods){
var value = drails.Element.Methods[prop];
if(dojo.isFunction(value) && prop[0] != "_"){
functions[prop] = functionize(value);
}
}
return functions;
})();
It took me awhile to get this flow correct and working, as there are some tricky scope issues when passing around anonymous functions. At least now I finally understand what this means.
drails.ElementCollection is similar in scope, in that it wraps a NodeList (i.e. one returned from dojo.query) in a class, and mixes in the Enumerable interface, so you end up with an Enumerable collection of drails.Elements.
Not much more to say here until I finish getting unit tests in place, but you can have a look at all the new javascript code here: http://pastie.org/226646
Please let me know if you see anything amiss with it.
