Turboboost: Enhanced AJAX handling for Rails apps

Update 2/23/14

This gem was renamed from turboforms to turboboost for a couple reasons:

  • A simpler, unmaintained gem named turboforms already exists.
  • The gem now provides enhanced AJAX handling to links and other non-form elements.

I’ve added some scoped response rendering options so you can do convenient JavaScript rendering in your controller actions like:

1
2
3
respond_to do |format|
  format.js { render partial: 'task', object: @task, prepend: "#todo-list" }
end

Check out turboboost on Github for more.


Lately I’ve been working on a Rails app that has been leveraging Turbolinks to really speed up the app experience. As I’ve talked about before, you do have to follow some JavaScript conventions for your app to be Turbolink-friendly, but such is the case for any performance-oriented JavaScript workflow. The sweet thing about Turbolinks is you get to hold close your dear bag of Rails tricks.

In our app though, there are lots of forms that send POST and PUT requests. Every time a form gets submitted, the browser would wait for a response and then follow the success redirection or render the invalid response with a full-page reload. Either way, the screen would flash and the scripts and styles would need to get reloaded in a very non-turbo way.

Sure, the jQuery unobtrusive scripting adapter provided by jquery-rails gives us access to binding to AJAX responses, but simply dumping a success or error response into the body doesn’t play nice with Turbolinks ability to preserve the browser’s state and history at these checkpoints of user activity. In search for a seemless solution, turboboost was born.

Design & Behavior

In order to bring AJAX control over our app’s forms in a Turbolinks compatible way, I defined some assumptions:

  • For GET requests, Turbolinks should visit the form’s action URL with the serialized data appended to it as a query string. This preserves navigable history states for things like live-updating search filter forms.

  • For other request types (POST, PUT, PATCH, DELETE, etc), an AJAX request should hit the Rails controllers. Then:

    • If the response has a redirect_to declaration, do not reload. Instead, visit that route with Turbolinks.
    • If there is an error, don’t visit anything with Turbolinks. Instead, the errors will be sent through the global document event turboboost:error. Optionally, the errors can be prepended to the form as HTML.
  • Turboboost only works on forms that you define with turboboost: true in your Rails form helper options (ex: form_for @post, turboboost: true do |f| ...) or manually with a data-turboboost attribute.

  • When a Turboform has an AJAX request in process, do sensible things like disable that form’s submit button.

These resulted in substantially faster loading upon successful form submissions with redirects, as only the body is swapped out. As a bonus, since failed form submissions are caught and immediately sent back to the app in JavaScript, you can cache your form views harder, since you don’t have to re-render the form view in an invalid state.

Example Usage

In its simplest server-side implementation, a controller action would look like this:

1
2
3
4
def create
  post = Post.create!(params[:post]) <- trigger exception if model is invalid
  redirect_to post, notice: 'Post was successfully created.'
end

If the post is invalid, a rescue_from handler will pass off the errors to JavaScript through the turboboost:error event. If the post is successfully created, the app will visit the post_url with Turbolinks if it was sent from a Turboform. Otherwise, the redirect will happen like normal.

You can also render the JSON error messages explicitly with the method render_turboboost_error_for(record), ex:

1
2
3
4
5
6
7
8
9
10
11
def create
  @post = Post.new(post_params)
  if @post.save
    redirect_to @post, notice: 'Post was successfully created.'
  else
    respond_to do |format|
      format.html { render 'new' }
      format.js { render_turboboost_error_for(@post) }
    end
  end
end

JavaScript options and events

By default, Turboboost will render returned errors with the same HTML structure used in the default Rails generators and prepend it to the form. The structure looks like this:

1
2
3
4
5
6
7
<div id="error_explanation">
  <ul>
    - for error in errors
      <li>= error</li>
    - end
  </ul>
</div>

Or this can be disabled and you can roll your own error handler:

1
2
3
4
Turboboost.insertErrors = false

$(document).on "turboboost:error", (e, errors) ->
  console.log(errors) # <- JSON array of errors messages

There is also a turboboost:success event that is trigger and passed a hash of the flash messages if they are present. You may also prevent redirecting on any Turboform by adding the attribute data-no-turboboost-redirect to your form element if you just want to handle the response and returned flash messages manually:

1
2
$(document).on "turboboost:success", (e, flash) ->
  console.log(flash) # -> {'notice': 'Post was successfully created.'}

More on Github

Turboboost is still in its infancy, but I think it has some legs. For install instructions and more info, follow development on Github. And as always, pull requests and comments are welcome.

Comments