Ever found yourself paralyzed trying to pick a JS framework?

Case Study:

Choosing Turbolinks over a JS framework

Hey Frey,

Have you ever found yourself starting a project but feeling paralyzed by trying to decide which JavaScript framework to use? There's tons of choices with new ones popping up every day, and everyone has their opinions. Not to mention, none of them are particularly easy to incorporate into a Rails application.

I've been working on a side project lately and found an interesting way to use Turbolinks to get it up and running quickly while still feeling like a modern, single page application.  

In this case study, I'll demonstrate how I used Turbolinks to get a real world application up and running in a just few weekends, and show you a few things you might not have know Turbolinks could do.

Why Turbolinks?

You might be thinking to yourself right now: "Turbolinks? But everyone hates that!" And while it's true that Turbolinks gets its fair share of hostility from the Rails community, when it's used right, it can be an extremely effective tool.

If you've read Efficient Rails, you know I dedicated three sections to Turbolinks. I did this not because I believe Turbolinks to be flawless pinnacle of engineering, but because I believe it gives you a modern, responsive-feeling app with relatively little work and frees you from the analysis paralysis associated with choosing a JavaScript framework.

Is Turbolinks a final solution? Certainly not for most apps. A more advanced JavaScript framework will afford more intricate interactions and even snappier page transitions. But Turbolinks allowed me to get a modern-feeling web app up and running in a few weekends and I'm going to show you how I did that.

What I built

For background, I changed jobs recently. Early in the recruiting process came the inevitable: "Can you send me your résumé?"

My résumé was dusty and needed updating. I wanted an easy way to create a good-looking, but simple résumé (none of those infographic, overwrought designs for me, thank you very much!).

And I wanted a way to get it reviewed by someone who knew what they were talking about. Sending it to friends and family typically got me a “looks great!” response, but I wanted some critical feedback from someone who knew the tech industry.

After some dead-end Googling, I built what I wished I had: Vanilla.

Using Turbolinks in Vanilla

As you probably know, Turbolinks comes pre-bundled in new Rails projects, so it didn't really take much to get started, it was simply there.

But, as you also probably know, it doesn't take long for the Turbolinks bugs to come creeping in. This is where most developers would simply rip it out and move on. However Turbolinks bugs typically follow similar patterns, and once you know how to deal with them, they're pretty easy to squash.

The most common bug you'll experience is jQuery plugins failing to load without a full page refresh. And this is just due to the nature of how Turbolinks works; when you click a link, it requests the page at that link, and replaces the contents of the <body> tag with what comes back from that request. Any JavaScript that was loaded in the <head> tag will not re-execute.

In Vanilla, I use a Wysiwyg editor plugin called Trumbowyg to allow users to add some basic formatting to certain sections of their résumés. If you follow the documentation on their site, you'll initialize the plugin like this:

Now, this will work once, but as soon as you navigate away, all of this initialization will be lost. One tactic to solve this problem is to use a drop-in fix like jquery.turbolinks which automatically reruns the JavaScript defined in the <head> tag between page loads.

I actually chose to go a more manual route and handle the reinitialization myself because I didn't need every line of JavaScript to be rerun. Here's what I came up with:

Notice that I define a function, initialize_wysiwyg, which will turn any textarea with the class .wysiwyg into a fancy text editor.

Then below that, I add an event listener to the document that binds to the 'turbolinks:load` event, and when it hears that event, runs the wysiwyg initialization logic. This way, you can navigate all around the app and whenever you hit a page that has a wysiwyg editor on it, it will be initialized.

Another trick is to attach things like click event listeners to the document itself, rather than the link or button where the event originates:

This way, event listeners defined on the first page load will continue to be valid. If you attach an event listener to a DOM element like a <button>, once that element is gone (ie after a Turbolinks page load), the event listener goes away with it. Since the document object never disappears, we can attach event listeners to it without worrying if they'll still be there after Turbolinks replaces the <body>.

More complex Turbolinks interactions

Alright so you might be thinking: "That's all well and good, but I need to do something more complex." I had the same thought, but before you rip Turbolinks out and spend a weekend bolting a JS framework into your app, it's worth considering building a version of that complex interaction using Turbolinks.

In Vanilla, I needed to  build a stateful multi-step wizard. This is a pretty common UX pattern you're no doubt familiar with. It's frequently seen on checkout and user onboarding flows due to its proven ability to improve funnel conversion rates.

 Here's the end result:

Now the tricky thing about this component is that one Rails route represents a single form, but it's split across multiple steps to improve the user experience. So I wouldn't be able to split the form across separate routes like /resumes/123/step_1.

The whole form needs to load in a single request, but only a single part of of it should be shown at a time. Once loaded, it also needs to allow the user to navigate forward and back through the various sections of the form.

I could have written a tangled mess of jQuery or found some off the shelf plugin that would have required significant configuration.

But instead, I worked within the bounds of Turbolinks. And with a few lines of JavaScript and a little CSS, I was able to accomplish exactly what I set out to build.

First, I divided the giant form up into sections, which are hidden by default, and shown when the .active class is applied to them. The following initialization code uses the url's hash parameter to find a section with an id matching the hash param and apply the active class to it.

This way, if someone reloads the page halfway through the form, we take them back to the same page they were on.

That will also take care of the links across the top of the wizard since they're simply links with a hash param (eg clicking the Education link will take you to /resumes/123/edit#education) and Turbolinks will make this transition feel very smooth.

Now we need a way to proceed to the next section, while saving the contents of the current section.

To do this, we need to make the form a remote form. That way, the "Next Section -->" button at the bottom of each section submits the form and creates/updates the résumé, but we can handle the response with JavaScript.

This JavaScript is executed in the remote controller actions (create.js.erb and update.js.erb):

It simply figures out what the current form section is and picks the next form section, changing the window's location to include the appropriate hash param. Turbolinks will intercept this location change event and make it a smooth transition. If it can't find a next form section (ie if we hit the last section), it will take us to the show page for the résumé.

Note that I consider this "quick and dirty" JavaScript since I didn't think anyone else would see it at the time :)

I'll probably find some ways to improve this going forward, but the important thing is that it works quite well and only took me an hour or so to build out. When time is at a premium, tradeoffs and compromises like this one can get your product to market that much quicker.

That's all for now. I hope I was able to convince you that Turbolinks is not evil and can in fact be quite useful when bootstrapping your project. Stay tuned for future Efficient Rails case studies as I continue to build out Vanilla.

Andrew @allenan_

PS... Vanilla is currently in private beta, but if you want to try it out to see how Turbolinks is working for me, you can use invite code EfficientRails, or click here.