The Bungee Blog

News, updates and rants around The Bungee Book (the landmark book on Prototype and script.aculo.us)

Archive for the 'Cool tricks' Category

Getting Out of Binding Situations in JavaScript

I’m delighted to report that my article in renowned A List Apart just went out, and it covers a pretty tricky aspect of JavaScript often misunderstood or poorly known: binding.

Head over to ALA and enjoy: Getting Out of Binding Situations in JavaScript.

2 comments

Rich 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 select option 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 afterUpdateElement callback.

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.

Check out the full demo page!

I hope this helps, and perhaps even provides a few of you with that “Aha!” moment I cherish.

Cheers,

No comments

JavaScript Nuggets #1

This post opens up a new series on The Bungee Blog, called “JavaScript Nuggets.” In this series, I shall attempt to share with you a few bits of code I have been using and refining over actual projects, bits that I find myself reusing all the time, which means I’m happy enough with them.

Today we’ll talk about two generic functions I use in most of my backoffice screens to consistently “ajaxify” checkbox lists (persisting the checked ones on-the-fly, without needing to submit anything) and deletion of list items (with proper, dynamic confirmation dialog boxes).

Read more

4 comments

Mixing 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 comments

No more excuse not to upgrade to 1.6!

I finally find a moment to spread the good news: Tobie equipped Prototype with a deprecation/removal assistance layer. This extra deprecation.js file, to be loaded right after Prototype, spews clear, detailed, advise-replete messages in your Firebug console whenever your scripts use a now-removed or deprecated feature from earlier versions of the lib.

It’s a great help for migrating, and migrate you should. Get the whole details, usage recommendations, and grab the file on the relevant post at the Prototype Blog.

No comments

A schedule for Neuron Workout solutions

Just FYI, here’s the system I came up with for Neuron Workout solutions:

  • Roughly one installment per week, usually probably in mid-week
  • A maximum of 5 workouts (single items in the Neuron Workout sections) per installment
  • Chapters 4 to 6 will come first, then I’ll just pick workouts through the book in a less sequential way
  • When a workout has multiple valid solutions, I’ll try to present at least a few
  • I’ll try to highlight “bad” solutions as well, explaining why they’re bad, to stir you away from them.
  • As the paper book releases around January 11 at major retailers (e.g. Amazon.com), I’ll give another two weeks without spoiler to potential readers before starting the solutions here. So that means the first installment should appear around January 28.

I look forward to starting this, and seeing what kind of productive debating can result in the comments.

In the meantime, Prototype Core’s hard at work on 1.6.1, so stay tuned.

No comments

Custom 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