Archive for the 'Tutorials' Category
Looking for another approach?
One size does not necessarily fit all. Although it is my fervent hope that the very vast majority of the book’s readers found it to their liking and very useful to their endeavors, it never hurts to cross multiple approaches in order to get a better understanding of a topic.
Which is why I couldn’t be silent on the recent release of fellow Prototype Core member Andrew Dupont’s book, Practical Prototype and script.aculo.us. Amazon has Search-Inside already, and you can grab a free chapter at Ajaxian.
You’ll notice Amazon pairs Andrew’s book and mine more often then not
Andrew’s a superb Prototype expert, one of the four people, actually, having commit rights to Prototype’s repository. He’s extremely active (far more than I these days, I’m afraid) and I have every confidence his book is very useful. Check it out!
No commentsRich autocompletion
A few days ago, an astute reader of the book, Bharat Ruparel, requested an example of multiple-update autocompletion on the book’s forums. I thought it would make for a nice demo page and a post here, so here you go, Bharat.
The main idea behind autocompletion with script.aculo.us is that the possible results are sent as a <ul>/<li> list, one list item per result, and the full textual contents of an item (including line breaks and whitespace), except whatever contents sits inside an element with a CSS class named “informal,” are extracted to provide the completed text.
This behavior can be altered, however, mainly through two means:
- Using the
selectoption to provide a CSS class name marking which textual contents to use (any other textual contents will be ignored) - Providing a custom extraction logic in addition to the default one through the
afterUpdateElementcallback.
Both these are discussed in the book (chapter 16, which is 20 pages), but the callback approach isn’t demonstrated in-depth. For this post, I prepared a detailed demo page that I invite you all to go through. It contains detailed explanative material around each step, from the most basic call to the full-fledged one, and tries to demonstrate the snags you can hit and how to achieve more advanced completion.
I hope this helps, and perhaps even provides a few of you with that “Aha!” moment I cherish.
Cheers,
No commentsMixing argumentNames and wrap for varargs-like sweetness
Back in the first Neuron Workout solutions, I suggested an exercise for implementing varargs-like functionality using two of Prototype’s extensions to Function: argumentNames and wrap. I also promised I would post a possible solution here.
The idea was to provide a mechanism that would take an object or class in, and alter any method ending with a parameter named anythingElse so that such methods would end up with any “remaining” arguments stored as an array in this special parameter.
As promised, here’s the code for it:
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | Object.enableVarArgs = function(object) { for (var methodName in object) { var method = object[methodName]; if (!Object.isFunction(method)) continue; (function() { var argNames = method.argumentNames(); if (argNames.last() != 'anythingElse') return; object[methodName] = method.wrap(function() { var args = $A(arguments), proceed = args.shift(); args[argNames.length - 1] = args.slice(argNames.length - 1); args.length = argNames.length; return proceed.apply(this, args); }); })(); } }; Class.enableVarArgs = function(klass) { return Object.enableVarArgs(klass.prototype); }; |
Before diving too much into the inner workings of this, let’s put together a demo object, reuse it for a demo class, and see whether this all works:
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | var obj = { method1: function(a, b) { console.log("CALLED: method1(" + Object.inspect(a) + ', ' + Object.inspect(b) + ")"); }, someMethod: function(a, b, anythingElse) { console.log("CALLED: someMethod(" + Object.inspect(a) + ", " + Object.inspect(b) + ", " + Object.inspect(anythingElse) + ")"); }, method2: function(anythingElse) { console.log("CALLED: method2(" + Object.inspect(anythingElse) + ")"); } }; var Klass = Class.create(obj); var obj2 = new Klass(); Class.enableVarArgs(Klass); Object.enableVarArgs(obj); obj.method1(1, 2, 3, 4); obj.someMethod(1, 2, 3, 4); obj.method2(1, 2, 3, 4); obj2.method1(1, 2, 3, 4); obj2.someMethod(1, 2, 3, 4); obj2.method2(1, 2, 3, 4); |
I used Firebug’s console object here, but you may use interactive alert calls just as well. On my browser, the output goes like this:
CALLED: method1(1, 2) CALLED: someMethod(1, 2, [3, 4]) CALLED: method2([1, 2, 3, 4]) CALLED: method1(1, 2) CALLED: someMethod(1, 2, [3, 4]) CALLED: method2([1, 2, 3, 4])
Okay, so how does this work?
Wel, we start by iterating over the object’s methods. This can easily be done by using a regular for…in loop to grab all the properties and filter using Prototype’s Object.isFunction predicate (which is just a typeof test).
11 12 13 | for (var methodName in object) { var method = object[methodName]; if (!Object.isFunction(method)) continue; |
Now the major trick was to avoid scope/reference issues with the remainding variables, most importantly argNames.
Because of JavaScript scoping issues, argNames will usually be a shared reference for all iterations of that loop. For instance, consider our test object: two of its methods match our criteria: someMethod, with argument names a, b and anythingElse, and method2, with just anythingElse. Because of this, the method replacement code (the one used in our wrap call) for someMethod will end up using the value of argNames that was set by processing method2, resulting in a wrecked final arguments list.
The traditional way of coping with such situations is to give such variables a guaranteed private scope, which is achieved by creating an anonymous function for the scope and calling it right after it’s defined (a technique used for implementing the famous module pattern, too).
This is why we wrap the remainder of our code inside this anonymous function, which we then call immediately (hence the weird )() code fragment).
14 15 16 17 18 19 20 21 22 23 | (function() { var argNames = method.argumentNames(); if (argNames.last() != 'anythingElse') return; object[methodName] = method.wrap(function() { var args = $A(arguments), proceed = args.shift(); args[argNames.length - 1] = args.slice(argNames.length - 1); args.length = argNames.length; return proceed.apply(this, args); }); })(); |
So on to the code inside this anonymous function.
We start by filtering more on the last argument name, to check whether it is indeed anythingElse. As we’re not in the lexical scope of the surrounding for loop now, we can’t just call continue, we need to return from the anonymous function (since the loop has no code after this function, it’s pretty much equivalent).
15 16 | var argNames = method.argumentNames(); if (argNames.last() != 'anythingElse') return; |
Then we replace the former code for the method by our new code, which wraps the original one. Wrapped methods always received the former version as their first argument, which we traditionally call proceed. To easily tweak the arguments actually passed to our new methods, we grab the JavaScript predefined arguments variable, pass it to $A to make sure we have a proper, full-fledged Array to play with, and take the first argument out as our original method (proceed).
18 | var args = $A(arguments), proceed = args.shift(); |
All that is left to do is put all arguments from the anythingElse-positioned one to the end of the actual argument list in an array to be passed instead of anythingElse, and “cut” the arguments array to the proper length:
19 20 | args[argNames.length - 1] = args.slice(argNames.length - 1); args.length = argNames.length; |
The actual call is performed by the native apply method on our original function, which has the good sense of taking the arguments as an array (which makes it ideal for our purposes). Of course, if there was an original returned value, we need to propagate it by returning it too.
20 | return proceed.apply(this, args); |
Feel free to ask your questions in the comments, and discuss your own solutions if you had worked out any!
You can also download the full demo script for this example and play with it (you’ll need prototype.js loaded, obviously).
Also note I’m working on the second installment of the Neuron Workout Solutions, so stay tuned!
No commentsCustom events, the definitive tutorial™
I just realized I hadn’t mentioned it here, but Andrew Dupont (aka My Favorite Competitor) released a while ago a kick-ass article on custom events in Prototype 1.6, complete with sweet unification of mousewheel events. It’s very well written, clear and effective.
If you haven’t read it yet, hop over the link!
No comments


