A Tour of Rails’ jQuery UJS

Derek Prior

If you have a look at the default application.js file generated by Rails, you’ll see //= require jquery_ujs. You might know exactly what this require is for, but if you’re like me you know you need it but have only a vague idea of what it’s responsible for.

Maybe you’ve looked at that line and thought, “I should really figure out what that does someday.” Well, for me, today is that day. I thought you might want to join me.

Unobtrusive JavaScript

The UJS in jquery-ujs stands for unobtrusive JavaScript. This is a rather broad term that generally refers to using JavaScript to progressively enhance the user experience for capable browsers without negatively impacting clients that do not support or do not enable JavaScript.

jquery-ujs wires event handlers to eligible DOM elements to provide enhanced functionality. In most cases, the eligible DOM elements are identified by HTML5 data attributes.

Let’s have a look at the progressive enhancements jquery-ujs provides.

POST, PUT, DELETE Links

<%= link_to 'Delete', item, method: :delete %>

Clicking a link will always result in an HTTP GET request. If your link represents an action on a resource it may be more semantically correct for it to be performed with a different HTTP verb; in the case above, we want to use DELETE.

jquery-ujs attaches a handler to links with the data-method attribute. When the link is clicked, the handler constructs an HTML form along with a hidden input that sets the _method parameter to the requested HTTP verb and submits the form rather than following the link.

Confirmation Dialogs

<%= form_for item, data: { confirm: 'Are you sure?' } %>

jquery-ujs attaches a handler to links or forms with the data-confirm attribute that displays a JavaScript confirmation dialog. The user can choose to proceed with or cancel the action.

<%= form.submit data: { disable_with: 'Submitting...' } %>

Users double click links and buttons all the time. This causes duplicate requests but is easily avoided thanks to jquery-ujs. Links and buttons that have a data-disable-with attribute get a click handler that disables the element and updates the text of the button to that which was provided in the data attribute and disables the button. If the action is performed via AJAX, the handler will re-enable the button and reset the text when the request completes.

AJAX Forms

<%= form_for item, remote: true %>

Adding remote: true to your form_for calls in Rails causes jquery-ujs to handle the form submission as an AJAX request. Your controller can handle the AJAX request and return JavaScript to be executed in the response. Thanks to jquery-ujs and Rails’ respond_with, setting remote: true is likely the quickest way to get your Rails application making AJAX requests. The unobtrusive nature of the implementation makes it simple to support both AJAX and standard requests at the same time.

AJAX File Uploads

Browsers do not natively support AJAX file uploads. If you have an AJAX form that contains a populated file input, jquery-ujs will fire the ajax:aborted:file event. If this event is not stopped by an event handler, the AJAX submission will be aborted and the form will submit as a normal form.

Remoteipart is one Rails gem that hooks into this event to enable AJAX file uploads.

Required Field Validation

HTML5 added the ability to mark an input as required. Browsers with full support for this feature will stop form submission and add browser-specific styling to the inputs that are required but not yet provided. jquery-ujs adds a polyfill that brings this behavior, minus the styling, to all JavaScript-enabled browsers. There’s no default styling provided by the polyfill, which means users of impacted browsers may be puzzled as to why the form will not submit. There’s an ongoing discussion about the appropriateness of this polyfill.

You can opt-out of this behavior by setting the “novalidate” attribute on your form. This will cause both the jquery-ujs polyfill and browsers with native support to skip HTML5 input validation. Given both the potential for confusion in browsers without native support and the fact that browsers with native support apply styles that may clash with your site design, handling validation is probably better left up to each developer.

Cross-Site Request Forgery Protection

Cross-Site Request Forgery (CSRF) is an attack wherein the attacker tricks the user into submitting a request to an application the user is likely already authenticated to. The user may think he’s simply signing up for an email newsletter, but the attacker controlling that sign up form is actually turning that into a request to post a status to some other website, using the users preexisting session.

Rails has built in protection which requires a token only available on your actual site to accompany every POST, PUT, or DELETE request. In collaboration with <%= csrf_meta_tags %> in your application layout HEAD, jquery-ujs augments this protection by adding the CSRF token to a header on outgoing AJAX requests.

jquery-ujs also updates the CSRF token on all non-AJAX forms on page load, which may be out-of-date if the form was rendered from a fragment cache.

Extensibility

jquery-ujs exposes its functions in the $.rails namespace and fires many events when submitting AJAX forms. jquery-ujs behavior can be customized by overriding these methods or handling the appropriate events. We’ve already seen Remoteipart as an example of custom event handling. There also exist several gems that override $.rails.allowAction to replace JavaScript confirmation dialogs with application-specific modal dialogs.