2012-10-23 16:25:21 -04:00
Working with JavaScript in Rails
2012-10-23 16:08:20 -04:00
================================
2012-08-30 22:39:49 -04:00
This guide covers the built-in Ajax/JavaScript functionality of Rails (and
2012-10-23 16:21:02 -04:00
more); it will enable you to create rich and dynamic Ajax applications with
2012-11-29 17:25:02 -05:00
ease!
After reading this guide, you will know:
2012-08-30 22:39:49 -04:00
2012-12-07 12:50:09 -05:00
* The basics of Ajax.
2012-11-29 08:14:08 -05:00
* Unobtrusive JavaScript.
* How Rails' built-in helpers assist you.
2012-12-07 12:50:09 -05:00
* How to handle Ajax on the server side.
2012-11-29 08:14:08 -05:00
* The Turbolinks gem.
2012-08-30 22:39:49 -04:00
-------------------------------------------------------------------------------
2012-10-23 16:25:21 -04:00
An Introduction to Ajax
2012-08-30 22:39:49 -04:00
------------------------
2012-10-23 16:21:02 -04:00
In order to understand Ajax, you must first understand what a web browser does
2012-08-30 22:39:49 -04:00
normally.
When you type `http://localhost:3000` into your browser's address bar and hit
'Go,' the browser (your 'client') makes a request to the server. It parses the
response, then fetches all associated assets, like JavaScript files,
stylesheets and images. It then assembles the page. If you click a link, it
does the same process: fetch the page, fetch the assets, put it all together,
show you the results. This is called the 'request response cycle.'
JavaScript can also make requests to the server, and parse the response. It
also has the ability to update information on the page. Combining these two
powers, a JavaScript writer can make a web page that can update just parts of
itself, without needing to get the full page data from the server. This is a
2012-10-23 16:21:02 -04:00
powerful technique that we call Ajax.
2012-08-30 22:39:49 -04:00
Rails ships with CoffeeScript by default, and so the rest of the examples
in this guide will be in CoffeeScript. All of these lessons, of course, apply
to vanilla JavaScript as well.
2012-10-23 16:21:02 -04:00
As an example, here's some CoffeeScript code that makes an Ajax request using
2012-08-30 22:39:49 -04:00
the jQuery library:
2012-10-28 10:26:07 -04:00
```coffeescript
2012-08-30 22:39:49 -04:00
$.ajax(url: "/test").done (html) ->
$("#results").append html
```
This code fetches data from "/test", and then appends the result to the `div`
with an id of `results` .
Rails provides quite a bit of built-in support for building web pages with this
technique. You rarely have to write this code yourself. The rest of this guide
2013-01-12 22:37:39 -05:00
will show you how Rails can help you write websites in this way, but it's
2012-08-30 22:39:49 -04:00
all built on top of this fairly simple technique.
Unobtrusive JavaScript
-------------------------------------
Rails uses a technique called "Unobtrusive JavaScript" to handle attaching
JavaScript to the DOM. This is generally considered to be a best-practice
within the frontend community, but you may occasionally read tutorials that
demonstrate other ways.
Here's the simplest way to write JavaScript. You may see it referred to as
'inline JavaScript':
2012-10-28 10:26:07 -04:00
```html
2013-01-05 15:12:39 -05:00
< a href = "#" onclick = "this.style.backgroundColor='#990000'" > Paint it red< / a >
2012-08-30 22:39:49 -04:00
```
2013-01-05 15:12:39 -05:00
When clicked, the link background will become red. Here's the problem: what
happens when we have lots of JavaScript we want to execute on a click?
2012-08-30 22:39:49 -04:00
2012-10-28 10:26:07 -04:00
```html
2013-01-05 15:12:39 -05:00
< a href = "#" onclick = "this.style.backgroundColor='#009900';this.style.color='#FFFFFF';" > Paint it green< / a >
2012-08-30 22:39:49 -04:00
```
Awkward, right? We could pull the function definition out of the click handler,
and turn it into CoffeeScript:
2012-10-28 10:26:07 -04:00
```coffeescript
2013-01-05 15:12:39 -05:00
paintIt = (element, backgroundColor, textColor) ->
element.style.backgroundColor = backgroundColor
if textColor?
element.style.color = textColor
2012-08-30 22:39:49 -04:00
```
And then on our page:
2012-10-28 10:26:07 -04:00
```html
2013-01-05 15:12:39 -05:00
< a href = "#" onclick = "paintIt(this, '#990000')" > Paint it red< / a >
2012-08-30 22:39:49 -04:00
```
That's a little bit better, but what about multiple links that have the same
effect?
2012-10-28 10:26:07 -04:00
```html
2013-01-05 15:12:39 -05:00
< a href = "#" onclick = "paintIt(this, '#990000')" > Paint it red< / a >
< a href = "#" onclick = "paintIt(this, '#009900', '#FFFFFF')" > Paint it green< / a >
< a href = "#" onclick = "paintIt(this, '#000099', '#FFFFFF')" > Paint it blue< / a >
2012-08-30 22:39:49 -04:00
```
Not very DRY, eh? We can fix this by using events instead. We'll add a `data-*`
attribute to our link, and then bind a handler to the click event of every link
that has that attribute:
2012-10-28 10:26:07 -04:00
```coffeescript
2013-01-05 15:12:39 -05:00
paintIt = (element, backgroundColor, textColor) ->
element.style.backgroundColor = backgroundColor
if textColor?
element.style.color = textColor
$ ->
2014-03-07 13:46:27 -05:00
$("a[data-background-color]").click (e) ->
e.preventDefault()
2013-01-05 15:12:39 -05:00
backgroundColor = $(this).data("background-color")
textColor = $(this).data("text-color")
paintIt(this, backgroundColor, textColor)
```
```html
< a href = "#" data-background-color = "#990000" > Paint it red< / a >
< a href = "#" data-background-color = "#009900" data-text-color = "#FFFFFF" > Paint it green< / a >
< a href = "#" data-background-color = "#000099" data-text-color = "#FFFFFF" > Paint it blue< / a >
2012-08-30 22:39:49 -04:00
```
We call this 'unobtrusive' JavaScript because we're no longer mixing our
JavaScript into our HTML. We've properly separated our concerns, making future
change easy. We can easily add behavior to any link by adding the data
attribute. We can run all of our JavaScript through a minimizer and
concatenator. We can serve our entire JavaScript bundle on every page, which
means that it'll get downloaded on the first page load and then be cached on
every page after that. Lots of little benefits really add up.
The Rails team strongly encourages you to write your CoffeeScript (and
JavaScript) in this style, and you can expect that many libraries will also
follow this pattern.
Built-in Helpers
----------------------
Rails provides a bunch of view helper methods written in Ruby to assist you
2012-10-23 16:21:02 -04:00
in generating HTML. Sometimes, you want to add a little Ajax to those elements,
2012-08-30 22:39:49 -04:00
and Rails has got your back in those cases.
2012-10-23 16:21:02 -04:00
Because of Unobtrusive JavaScript, the Rails "Ajax helpers" are actually in two
2012-08-30 22:39:49 -04:00
parts: the JavaScript half and the Ruby half.
2012-11-02 16:26:16 -04:00
2012-08-30 22:39:49 -04:00
[rails.js ](https://github.com/rails/jquery-ujs/blob/master/src/rails.js )
provides the JavaScript half, and the regular Ruby view helpers add appropriate
tags to your DOM. The CoffeeScript in rails.js then listens for these
attributes, and attaches appropriate handlers.
### form_for
[`form_for` ](http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for )
2012-10-21 09:49:47 -04:00
is a helper that assists with writing forms. `form_for` takes a `:remote`
2012-08-30 22:39:49 -04:00
option. It works like this:
2012-10-28 10:26:07 -04:00
```erb
2014-05-21 21:47:18 -04:00
< %= form_for(@article, remote: true) do |f| %>
2012-08-30 22:39:49 -04:00
...
< % end %>
```
This will generate the following HTML:
2012-10-28 10:26:07 -04:00
```html
2014-05-21 21:47:18 -04:00
< form accept-charset = "UTF-8" action = "/articles" class = "new_article" data-remote = "true" id = "new_article" method = "post" >
2012-08-30 22:39:49 -04:00
...
< / form >
```
2013-12-14 06:22:04 -05:00
Note the `data-remote="true"` . Now, the form will be submitted by Ajax rather
2012-08-30 22:39:49 -04:00
than by the browser's normal submit mechanism.
You probably don't want to just sit there with a filled out `<form>` , though.
You probably want to do something upon a successful submission. To do that,
bind to the `ajax:success` event. On failure, use `ajax:error` . Check it out:
2012-10-28 10:26:07 -04:00
```coffeescript
2012-08-30 22:39:49 -04:00
$(document).ready ->
2014-05-21 21:47:18 -04:00
$("#new_article").on("ajax:success", (e, data, status, xhr) ->
$("#new_article").append xhr.responseText
2014-02-13 05:37:34 -05:00
).on "ajax:error", (e, xhr, status, error) ->
2014-05-21 21:47:18 -04:00
$("#new_article").append "< p > ERROR< / p > "
2012-08-30 22:39:49 -04:00
```
Obviously, you'll want to be a bit more sophisticated than that, but it's a
2013-08-09 18:35:14 -04:00
start. You can see more about the events [in the jquery-ujs wiki ](https://github.com/rails/jquery-ujs/wiki/ajax ).
2012-08-30 22:39:49 -04:00
### form_tag
[`form_tag` ](http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-form_tag )
is very similar to `form_for` . It has a `:remote` option that you can use like
this:
2012-10-28 10:26:07 -04:00
```erb
2014-05-21 21:47:18 -04:00
< %= form_tag('/articles', remote: true) do %>
2013-12-14 06:22:04 -05:00
...
< % end %>
```
This will generate the following HTML:
```html
2014-05-21 21:47:18 -04:00
< form accept-charset = "UTF-8" action = "/articles" data-remote = "true" method = "post" >
2013-12-14 06:22:04 -05:00
...
< / form >
2012-08-30 22:39:49 -04:00
```
Everything else is the same as `form_for` . See its documentation for full
details.
### link_to
[`link_to` ](http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to )
2012-10-21 09:49:47 -04:00
is a helper that assists with generating links. It has a `:remote` option you
2012-08-30 22:39:49 -04:00
can use like this:
2012-10-28 10:26:07 -04:00
```erb
2014-05-21 21:47:18 -04:00
< %= link_to "an article", @article , remote: true %>
2012-08-30 22:39:49 -04:00
```
which generates
2012-10-28 10:26:07 -04:00
```html
2014-05-21 21:47:18 -04:00
< a href = "/articles/1" data-remote = "true" > an article< / a >
2012-08-30 22:39:49 -04:00
```
2012-10-23 16:21:02 -04:00
You can bind to the same Ajax events as `form_for` . Here's an example. Let's
2014-05-21 21:47:18 -04:00
assume that we have a list of articles that can be deleted with just one
2013-01-05 15:12:39 -05:00
click. We would generate some HTML like this:
2012-08-30 22:39:49 -04:00
2012-10-28 10:26:07 -04:00
```erb
2014-05-21 21:47:18 -04:00
< %= link_to "Delete article", @article , remote: true, method: :delete %>
2012-10-28 10:15:35 -04:00
```
2012-08-30 22:39:49 -04:00
and write some CoffeeScript like this:
2012-10-28 10:26:07 -04:00
```coffeescript
2013-01-05 15:12:39 -05:00
$ ->
$("a[data-remote]").on "ajax:success", (e, data, status, xhr) ->
2014-05-21 21:47:18 -04:00
alert "The article was deleted."
2012-08-30 22:39:49 -04:00
```
### button_to
[`button_to` ](http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-button_to ) is a helper that helps you create buttons. It has a `:remote` option that you can call like this:
2012-10-28 10:26:07 -04:00
```erb
2014-05-21 21:47:18 -04:00
< %= button_to "An article", @article , remote: true %>
2012-08-30 22:39:49 -04:00
```
this generates
2012-10-28 10:26:07 -04:00
```html
2014-05-21 21:47:18 -04:00
< form action = "/articles/1" class = "button_to" data-remote = "true" method = "post" >
< div > < input type = "submit" value = "An article" > < / div >
2012-08-30 22:39:49 -04:00
< / form >
```
Since it's just a `<form>` , all of the information on `form_for` also applies.
2012-10-23 16:25:21 -04:00
Server-Side Concerns
2012-08-30 22:39:49 -04:00
--------------------
2012-10-23 16:21:02 -04:00
Ajax isn't just client-side, you also need to do some work on the server
side to support it. Often, people like their Ajax requests to return JSON
2012-08-30 22:39:49 -04:00
rather than HTML. Let's discuss what it takes to make that happen.
### A Simple Example
Imagine you have a series of users that you would like to display and provide a
form on that same page to create a new user. The index action of your
controller looks like this:
2012-10-28 10:26:07 -04:00
```ruby
2012-08-30 22:39:49 -04:00
class UsersController < ApplicationController
def index
@users = User.all
@user = User.new
end
# ...
```
The index view (`app/views/users/index.html.erb`) contains:
2012-10-28 10:26:07 -04:00
```erb
2012-08-30 22:39:49 -04:00
< b > Users< / b >
< ul id = "users" >
2013-06-17 04:21:59 -04:00
< %= render @users %>
2012-08-30 22:39:49 -04:00
< / ul >
< br >
< %= form_for(@user, remote: true) do |f| %>
< %= f.label :name %>< br >
< %= f.text_field :name %>
< %= f.submit %>
< % end %>
```
The `app/views/users/_user.html.erb` partial contains the following:
2012-10-28 10:26:07 -04:00
```erb
2012-08-30 22:39:49 -04:00
< li > < %= user.name %>< / li >
```
The top portion of the index page displays the users. The bottom portion
provides a form to create a new user.
2013-12-14 06:22:04 -05:00
The bottom form will call the `create` action on the `UsersController` . Because
2012-08-30 22:39:49 -04:00
the form's remote option is set to true, the request will be posted to the
2013-12-14 06:22:04 -05:00
`UsersController` as an Ajax request, looking for JavaScript. In order to
serve that request, the `create` action of your controller would look like
2012-08-30 22:39:49 -04:00
this:
2012-10-28 10:26:07 -04:00
```ruby
2012-08-30 22:39:49 -04:00
# app/controllers/users_controller.rb
# ......
def create
@user = User.new(params[:user])
respond_to do |format|
if @user .save
format.html { redirect_to @user , notice: 'User was successfully created.' }
format.js {}
format.json { render json: @user , status: :created, location: @user }
else
format.html { render action: "new" }
format.json { render json: @user .errors, status: :unprocessable_entity }
end
end
end
```
2012-11-02 16:46:30 -04:00
Notice the format.js in the `respond_to` block; that allows the controller to
2012-10-23 16:21:02 -04:00
respond to your Ajax request. You then have a corresponding
2012-08-30 22:39:49 -04:00
`app/views/users/create.js.erb` view file that generates the actual JavaScript
code that will be sent and executed on the client side.
2012-10-28 10:26:07 -04:00
```erb
2012-08-30 22:39:49 -04:00
$("< %= escape_javascript(render @user ) %>").appendTo("#users");
```
Turbolinks
----------
Rails 4 ships with the [Turbolinks gem ](https://github.com/rails/turbolinks ).
2012-10-23 16:21:02 -04:00
This gem uses Ajax to speed up page rendering in most applications.
2012-08-30 22:39:49 -04:00
2012-10-23 16:25:21 -04:00
### How Turbolinks Works
2012-08-30 22:39:49 -04:00
Turbolinks attaches a click handler to all `<a>` on the page. If your browser
supports
[PushState ](https://developer.mozilla.org/en-US/docs/DOM/Manipulating_the_browser_history#The_pushState(\ ).C2.A0method),
2012-10-23 16:21:02 -04:00
Turbolinks will make an Ajax request for the page, parse the response, and
2012-08-30 22:39:49 -04:00
replace the entire `<body>` of the page with the `<body>` of the response. It
will then use PushState to change the URL to the correct one, preserving
refresh semantics and giving you pretty URLs.
The only thing you have to do to enable Turbolinks is have it in your Gemfile,
and put `//= require turbolinks` in your CoffeeScript manifest, which is usually
`app/assets/javascripts/application.js` .
If you want to disable Turbolinks for certain links, add a `data-no-turbolink`
attribute to the tag:
2012-10-28 10:26:07 -04:00
```html
2012-08-30 22:39:49 -04:00
< a href = "..." data-no-turbolink > No turbolinks here< / a > .
```
2012-10-23 16:25:21 -04:00
### Page Change Events
2012-08-30 22:39:49 -04:00
When writing CoffeeScript, you'll often want to do some sort of processing upon
page load. With jQuery, you'd write something like this:
2012-10-28 10:26:07 -04:00
```coffeescript
2012-08-30 22:39:49 -04:00
$(document).ready ->
alert "page has loaded!"
```
However, because Turbolinks overrides the normal page loading process, the
event that this relies on will not be fired. If you have code that looks like
this, you must change your code to do this instead:
2012-10-28 10:26:07 -04:00
```coffeescript
2012-08-30 22:39:49 -04:00
$(document).on "page:change", ->
alert "page has loaded!"
```
For more details, including other events you can bind to, check out [the
Turbolinks
2012-11-02 16:46:30 -04:00
README](https://github.com/rails/turbolinks/blob/master/README.md).
2012-08-30 22:39:49 -04:00
2012-10-23 16:25:21 -04:00
Other Resources
2012-08-30 22:39:49 -04:00
---------------
Here are some helpful links to help you learn even more:
* [jquery-ujs wiki ](https://github.com/rails/jquery-ujs/wiki )
* [jquery-ujs list of external articles ](https://github.com/rails/jquery-ujs/wiki/External-articles )
* [Rails 3 Remote Links and Forms: A Definitive Guide ](http://www.alfajango.com/blog/rails-3-remote-links-and-forms/ )
* [Railscasts: Unobtrusive JavaScript ](http://railscasts.com/episodes/205-unobtrusive-javascript )
2013-05-28 08:19:22 -04:00
* [Railscasts: Turbolinks ](http://railscasts.com/episodes/390-turbolinks )