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).
When I create backoffices for projects, a number of recurring needs surface. Over time, I extracted most of these needs in generic-enough CSS styling, XHTML markup and JavaScript files that I keep reusing from one screen to the next, one project to another. This helps bring a sense of consistency to the various screens, and reduces the learning curve for their users. It also means I end up developing new, qualitative screens pretty fast, as most of the building blocks are already there.
Ajaxified checklists
One of the needs I often encounter is “checklists,” or checkbox lists. They usually materialize a n-n association in the database, when the potential set is short enough to be comfortably handled in one view. Checking an item establishes the relationship; unchecking it breaks it. I like to do away with manual submission of the resulting checked statuses, and ajaxify it: every time a checkbox’s status changes, I just ajaxify the container form and update some notice element on the view to let the user know whether persistence worked (the nominal case) or failed (the exceptional case).
To deal with this, what I settled on a few conventions:
- Each checklist is wrapped inside its own
<form>, that bears a “checkListForm” CSS class. - The “notice” element should be a paragraph (
<p>) with CSS class “flash” sitting in the DOM someplace after the form, but with the same direct containing element (say, it may just follow the form closing tag).
So I’ve got markup similar to this in my web page:
<form id="locationsForm" method="post" action="/destinations/9/terrains/2/locations" class="checkListForm"> <ul> <li> <input type="checkbox" name="locations[]" value="42" id="chkLocation_42" /> <label for="chkLocation_42">Cool beach</label> </li> <li> <input type="checkbox" name="locations[]" value="43" id="chkLocation_43" /> <label for="chkLocation_43">Nice creek</label> </li> <li> <input type="checkbox" name="locations[]" value="44" id="chkLocation_44" /> <label for="chkLocation_44">Scruffy haven</label> </li> </ul> </form> … <p class="flash" style="display: none;">Locations successfully updated.</p>
If I didn’t require JavaScript (something I’d not mandate on a public-facing page, but my backoffices can usually afford web standards + JS), I’d have a submit button that JS would then hide, to allow for regular form submission when JS is disabled.
On the server side, I’ve got an action mapped to the form’s URL. In Rails, it would look like this:
def locations @terrain = Terrain.find(params[:id], :include => :locations) @terrain.location_ids = params[:locations] head :ok end
Here’s a rough PHP equivalent:
$terrainId = 9; // There would be some URL analysis to get this… mysql_query("delete from locations_terrains where terrain_id = $terrainId;"); foreach ($_POST['locations'] as $locationId) mysql_query("insert into locations_terrains (location_id, terrain_id) values ($locationId, $terrainId);"); header('HTTP/1.1 200 Associations updated'); return;
Now, the scripting handling such lists goes like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | function bindCheckLists() { var lists = $$('form.checkListForm'); lists.each(function(form) { var currentEffect, flash = form.next('p.flash'); new Form.EventObserver(form, function(form, ser) { form.request({ onSuccess: function() { currentEffect && currentEffect.cancel(); flash.setOpacity(1).show(); currentEffect = Effect.Fade(flash, { duration: 0.5, delay: 1 }); }}); }); }); } // bindCheckLists … document.observe('dom:loaded', bindCheckLists); |
So what’s going on here?
- We’re getting all the forms with the proper CSS class
- For each of them, we get the corresponding notice element (the next paragraph with a “
flash” CSS class), and create aForm.EventObserver, which will trigger its callback whenever a field inside the form sees its value changed; in our case, whenever a checkbox is toggled. - The callback submits the form through Ajax, using Prototype’s extended
requestmethod on forms, which just creates anAjax.Requestbased on the form’smethod=andaction=attributes, and actual field values. - When the server side returns a successful result, we show the notice paragraph for a second, then quickly fade it away
Because the user might toggle off multiple checkboxes within a short span, we’re making sure there’s only one fade effect at a time. To do this, we maintain a reference to the current effect, if any, and cancel it prior to resetting the opacity to 1 (full opacity) and triggering the latest effect.
That works pretty well, and it lets me quicky whip up checkbox-based association management.
Ajaxified list item deletion
In the same spirit, I use a set of conventions to quickly whip up ajaxified delete icons on item lists.
The markup would be something like this:
<ul class="mainList" id="customers" kind_label="the customer"> <li> <a href="/customers/1/edit" title="Edit this customer"><span class="name">John Smith</span></a> <a href="/customers/1" title="Delete this customer" class="delete"><img src="/images/back/delete.png" alt="Delete" /></a> </li> <li> <a href="/customers/2/edit" title="Edit this customer"><span class="name">Mark Mywords</span></a> <a href="/customers/2" title="Delete this customer" class="delete"><img src="/images/back/delete.png" alt="Delete" /></a> </li> <li> <a href="/customers/3/edit" title="Edit this customer"><span class="name">Frodo Baggins</span></a> <a href="/customers/3" title="Delete this customer" class="delete"><img src="/images/back/delete.png" alt="Delete" /></a> </li> </ul>
Notice that the delete links map to the proper URL (we’re going RESTful here), so all we need to do is intercept clicks on “delete“-class’d links and emit a HTTP-delete request on this URL, then react to a successful response by removing the list item from the page.
Also, in order to keep the generic script useful enough, we equip our lists with a custom “kind_label” attribute that we’ll use in our confirmation message, as well as a “name“-class’d fragment within the list items to obtain specific messages (”Are you sure you want to delete the customer ‘John Smith’?” is a whole lot better than “Are you sure you want to delete this customer?”, not to mention “Are you sure you want to delete this?”).
The server-side code just needs to grab the ID in the URL and delete the relevant entity from the database. In Rails, it could be as simple as this:
def destroy Customer.find(params[:id]).destroy head :ok end
In PHP, it would look more like this (assuming we don’t have to handle cascade deletions or custom deletion logic):
$customerId = 1; // URL analysis instead… mysql_query("delete from customers where id = $customerId"); header("HTTP/1.1 200 Customer deleted");
As for the script, here it goes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | function bindMainListDeleters() { var lists = $$('.mainList'); if (!lists) return; lists.invoke('observe', 'click', function(e) { var activator = e.findElement('a.delete'); if (!activator) return; e.stop(); activator.blur(); var name = activator.up('li').down('.name'), kindLabel = activator.up('li').up().readAttribute('kind_label') || 'the item'; name = (name ? name.innerHTML.stripTags().unescapeHTML() : '').strip(); var msg = 'Are you sure you want to delete ' + kindLabel + (name == '' ? '' : (' “' + name + '”')) + '?'; if (!confirm(msg)) return; new Ajax.Request(activator.href, { method: 'delete', onSuccess: function() { activator.up('li').fade({ duration: 0.25 }); }, onFailure: function() { alert('Deletion failed.'); } }); }); } // bindMainListDeleters … document.observe('dom:loaded', bindMainListDeleters); |
That’s an interesting piece, it contains a lot of tiny patterns I always use:
- I start by grabbing the element(s) I’m going to play with, and check they exist: if not, I exit the function (on line 3). This lets me put such generic behaviors in a backoffice-wide script without breaking pages where it’s irrelevant.
- The event handler function (here, the anonymous function passed to
invoke) is attached to the container (the list) instead of each individual link, thus relying on event delegation (which leverages event bubbling): when a click occurs somewhere inside the list, I just verify it happened inside a link with thedeleteclass, using the all-importantfindElementmethod of event objects (line 5). If it didn’t, then myactivatorvariable isundefined, and I just exit the handler, as it is then irrelevant. - As soon as the handler is relevant, and because it’s a “Ajax link” kind of behavior, I start by stopping the event (preventing further bubbling and default behavior–for instance, regular navigation to the link’s
href) and blurring the link (I find it ugly to keep it focused), on lines 7 and 8. - I then grab the relevant item’s name (if any) by looking for anything with a “name” CSS class in my containing
<li>, and also grab the item kind’s label in the surroundling list’s customkind_labelattribute. ** If there is no such attribute,readAttributewill returnundefined, so the logical-or operator (||) will evaluate to its right-hand operand, and assign a default value (”the item”) to my variable. ** If I did find a name-related markup, I strip any tags from it and unescape its HTML for non-document display (it’s going into aconfirmbox). ** The final message composition occurs on line 11 - I then ask the user to confirm, and if they do, go on with my Ajax request.
** Being RESTful, I use the HTTP DELETE method over the item’s identity URL, which was the link’s original
hrefanyway (this spares me the need to maintain some scripted hash somewhere binding ‘#’-referecing links to their actual URLs). ** Successful deletion triggers a a quick fade of the list item (it could be complemented by aremovecall when the effect is over, too). ** Failed deletion triggers an alert message.
A quick note to Rails 2.x developers: if you’re using the request forgery protection (enabled by default), you’ll need to pass the authenticity token along in your request’s parameters. I usually do that by putting it in a global script variable, initialized by an inline script in my backoffice’s outer layout’s <head>, and then I add it to all my non-GET Ajax requests through the parameters option.
That’s it for today
I hope you liked these two bits of code. I look forward to sharing more with you soon about real-world code I use. Keep in mind that this code is no silver bullet; code never is. But it may provide ideas and tricks to address your own needs.
4 Comments so far
Leave a reply



In your first example you mention what you’d do if javascript was not enabled. There doesn’t appear to be a slick way of allowing the javascript impaired on the second example. Won’t clicking the delete link just end up showing you the customer?
Hey Darrin,
Good question.
When following REST conventions, our link will go to the identity URL for the resource, which would normally result in a regular “show” action. What we could do, for instance, to avoid that is complete this URL with some extra param, perhaps something like
?mode=delete, and use that on the server side to produce a regular delete confirmation form, that would produce the same final request as our Ajax one (e.g. a HTTP DELETE, or emulation thereof, to the identity URL).When I’m in my backoffices, I mandate web standards compliance (i.e. “not IE”), session cookies and enabled JS. On frontoffices, I find myself using the kind of progressive enhancement I just described.
Does that answer your question?
Cheers,
Nice tutorial! Thanks Christophe
Keith: most welcome