# 在 Rails 中使用 JavaScript
本文介紹 Rails 內建對 Ajax 和 JavaScript 等的支持,使用這些功能可以輕易的開發強大的 Ajax 程序。
讀完本文,你將學到:
* Ajax 基本知識;
* 非侵入式 JavaScript;
* 如何使用 Rails 內建的幫助方法;
* 如何在服務器端處理 Ajax;
* Turbolinks 簡介;
### Chapters
1. [Ajax 簡介](#ajax-%E7%AE%80%E4%BB%8B)
2. [非侵入式 JavaScript](#%E9%9D%9E%E4%BE%B5%E5%85%A5%E5%BC%8F-javascript)
3. [內建的幫助方法](#%E5%86%85%E5%BB%BA%E7%9A%84%E5%B8%AE%E5%8A%A9%E6%96%B9%E6%B3%95)
* [`form_for`](#form_for)
* [`form_tag`](#form_tag)
* [`link_to`](#link_to)
* [`button_to`](#button_to)
4. [服務器端處理](#%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%AB%AF%E5%A4%84%E7%90%86)
* [一個簡單的例子](#%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E4%BE%8B%E5%AD%90)
5. [Turbolinks](#turbolinks)
* [Turbolinks 的工作原理](#turbolinks-%E7%9A%84%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86)
* [頁面內容變更事件](#%E9%A1%B5%E9%9D%A2%E5%86%85%E5%AE%B9%E5%8F%98%E6%9B%B4%E4%BA%8B%E4%BB%B6)
6. [其他資源](#%E5%85%B6%E4%BB%96%E8%B5%84%E6%BA%90)
### 1 Ajax 簡介
在理解 Ajax 之前,要先知道網頁瀏覽器常規的工作原理。
在瀏覽器的地址欄中輸入 `http://localhost:3000` 后,瀏覽器(客戶端)會向服務器發起一個請求。然后瀏覽器會處理響應,獲取相關的資源文件,比如 JavaScript、樣式表、圖片,然后顯示頁面內容。點擊鏈接后發生的事情也是如此:獲取頁面內容,獲取資源文件,把全部內容放在一起,顯示最終的網頁。這個過程叫做“請求-響應循環”。
JavaScript 也可以向服務器發起請求,并處理響應。而且還能更新網頁中的內容。因此,JavaScript 程序員可以編寫只需更新部分內容的網頁,而不用從服務器獲取完整的頁面數據。這是一種強大的技術,我們稱之為 Ajax。
Rails 默認支持 CoffeeScript,后文所有的示例都用 CoffeeScript 編寫。本文介紹的技術,在普通的 JavaScript 中也可使用。
例如,下面這段 CoffeeScript 代碼使用 jQuery 發起一個 Ajax 請求:
```
$.ajax(url: "/test").done (html) ->
$("#results").append html
```
這段代碼從 `/test` 地址上獲取數據,然后把結果附加到 `div#results`。
Rails 內建了很多使用這種技術開發程序的功能,基本上無需自己動手編寫上述代碼。后文介紹 Rails 如何為開發這種程序提供幫助,不過都構建在這種簡單的技術之上。
### 2 非侵入式 JavaScript
Rails 使用一種叫做“非侵入式 JavaScript”(Unobtrusive JavaScript)的技術把 JavaScript 應用到 DOM 上。非侵入式 JavaScript 是前端開發社區推薦使用的方法,但有些教程可能會使用其他方式。
下面是編寫 JavaScript 最簡單的方式,你可能見過,這叫做“行間 JavaScript”:
```
<a href="#" onclick="this.style.backgroundColor='#990000'">Paint it red</a>
```
點擊鏈接后,鏈接的背景會變成紅色。這種用法的問題是,如果點擊鏈接后想執行大量代碼怎么辦?
```
<a href="#" onclick="this.style.backgroundColor='#009900';this.style.color='#FFFFFF';">Paint it green</a>
```
太別扭了,不是嗎?我們可以把處理點擊的代碼定義成一個函數,用 CoffeeScript 編寫如下:
```
paintIt = (element, backgroundColor, textColor) ->
element.style.backgroundColor = backgroundColor
if textColor?
element.style.color = textColor
```
然后在頁面中這么做:
```
<a href="#" onclick="paintIt(this, '#990000')">Paint it red</a>
```
這種方法好點兒,但是如果很多鏈接需要同樣的效果該怎么辦呢?
```
<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>
```
非常不符合 DRY 原則。為了解決這個問題,我們可以使用“事件”。在鏈接上添加一個 `data-*` 屬性,然后把處理程序綁定到擁有這個屬性的點擊事件上:
```
paintIt = (element, backgroundColor, textColor) ->
element.style.backgroundColor = backgroundColor
if textColor?
element.style.color = textColor
$ ->
$("a[data-background-color]").click ->
backgroundColor = $(this).data("background-color")
textColor = $(this).data("text-color")
paintIt(this, backgroundColor, textColor)
```
```
<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>
```
我們把這種方法稱為“非侵入式 JavaScript”,因為 JavaScript 代碼不再和 HTML 混用。我們把兩中代碼完全分開,這么做易于修改功能。我們可以輕易地把這種效果應用到其他鏈接上,只要添加相應的 `data` 屬性就行。所有 JavaScript 代碼都可以放在一個文件中,進行壓縮,每個頁面都使用這個 JavaScript 文件,因此只在第一次請求時加載,后續請求會直接從緩存中讀取。“非侵入式 JavaScript”帶來的好處太多了。
Rails 團隊極力推薦使用這種方式編寫 CoffeeScript 和 JavaScript,而且你會發現很多代碼庫都沿用了這種方式。
### 3 內建的幫助方法
Rails 提供了很多視圖幫助方法協助你生成 HTML,如果想在元素上實現 Ajax 效果也沒問題。
因為使用的是非侵入式 JavaScript,所以 Ajax 相關的幫助方法其實分成兩部分,一部分是 JavaScript 代碼,一部分是 Ruby 代碼。
[rails.js](https://github.com/rails/jquery-ujs/blob/master/src/rails.js) 提供 JavaScript 代碼,常規的 Ruby 視圖幫助方法用來生成 DOM 標簽。rails.js 中的 CoffeeScript 會監聽這些屬性,執行相應的處理程序。
#### 3.1 `form_for`
[`form_for`](http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for) 方法協助編寫表單,可指定 `:remote` 選項,用法如下:
```
<%= form_for(@post, remote: true) do |f| %>
...
<% end %>
```
生成的 HTML 如下:
```
<form accept-charset="UTF-8" action="/posts" class="new_post" data-remote="true" id="new_post" method="post">
...
</form>
```
注意 `data-remote="true"` 屬性,現在這個表單不會通過常規的提交按鈕方式提交,而是通過 Ajax 提交。
或許你并不需要一個只能填寫內容的表單,而是想在表單提交成功后做些事情。為此,我們要綁定到 `ajax:success` 事件上。處理表單提交失敗的程序要綁定到 `ajax:error` 事件上。例如:
```
$(document).ready ->
$("#new_post").on("ajax:success", (e, data, status, xhr) ->
$("#new_post").append xhr.responseText
).on "ajax:error", (e, xhr, status, error) ->
$("#new_post").append "<p>ERROR</p>"
```
顯然你需要的功能比這要復雜,上面的例子只是個入門。關于事件的更多內容請閱讀 [jquery-ujs 的維基](https://github.com/rails/jquery-ujs/wiki/ajax)。
#### 3.2 `form_tag`
[`form_tag`](http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-form_tag) 方法的功能和 `form_for` 類似,也可指定 `:remote` 選項,如下所示:
```
<%= form_tag('/posts', remote: true) do %>
...
<% end %>
```
生成的 HTML 如下:
```
<form accept-charset="UTF-8" action="/posts" data-remote="true" method="post">
...
</form>
```
其他用法都和 `form_for` 一樣。詳細介紹參見文檔。
#### 3.3 `link_to`
[`link_to`](http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to) 方法用來生成鏈接,可以指定 `:remote`,用法如下:
```
<%= link_to "a post", @post, remote: true %>
```
生成的 HTML 如下:
```
<a href="/posts/1" data-remote="true">a post</a>
```
綁定的 Ajax 事件和 `form_for` 方法一樣。下面舉個例子。加入有一個文章列表,我們想只點擊一個鏈接就刪除所有文章,視圖代碼如下:
```
<%= link_to "Delete post", @post, remote: true, method: :delete %>
```
CoffeeScript 代碼如下:
```
$ ->
$("a[data-remote]").on "ajax:success", (e, data, status, xhr) ->
alert "The post was deleted."
```
#### 3.4 `button_to`
[`button_to`](http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-button_to) 方法用來生成按鈕,可以指定 `:remote` 選項,用法如下:
```
<%= button_to "A post", @post, remote: true %>
```
生成的 HTML 如下:
```
<form action="/posts/1" class="button_to" data-remote="true" method="post">
<div><input type="submit" value="A post"></div>
</form>
```
因為生成的就是一個表單,所以 `form_for` 的全部信息都適用于這里。
### 4 服務器端處理
Ajax 不僅需要編寫客戶端代碼,服務器端也要做處理。Ajax 請求一般不返回 HTML,而是 JSON。下面詳細介紹處理過程。
#### 4.1 一個簡單的例子
假設在網頁中要顯示一系列用戶,還有一個新建用戶的表單,控制器的 `index` 動作如下所示:
```
class UsersController < ApplicationController
def index
@users = User.all
@user = User.new
end
# ...
```
`index` 動作的視圖(`app/views/users/index.html.erb`)如下:
```
<b>Users</b>
<ul id="users">
<%= render @users %>
</ul>
<br>
<%= form_for(@user, remote: true) do |f| %>
<%= f.label :name %><br>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
```
`app/views/users/_user.html.erb` 局部視圖如下:
```
<li><%= user.name %></li>
```
`index` 動作的上部顯示用戶,下部顯示新建用戶的表單。
下部的表單會調用 `UsersController` 的 `create` 動作。因為表單的 `remote` 屬性為 `true`,所以發往 `UsersController` 的是 Ajax 請求,使用 JavaScript 處理。要想處理這個請求,控制器的 `create` 動作應該這么寫:
```
# 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
```
注意,在 `respond_to` 的代碼塊中使用了 `format.js`,這樣控制器才能處理 Ajax 請求。然后還要新建 `app/views/users/create.js.erb` 視圖文件,編寫發送響應以及在客戶端執行的 JavaScript 代碼。
```
$("<%= escape_javascript(render @user) %>").appendTo("#users");
```
### 5 Turbolinks
Rails 4 提供了 [Turbolinks gem](https://github.com/rails/turbolinks),這個 gem 可用于大多數程序,加速頁面渲染。
#### 5.1 Turbolinks 的工作原理
Turbolinks 為頁面中所有的 `<a>` 元素添加了一個點擊事件處理程序。如果瀏覽器支持 [PushState](http://dwz.cn/pushstate),Turbolinks 會發起 Ajax 請求,處理響應,然后使用響應主體替換原始頁面的整個 `<body>` 元素。最后,使用 PushState 技術更改頁面的 URL,讓新頁面可刷新,并且有個精美的 URL。
要想使用 Turbolinks,只需將其加入 `Gemfile`,然后在 `app/assets/javascripts/application.js` 中加入 `//= require turbolinks` 即可。
如果某個鏈接不想使用 Turbolinks,可以在鏈接中添加 `data-no-turbolink` 屬性:
```
<a href="..." data-no-turbolink>No turbolinks here</a>.
```
#### 5.2 頁面內容變更事件
編寫 CoffeeScript 代碼時,經常需要在頁面加載時做一些事情。在 jQuery 中,我們可以這么寫:
```
$(document).ready ->
alert "page has loaded!"
```
不過,因為 Turbolinks 改變了常規的頁面加載流程,所以不會觸發這個事件。如果編寫了類似上面的代碼,要將其修改為:
```
$(document).on "page:change", ->
alert "page has loaded!"
```
其他可用事件等詳細信息,請參閱 [Turbolinks 的說明文件](https://github.com/rails/turbolinks/blob/master/README.md)。
### 6 其他資源
下面列出一些鏈接,可以幫助你進一步學習:
* [jquery-ujs 的維基](https://github.com/rails/jquery-ujs/wiki)
* [其他介紹 jquery-ujs 的文章](https://github.com/rails/jquery-ujs/wiki/External-articles)
* [Rails 3 遠程鏈接和表單權威指南](http://www.alfajango.com/blog/rails-3-remote-links-and-forms/)
* [Railscasts: Unobtrusive JavaScript](http://railscasts.com/episodes/205-unobtrusive-javascript)
* [Railscasts: Turbolinks](http://railscasts.com/episodes/390-turbolinks)
### 反饋
歡迎幫忙改善指南質量。
如發現任何錯誤,歡迎修正。開始貢獻前,可先行閱讀[貢獻指南:文檔](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation)。
翻譯如有錯誤,深感抱歉,歡迎 [Fork](https://github.com/ruby-china/guides/fork) 修正,或至此處[回報](https://github.com/ruby-china/guides/issues/new)。
文章可能有未完成或過時的內容。請先檢查 [Edge Guides](http://edgeguides.rubyonrails.org) 來確定問題在 master 是否已經修掉了。再上 master 補上缺少的文件。內容參考 [Ruby on Rails 指南準則](ruby_on_rails_guides_guidelines.html)來了解行文風格。
最后,任何關于 Ruby on Rails 文檔的討論,歡迎到 [rubyonrails-docs 郵件群組](http://groups.google.com/group/rubyonrails-docs)。
- Ruby on Rails 指南 (651bba1)
- 入門
- Rails 入門
- 模型
- Active Record 基礎
- Active Record 數據庫遷移
- Active Record 數據驗證
- Active Record 回調
- Active Record 關聯
- Active Record 查詢
- 視圖
- Action View 基礎
- Rails 布局和視圖渲染
- 表單幫助方法
- 控制器
- Action Controller 簡介
- Rails 路由全解
- 深入
- Active Support 核心擴展
- Rails 國際化 API
- Action Mailer 基礎
- Active Job 基礎
- Rails 程序測試指南
- Rails 安全指南
- 調試 Rails 程序
- 設置 Rails 程序
- Rails 命令行
- Rails 緩存簡介
- Asset Pipeline
- 在 Rails 中使用 JavaScript
- 引擎入門
- Rails 應用的初始化過程
- Autoloading and Reloading Constants
- 擴展 Rails
- Rails 插件入門
- Rails on Rack
- 個性化Rails生成器與模板
- Rails應用模版
- 貢獻 Ruby on Rails
- Contributing to Ruby on Rails
- API Documentation Guidelines
- Ruby on Rails Guides Guidelines
- Ruby on Rails 維護方針
- 發布記
- A Guide for Upgrading Ruby on Rails
- Ruby on Rails 4.2 發布記
- Ruby on Rails 4.1 發布記
- Ruby on Rails 4.0 Release Notes
- Ruby on Rails 3.2 Release Notes
- Ruby on Rails 3.1 Release Notes
- Ruby on Rails 3.0 Release Notes
- Ruby on Rails 2.3 Release Notes
- Ruby on Rails 2.2 Release Notes