Ajax progress indicator with prototype
Courtenay : March 26th, 2008
Today I'm going to show you how I combine my old-school Javascript with the latest Prototype has to offer. I hope you learn a little about the basic use and misuse of "bind". Hopefully someone will chime into the comments and give a better way to do this code!
You may know that you can add a global ajax responder to your application:
Ajax.Responders.register({
onCreate: function() {
Ajax.activeRequestCount++;
},
onComplete: function() {
Ajax.activeRequestCount--;
}
});
But I'm not going to cover that here, because you can't get the originating object. For example, there's no way here for a global ajax responder to get the anchor object.
<a class="add" href="#" onclick="new Ajax.Request('/products/3/categorizations/18', {asynchronous:true, evalScripts:true, method:'put'}); return false;">add</a>
Here's the rails generator code:
<%= link_to_remote "add", { :url => product_categorization_path(@product, category), :method => :put } %>
So, let's add some code to prevent the user from clicking twice.
To keep the view clean, I'll implement it as a simple view helper in application_helper.rb
<%= link_to_remote "add", { :url => product_categorization_path(@product, category), :before => ajax_progress, :method => :put } %>
def ajax_progress
"setTimeout(function(){ this.innerHTML = '...' }.bind(this), 100)"
end
SetTimeout takes either a function name or an anonymous function. I'm binding the anonymous function to "this", which in this context refers to the 'A' anchor tag. Because it's bound, I can refer to "this" inside the function and get the anchor tag.
The setTimeout is useful because it lets us modify the tag (even remove it from the DOM) without messing with the ajax request. You can use setTimeout on a form to change the action, so it can't be submitted twice (for important forms)
I'm going to step it up one more, because this code belongs in a library. Time to open up application.js and create a ghetto pseudoclass singleton thingy.
MyApp = {
/* do this while we're waiting for ajax responses */
ajaxing: function(){
this.innerHTML = '...';
this.onclick = FacetApp.nothing;
},
/* don't do anything. useful for descriptive declarations like $('a').onclick = FacetApp.nothing */
nothing: function() {
return false
}
}
This will prevent the link from doing anything if the user clicks it twice.
Now, to modify the ajax_progress helper to simple beauty.
def ajax_progress
"setTimeout(MyApp.ajaxing.bind(this), 100)"
end
Finally, let's show the user a GMail-style notice after 5 seconds, just to let them know that we're running slow or have just plain died.
MyApp = {
ajaxing: function(){
this.innerHTML = '...';
this.onclick = FacetApp.nothing;
setTimeout(MyApp.slooow.bind(this), 5000);
},
/* shows a ? symbol. useful for showing progress on an ajax action */
slooow: function(){
this.innerHTML = ".?."
/* show a warning message in the ui somewhere */
}
}
Got a better way of doing this?
1 Response to “Ajax progress indicator with prototype”
Sorry, comments are closed for this article.
March 27th, 2008 at 01:13 PM
Not sure if it's a "better way" — but an alternative to changing the innerHtml that might also be more flexible and prettier would be to add a classname, say "ajaxing", to the A element (or whatever element you want to use this on). And then, of course, to remove that class name.
Then you'd style a.ajaxing appropriately — first idea that pops into my head is to add to padding-right and set an animating image of "..." as a non-repeating right-set background image to it. Or whatever suits your fancy.