DHH has long promoted the idea of using server generated javascript responses instead of using a client side javascript framework. In this post, I show how to use JS responses to write interactive apps.
I’m a big fan of using the simplest tools possible for a job and when I heard DHH promote the idea of using server generated JS responses as a way to avoid using a client side javascript framework I was intrigued. Was it really possible write the majority of my app’s code in Ruby? I’m not ideologically opposed to using a client side framework. If it’s the right tool for the job, then I won’t hesitate to use one. However, if there’s away for me to reduce my app’s complexity and deliver the features we’ve designed with one toolset, why not do it?!
Overview
To give the idea a thorough exploration, I built a small Rails 4.2 app called People. People1 looks a lot like the todo list apps used to test web frameworks except you manage people instead of todos.
It turns out it is easy. Here’s a gif showing the app in action. I wanted to see how well the approach allows me to:
- create, edit, delete, and show people
- show validations and flash messages
- edit a resource in place
Let’s walk through the process of writing the app.
Create the App
First, I create a new app called “people”. Once Rails does it’s thing, I use the
rails generators to scaffold a Person
model.
I don’t need everything rails generates by default, so I delete scaffold.scss
.
To make things look decent without too working hard (We’re prototyping here!), I
add gems Bootstrap and Font Awesome
to my gemfile. I also remove gem turbolinks
because I don’t really need it and I don’t want it
causing me any confusion.
bundle install
installs my new gems and I’m almost done my setup. I just
need to setup Bootstrap and Font Awesome.
Rename application.css
to application.scss
and write:
Finally, I add root 'people#index'
to routes.rb
, so the app has somewhere to
go when it’s opened.
Create the Responses
Now that I have the app setup, I can begin my experiment. I start by building
the index page because it’s the main page of the app. Just like the todo list
apps, I want a list of people. I want to use a form to add new people to my
list, so I create an instance of Person
.
I render the form on the index page(index.html.erb
) and display the list of people:
_form.html.erb
:
_list.html.erb
:
_person.htm.erb
:
In _form.html.erb
, I set the form to submit using AJAX by giving the option
remote: true
. This allows me to submit the form without leaving the page. To
handle this remote request, I need to modify people_controller.rb
to return
javascript. Because I don’t want to support any other type of request, I remove
format.html
and format.json
. In case I want to render a flash message, I set
it using flash.now
, which makes the message available in the current request.
I want to update the list of people and want the list to be sorted, so I write
@people = Person.all.order(first_name: :asc)
.
The form submission is received by the create
action, so I add
create.js.coffee
and in it I write the changes I want to make to the page. I
add a presence validation to person.rb
ensure the person has a first name.
The form builder gives the form an id of new_person
and I use it to prepend
the message. The Ah ha! moment I had while implementing the error display, is
that errors are just like any other data and can be rendered in whatever way
makes sense for the app. In the traditional request oriented approach, I would
normally render the new
view with @person
which would cause the errors to be
shown. In this case, the form is already on the page, so all I need to do is add
the errors html. In this case, a little javascript does the trick. If I kept the
error message html in my form partial, I could re-render the form with the
errors. I chose to extract the error html out into partial, so I can reuse it as
needed. If the submission is error free I remove the error html, in case it
exists, and render the list.
Edit in Place
I want to be able to edit the data I enter into the app. Everything up to this
point has been fairly straight forward. To create a new person, I fill out the
form and press “Add”. How do make it so I click the person’s name on the list
and the edit form opens? I didn’t want to add an edit link, so I dove into the
source of jquery_ujs
to see if I could reuse it’s code to make a remote call
when I click the person’s name.
Rails’ jquery_ujs
works behind the scenes to make it possible to submit the
new form without changing pages. It watches <form>
s and <a>
s for the data
attributes added by the view helpers when remote: true
is passed as an option.
These attributes provide the information needed to make the remote request.
I found my answer starting on line 131 of jquery_ujs in handleRemote
. handleRemote
is the
method jquery_ujs
class to make the ajax call when a remote link is clicked or
form is submitted and is exposed by the plugin.
The code above means that any element that’s not watched by the plugin with the
attributes href
, data-method
, and optionally data-params
can be used to
make AJAX calls. All I need to do is add the attributes with the correct values
to the element and call handleRemote
when I want the request sent. With this
knowledge, I add click handler on the person’s name.
Recall, _person.html.erb
renders each list element with those attributes. With
the click handler I make an AJAX call to the edit path of the person.
In edit.js.coffee
, I return a form (_edit_form.html.erb
) styled to fit in
the list element.
When the form is submitted, I respond with the javascript to update the list of people.
And there we have it! I created an app that used server generated javascript responses to prevent page changes and offer client side-esque interaction.
Conclusion
I now understand the value of DHH’s suggestion. It’s quite easy to make a dynamic app. My view code is reusable and I never have to implement a view in a javascript templating language. The remote requests follow the Rails patterns of new → create and edit → update. Now, I’m interested to see how ActionCable takes this approach to the next level.
For those wanting a closer look, the code is available on GitHub.
Tips and Tricks
- Make use of the classes and ids generated by the helper methods and form builder
- Use
dom_id
to make generating ids in your javascript/coffeescript easier - Use jQuery’s
on
method to attach event handlers to persistent elements in the DOM.
Tradeoffs
-
Your app is only as fast as the network
In most cases the network is fast. When it isn’t, you can use progress indicators to indicate the app is still alive.
-
Loss of network connection makes it impossible for the app to work
This is the same drawback as a “traditional” website. No internet means no pages are delivered, so it’s not really a problem unless you’re trying to create an offline app.
-
Download the completed app. ↩
Never miss another sales pitch article.
Sign up for our company newsletter. We write short articles about design, products, and being entrepreneurs. We don't write very often, so the risk of spam is pretty small.
Sign up for our newsletter