Robust AJAX UIs with jQuery-UJS and Server-Generated Javascript Responses

Robust AJAX UIs with jQuery-UJS and Server-Generated Javascript Responses

The jQuery-UJS (“Unobtrusive Javascript”) library has been around since 2010, making it quite old by the standards of frontend tooling, whose pace of evolution is swift. In many contexts, however, jQuery-UJS, along with complimentary Server-generated Javascript Responses (SJR) still surpass more modern frontend tooling and frameworks in metrics that should matter to anybody building a web app with dynamic UI requirements, even in 2016.

The most profound virtue of UJS is probably how palpably it minimizes your overall code surface area. AJAX bindings are made to the specification of certain data-attributes read off of elements in the DOM, which in practice oftentimes means a lot of boilerplate Javascript files you might otherwise have in your codebase you simply do not need. Furthermore, with UJS, most of the custom Javascript that you do execute to implement your rich user interactions is, by necessity, nicely compartmentalized in server-side templates oftentimes only 1-3 lines long, and very clearly named and situated.

In many contexts, jQuery-UJS, along with complimentary Server-generated Javascript Responses (SJR) still surpass more modern frontend tooling and frameworks in metrics that should matter to anybody building a web app with dynamic UI requirements, even in 2016.

That said, UJS and SJR are not a panacea. I have never worked a project that didn’t have several features that ultimately weren’t a fit. Still, for a non-negligible part of many applications’ AJAX-ey feature-set, SJR with UJS constitute an incredibly robust, minimal (by source line count), testable and maintainable solution.

Let’s look at a concrete example of an AJAX-ey UI implemented with SJR and UJS. The UI that we’re ultimately going to achieve is the following:

It is a simple AJAX interface to manage a list of notes. We can assume the following notes schema:

And a simple model definition:

We’ll implement our UI with 1 template and 2 partials. First, an “index” template:

This index template renders a collection of notes (for each, Rails render method will render the _note.html.erb partial and pass the record) and a form partial. We will implement the “_note” partial like so:

…And the “_form” partial like so:

Hitting the index action and rendering the index template give us a nice empty state:
screen-shot-2016-10-04-at-1-27-20-pm

Now, let’s handle the create action. We want the create action to take what we input in the bottom form, validate it, render it into the note partial, append and highlight that partial, and clear the form. At this point let’s have a look at our create controller action:

This controller action implementation should appear pretty idiomatic and familiar if you have worked in any capacity with Ruby on Rails. The only conspicuous deviation from a more traditional, non-SJR controller action implementation is what you find in the bodies of the respond_to blocks, which explicitly indicate “js” as a handled MIME type. The DOM updates that constitute our dynamic UI are found rather in our views directory. When we POST a note to the create action, for instance, in the success case we render the following JS snippet in create.js.erb:

This code renders our server-side “note” partial and appends it to our notes list. It then clears out the form, grabs the newly-appended note, and highlights its body using the jQuery-UI “highlight” effect.

The dom_id method that you see being used to grab the DOM node is a stock helper from the actionview library which generates unique DOM-style IDs when passed an instance of an ActiveRecord model. If you look to the _note partial implementation further up you will see that the dom_id helper was used to generate the id attribute set on the div defined within that partial. You can find documentation for dom_id here.

Let’s now look at our failure case. In the case that validation of the user’s posted input fails, we render a new.js.erb js template:

As you can see, this js template simply replaces the in-page form with one that we render out anew with the present controller context. This form, as it’s built with a simple_form helper, will contain elements describing the error to the user.

Now that we’ve seen how we handle adding notes in the UI via AJAX, let’s have a look at how we can implement note editing and deletion features. The editing UI we aim to achieve is the following:

You’ll see that in our _note partial we have “edit” and “destroy” links constructed with the link_to helper and with both invocations are passing the remote: true option value (the “destroy” link has also been passed method: :delete). These options instruct the UJS library preventDefault on any clicks of these elements and instead make an ajax request against the URI indicated in the href and eval the server response. At this point it would be helpful to fill out the rest of our controller class:

When we click the “edit” link, UJS will perform a GET request against the link URI, hitting the edit controller action above, which will render this javascript snippet for evaluation:

This snippet simply grabs the note element in the DOM and swaps out its contents for our edit form. This form POSTs asynchronously to our update action, which either replaces the form with a refreshed _note partial on success…

…or replaces it with a re-rendered form (including validation errors) in the case of validation failure by re-rendering the edit template.

Similarly, our destroy action destroys the indicated record, then renders out a snippet which simply grabs the corresponding DOM element, fades it out and removes it from the DOM:

Non-UJS Features in a UJS Way

One feature that we do not get in jQuery-UJS but that would be great to have in our UI is drag-and-drop sorting. Let’s look at how we might implement this feature in an unobtrusive way that aligns with the conventions of UJS, introducing a minimum of bespoke scripting to our codebase. 2 key concepts in the jQuery-UJS library are (1) implicit binding based on data-attributes and (2) UI updates via the evaluation of Server-generated Javascript. We’ll try to adhere to these principles in the sortable-list implementation that we write:

At a high level what we’ve done here is take any DOM node with a data-sortable-list attribute on it and wrap it in an instance of a class (App.Views.Common.SortableList) that configures jQuery sortable for us. The jQuery-sortable integration is pretty stock. The only things worth noting are that we pull the URI to which we post sort-order changes off of the “data-sortable-list” attribute and we parse record IDs out of the list item id attributes based on the assumption that these list items are using the dom_id helper to set these attributes. You will notice that we do no explicit evaling of the response to our PATCH requests, and this is because the jQuery UJS library does this evaling implicitly for any server response with a Content-Type of “text/javascript”.

Server-side, we handle PATCH requests with a positions action that looks as follows:

This action simply fetches the indicated “target” record (the record being dragged) into an instance variable so it is in context for the template, then iterates over the positions hash that has been supplied to the action, grabbing Note records and updating their positions as specified by the hash. The positions.js.erb Javascript template that is rendered with this action simply grabs and highlights the target Note node:

What we end up with is a drag-and drop sorting interface which is slick, functional and, from an implementation perspective, incredibly minimal and robust:

With that, we’ve covered the entirety of the features and interactions we’d set out to. This walk-through can be downloaded in a complete and runnable form from https://github.com/hackernotes/ujs-demo. I hope this walk-through has done a decent job of demonstrating the benefits of UJS and SJR in respect of minimalism and robustness, even if you ultimately decide these tools and practices aren’t a fit for your application’s particular needs.

View Companion Repository

Nicholas

Hi! I'm Nicholas and I like building stuff. I spent a decade working with startups in NYC as a developer before turning my attention to seed-stage investing beginning in 2018. I write here on topics including startups, investing, travel, software development, and just about any other matter of personal relevance. I can be reached by email at [email protected].

No Comments

Leave a Comment

Please be polite. We appreciate that.
Your email address will not be published and required fields are marked