In my Javascript-fu adventures over the past week or so, I’ve consistently run into the same problem. I want to do slight variations of an Ajax request on an individual basis. Here is a common method from my code: (Note: these examples use Event.addBehavior from Dan Webb’s excellent lowpro library for registering events.)
Event.addBehavior({
// hijack any forms with the class "new" and submit them using Ajax
'form.new:submit': function(event) {
this.request({evalScripts: true,
onLoading: function() { this.disable(); }.bind(this),
onComlete: function() { this.enable(); }.bind(this)
});
Event.stop(event);
}
});
But occasionally, I want a certain form to do something slightly different. For example, I have some forms that are hidden by default and I want to hide them again after they are submitted:
Event.addBehavior({
'form.new.hidden:submit': function(event) {
this.request({evalScripts: true,
onLoading: function() { this.disable(); }.bind(this),
onComlete: function() { this.enable(); }.bind(this),
onSuccess: function() { new Effect.BlindUp(this); }.bind(this)
});
Event.stop(event);
},
})
The problems with this are, 1) there is a lot of duplication, and 2) if this form has the “new” class name, it’s going to end up with 2 event handlers registered, both making an Ajax request.
So what I want is event observers on Form.request and Anchor.request for the Ajax request lifecycle, so I can call form.request.observe('loading', function() { … }), and that will be invoked any time an Ajax request is made for that form:
Event.addBehavior({
// submit form requests using ajax
'form.new:submit': function(event) {
this.request({evalScripts: true});
Event.stop(event);
},
// register observers to disable and enable the form
'form.new': function() {
this.request.observe({
'loading': function() { this.disable(); }.bind(this),
'complete': function() { this.enable(); }.bind(this)
});
},
// register an observer to hide the form on success
'form.new.hidden': function() {
this.request.observe('success', function() {
new Effect.BlindUp(this);
}.bind(this));
}
})
And this is where I need your help.
The first problem is that we need some type of event notification framework. I have that part solved by using a slightly modified version of Ryan Johnson’s Object.Event library, which basically allows you to add event observers to any object, and then call them by calling notify('eventname') within the object.
Next, I’ve made a modified version of Anchor.reqeust method from my post yesterday, which calls notify on each of the Ajax callbacks, and extends the request methods with the event notification methods:
if (!window.Anchor) var Anchor = new Object();
Anchor.Methods = {
request: function(anchor, options) {
anchor = $(anchor)
callbacks = {}
$A(['loading', 'complete', 'exception', 'failure', 'success']).each(function(event) {
callbacks['on' + event.capitalize()] = function() {
anchor.notify.apply(anchor, arguments.unshift(event));
};
});
options = Object.extend(Object.extend({
method: 'get'
}, callbacks), options || {});
return new Ajax.Request(anchor.readAttribute('href'), options);
}
}
Object.Event.extend(Anchor.Methods.request);
Element.addMethods('a', Anchor.Methods);
This is where the second problem comes in. While the event notification methods are added to Anchor.Methods.request, they don’t get added to the anchor objects when the request method does. They’re getting lost in Prototype’s Element.extend method that adds the extensions to each element.
And as soon as I get that problem solved, I’m going to have another one: one instance of Anchor.request will be shared amongst all the anchor objects, so registering an observer on one will register it for every Ajax request. What I need then is for the event observer to keep track of the registered observers in each object, even though the methods are on the shared register function.
I could move the event registration methods to the anchor instead of on the request method, but that has problems of it’s own. Namely, each element already has an observe method for the browser’s events, and I can’t come up with a better name than observe. Besides, I think the methods belong on the request object; they are specific to the Ajax request.
So, does anyone have any good ideas? Is this a lame/unnecessary feature?
I just discovered that prototype has a really cool way to add methods to all instances of any tag. Simply call Element.addMethods(tagname, methods). For example, here’s how I added a request method to anchors, which will just make an ajax call for the anchor’s href:
if (!window.Anchor) { var Anchor = new Object(); }
Anchor.Methods = {
request: function(anchor, options) {
anchor = $(anchor)
options = Object.extend({method: 'get'}, options || {});
return new Ajax.Request(anchor.readAttribute('href'), options);
}
}
Element.addMethods('a', Anchor.Methods);
So now I can go through and hijack links with a specific tag name and just call request() on them:
$$('a.jax').each(function (link) {
link.observe('click', function(event) {
link.request({evalScripts: true});
Event.stop(event);
});
})
new Cookie({eggs: 1, flour: 3, sugar: 1.5, brownSugar: 1});
Oh, wait…not those kind of cookies (mmm, now I’m hungry for cookies).
The script.aculo.us wiki has some code for working with cookies in JavaScript. I’ve extended it a bit to allow for other options when setting the cookies. Here’s how to use it:
// setting cookies
Cookie.set('name', 'value');
// change domain, path, and expiration in # of days
Cookie.set('name', 'value', {
domain: 'foobar.com',
path: '/path',
expires: 14
});
// the google cookie (doesn't expire)
Cookie.set('name', 'google', {expires: false});
// reading cookies
Cookie.get('name');
// Get an array all cookies that are set
Cookie.all();
// erase a cookie
Cookie.erase('name');
// check if browser accepts cookies
if(Cookie.accept()) {
// do stuff with cookies
}
You can grab the code from here.
If for some reason you use one of those other browsers, then you don’t have the pleasure of using Firebug for debugging (Safari nuts, I know you secretly debug your javascript and stylesheets with Firebug in Firefox, despite it “not looking like a Mac app”).
Fortunately for you, there’s Firebug Lite. From the website:
Firebug is an extension for Firefox, but what happens when you need to test your pages in Internet Explorer, Opera, and Safari? If you are using console.log() to write to Firebug’s console, you’ll wind up with JavaScript errors in these other browsers, and that’s no fun.
The solution is Firebug Lite, a JavaScript file you can insert into your pages to simulate the Firebug console in browsers that are not named “Firefox”.
To ease the pain of debugging Javascript in those other browsers, I’ve thrown together a little plugin that automagically includes Firebug Lite into your application in development mode. All you need to do is install the plugin and include the default javascripts in your layout (which you’re probably already doing):
<%= javascript_include_tag :defaults %>
Just hit F12 (Ctrl+F12 on Mac) to open Firebug Lite on any page, or jump the focus directly to the command line with Ctrl+Shift+L (or ⌘+Shift+L on Mac).

http://source.collectiveidea.com/public/rails/plugins/firebug