在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ Ruby/ 3.3 視圖中的 AJAX 交互
寫在后面
寫在前面
第六章 Rails 的配置及部署
第四章 Rails 中的模型
4.4 模型中的校驗(yàn)(Validates)
1.3 用戶界面(UI)設(shè)計(jì)
6.5 生產(chǎn)環(huán)境部署
3.2 表單
4.3 模型中的關(guān)聯(lián)關(guān)系(Relations)
4.5 模型中的回調(diào)(Callback)
第五章 Rails 中的控制器
4.2 深入模型查詢
5.2 控制器中的方法
6.2 緩存
3.4 模板引擎的使用
6.4 I18n
第一章 Ruby on Rails 概述
6.6 常用 Gem
1.2 Rails 文件簡介
2.2 REST 架構(gòu)
2.3 深入路由(routes)
第三章 Rails 中的視圖
6.3 異步任務(wù)及郵件發(fā)送
第二章 Rails 中的資源
3.3 視圖中的 AJAX 交互

3.3 視圖中的 AJAX 交互

概要:

本課時通過對商品的添加、編輯和刪除,講解視圖中如何使用 UJS,jQuery 和 JSON,實(shí)現(xiàn)無刷新情況下的頁面更新。

知識點(diǎn):

  1. jQuery
  2. UJS
  3. AJAX
  4. JSON

正文

上一節(jié),我們講解了 Rails 中的視圖(View),我們再回顧一下這個視圖是如何產(chǎn)生的:我們向服務(wù)器發(fā)起一個請求,服務(wù)器返給我們結(jié)果,查看源代碼,它是一篇 HTML 的代碼。

我們每次請求一個地址,都會給我們完整的 HTML 結(jié)果,對于內(nèi)容較少的網(wǎng)頁,傳輸起來還是很快的,但是對于內(nèi)容多的網(wǎng)頁,大篇的結(jié)果自然會拖慢頁面顯示。

當(dāng)我們?yōu)g覽頁面的時候,并不期望總是刷新整個頁面,因?yàn)樗鼪]必要?,F(xiàn)在我們有 ajax 技術(shù),可以只加載和顯示部分頁面代碼。舉個簡單的例子:當(dāng)我們提交了一條評論,頁面上自動顯示出我們提交的評論內(nèi)容。我們點(diǎn)擊購買按鈕,頁面上就提示我們購物車?yán)镌黾恿艘粋€商品。而這些,都不必要刷新整個頁面。

ajax 是 Asynchronous Javascript And XML 的縮寫,含義是異步的 js 和 XML 交互技術(shù)。XML,可擴(kuò)展標(biāo)記語言,我們使用的 HTML 是基于其發(fā)展起來的。

下面我們看下 Rails 是如何把 ajax 技術(shù)應(yīng)用在視圖(View)中的。

3.3.1 ujs

我們在 Gemfile 中已經(jīng)使用了 gem 'jquery-rails' 這個 Gem,它可以讓我們在 application.js 中增加這兩行:

//= require jquery
//= require jquery_ujs

jQuery 是一個輕量級的 js 庫,可以方便的處理HTML,事件(Event),動態(tài)效果,為頁面提供 ajax 交互。jQuery 有很完善的文檔及演示代碼,以及大量的插件。

Rails 使用一種叫 ujs(Unobtrusive JavaScript)的技術(shù),將 js 應(yīng)用到 DOM 上。我們來看一個例子:

http://wiki.jikexueyuan.com/project/rails-practice/images/chapter_3/5.png" alt="" />

我們已經(jīng)給刪除連接增加了兩個屬性:

<%= link_to "刪除", product, :method => :delete, :data => { :confirm => "點(diǎn)擊確定繼續(xù)" } %>

來看看我們的 HTML:

<a data-confirm="點(diǎn)擊確定繼續(xù)" rel="nofollow" data-method="delete" href="/products/1">刪除</a>

輔助方法 link_to 使用了 :data => { :confirm => "點(diǎn)擊確定繼續(xù)" } 這個屬性,為我們添加了 data-confirm="點(diǎn)擊確定繼續(xù)" 這樣的 HTML 代碼,之后 ujs 將它處理成一個彈出框。

在刪除按鈕上,還有 :method => :delete 屬性,這為我們的連接上增加了 data-method="delete" 屬性,這樣,ujs 會把這個點(diǎn)擊動作,會發(fā)送一個 delete 請求刪除資源,這是符合 REST 要求的。

我們可以給 a 標(biāo)簽增加 data-disable-with 屬性,當(dāng)點(diǎn)擊它的時候,使它禁用,并提示文字信息。這樣可以防止用戶多次提交表單,或者重復(fù)的鏈接操作。

我們?yōu)樯唐繁韱沃械陌粹o,增加這個屬性:

<%= f.submit nil, :data => { :"disable-with" => "請稍等..." } %>

當(dāng)我們提交表單時,會有:

http://wiki.jikexueyuan.com/project/rails-practice/images/chapter_3/6.png" alt="" />

如果你還沒看清楚效果,頁面就已經(jīng)跳轉(zhuǎn)了,我們可以給 create 方法增加一個 sleep 10

def create
  sleep 10
  @product = Product.new(product_params)
  ...

更多 ujs 支持的屬性,我們在 這里 看到。

3.3.2 無刷新頁面的操作

ujs 給我們帶來的一些便利還不止這些,我們來點(diǎn)復(fù)雜的:在不刷新頁面的情形下,添加一個商品,并顯示在列表中。

我們現(xiàn)在的列表頁是這樣的:

http://wiki.jikexueyuan.com/project/rails-practice/images/chapter_3/7.png" alt="" />

現(xiàn)在點(diǎn)擊添加,我們會進(jìn)入到 http://localhost:3000/products/new,我們并不改變它,畢竟在某些 js 失效的情形下,點(diǎn)擊這個按鈕還是要跳轉(zhuǎn)到 new 頁面的。

我們希望給頁面增加一個表單,來輸入新商品的信息,在這之前,我們想更酷一點(diǎn),我們使用 modal 來顯示這個表單:

<%= link_to t('.new', :default => t("helpers.links.new")), new_product_path, :class => 'btn btn-primary', data: {toggle: "modal", target: "#productForm"} %>

ujs 允許我們在 link 上增加額外的屬性,當(dāng)我們再次點(diǎn)擊 添加 按鈕時:

http://wiki.jikexueyuan.com/project/rails-practice/images/chapter_3/8.png" alt="" />

當(dāng)然我做了其他一些修改,你可以在 這里 找到完整的代碼。

為了產(chǎn)生一個 ajax 的請求,我們在表單上增加一個參數(shù) remote: true

<%= form_for @product, remote: true, :html => { :class => 'form-horizontal' } do |f| %>

這時,ujs 將會調(diào)用 jQuery.ajax() 提交表單,此時的請求是一個 text/javascript 請求,Rails 會返回給我們相應(yīng)的結(jié)果,在我們的 action 里,增加這樣的聲明:

respond_to do |format|
  if @product.save
    format.html {...}
    format.js
  else
    format.html {...}
    format.js
  end
end

在保存(save)成功時,我們返回給視圖(view)一個 js 片段,它可以在瀏覽器端執(zhí)行。

我們創(chuàng)建一個新文件 app/views/products/create.js.erb,在這里,我們將新添加商品,顯示在上面的列表中。

$('#productsTable').prepend('<%= j render(@product) %>');
$('#productFormModal').modal('hide');

我們使用 .js.erb 的文件,方便我們在 js 文件里插入 erb 的語法。

我們將一行商品信息使用 prepend 方法,插入到 productsTable 的最上面,j 方法將我們的字符串轉(zhuǎn)換成 js 片段。

好了,你可以試一試效果了。

你可能也像我一樣做了一些測試,導(dǎo)致插入了很多測試數(shù)據(jù),為了繼續(xù)不刷新頁面就完成刪除操作,我們給 刪除 按鈕上也增加一個 ajax 調(diào)用。

我們先給每一行記錄,增加一個唯一的 ID 標(biāo)識,通常使用“名字 + id”的形式,我們還需要給刪除連接增加 remote: true 屬性,我們編輯 app/views/products/_product.html.erb

<tr id="product_<%= product.id %>">
...
<%= link_to "刪除", product, :method => :delete, remote: true, :data => { :confirm => "點(diǎn)擊確定繼續(xù)" }, :class => 'btn btn-danger btn-xs' %>

我們再增加一個文件以返回 js 片段給瀏覽器執(zhí)行 app/views/products/destroy.js.erb

$('#product_<%= @product.id %>').fadeOut();

你可以再試試看。

現(xiàn)在,我們看一下添加商品時的返回結(jié)果:

$('#productsTable').prepend('<tr id=\"product_14\">\n  <td><a href=\"/products/14\">kkk<\/a><\/td>\n  <td>jjj<\/td>\n  <td class=\"text-right\">CN¥ 999.00<\/td>\n  <td>2015年2月26日 星期四 23:57:55<\/td>\n  <td>\n    <a class=\"btn btn-primary btn-xs\" href=\"/products/14/edit\">編輯<\/a>\n    <a data-confirm=\"點(diǎn)擊確定繼續(xù)\" class=\"btn btn-danger btn-xs\" data-remote=\"true\" rel=\"nofollow\" data-method=\"delete\" href=\"/products/14\">刪除<\/a>\n  <\/td>\n<\/tr>\n');
$('#productFormModal').modal('hide');

這里面大部分代碼是不必要的 HTML代碼,如何讓我們的返回結(jié)果更簡潔呢?我們現(xiàn)在發(fā)送個是 text/javascript 請求,返回給我們的是 js 片段。下一節(jié)我們發(fā)送 'json' 請求,我們在瀏覽器端使用 js 處理返回的 json 數(shù)據(jù)。

3.3.3 json 數(shù)據(jù)的頁面處理

為了和添加商品區(qū)分開,我們在修改商品時,使用 json 來處理數(shù)據(jù),而且也在一個 modal 中完成。

<%= link_to t('.edit', :default => t("helpers.links.edit")), edit_product_path(product), remote: true, data: { type: 'json' }, :class => 'btn btn-primary btn-xs editProductLink' %>

我們給編輯鏈接,增加了 remote: true, data: { type: 'json' },這時我們沒有打開modal,我們把 js 代碼寫在 coffeescript 中。

我們新建一個文件,app/assets/javascripts/products.coffee。這個文件我們只在商品頁面使用,所以不必把它放到 simplex.js 中,現(xiàn)在我們只在商品的 index.html.erb 中使用它,所以:

<%= content_for :page_javascript do %>
<%= javascript_include_tag "products" %>
...

當(dāng)我們點(diǎn)擊編輯按鈕時,我們期望幾件事:

  1. 打開 modal 層,顯示編輯表單
  2. 讀取這個商品的信息(json 格式),把需要編輯的內(nèi)容填入表單

好,我們寫上這部分代碼:

jQuery ->
  $(".editProductLink")
    .on "ajax:success", (e, data, status, xhr) ->
      $('#alert-content').hide() [1]
      $('#editProductFormModal').modal('show') [2]
      $('#editProductName').val(data['name']) [3]
      $('#editProductDescription').val(data['description']) [3]
      $('#editProductPrice').val(data['price']) [3]
      $("#editProductForm").attr('action', '/products/'+data['id']) [4]
  • [1] 我們隱藏錯誤信息提示框
  • [2] 顯示層
  • [3] 填入編輯的信息
  • [4] 更改表單提交的地址

再來看看我們的編輯表單:

...
<%= form_tag "", method: :put, remote: true, data: { type: "json" }, id: "editProductForm", class: "form-horizontal" do %>
...
<%= text_field_tag "product[name]", "", :class => 'form-control', id: "editProductName", required: true %>
...
<%= text_field_tag "product[description]", "", :class => 'form-control', id: "editProductDescription" %>
...
<%= text_field_tag "product[price]", "", :class => 'form-control', id: "editProductPrice" %>
...

我們讓表單提交的地址,可以根據(jù)選擇的商品而改變,同時我們設(shè)定它的 type 為 json 格式。

我們?yōu)槊恳粋€輸入框,設(shè)定了 ID,這樣,我們用讀取的 json 信息,分別填入對應(yīng)的編輯框內(nèi)。

然后,我們改動一下 controller 中的方法:

def edit
  respond_to do |format
    format.html
    format.json { render json: @product, status: :ok, location: @product } [1]
  end
end
  • [1] 我們讓 edit 方法,返回給我們商品的 json 格式信息。
def update
  respond_to do |format|
    if @product.update(product_params)
      format.html { redirect_to @product, notice: 'Product was successfully updated.' }
      format.json [1]
    else
      format.html { render :edit }
      format.json { render json: @product.errors.full_messages.join(', '), status: :error } [2]
    end
  end
end
  • [1] 我們讓 update 方法,可以接受 json 的請求,
  • [2] 當(dāng) update 失敗時,我們需要把失敗的原因告訴客戶端,它也是 json 格式的。

當(dāng)我們需要考慮 update 方法會有成功和失敗兩種可能時,我們的 ajax 調(diào)用,就要這樣來寫了:

$("#editProductForm")
  .on "ajax:success", (e, data, status, xhr) ->
    $('#editProductFormModal').modal('hide') [1]
    $('#product_'+data['id']+'_name').html(  data['name'] ) [2]
    $('#product_'+data['id']+'_description').html(  data['description'] ) [2]
    $('#product_'+data['id']+'_price').html(  data['price'] ) [2]
  .on "ajax:error", (e, xhr, status, error) ->
    $('#alert-content').show() [3]
    $('#alert-content #msg').html( xhr.responseText ) [4]
  • [1] 我們隱藏這個層
  • [2] 當(dāng)成功的時候,我們把修改好的信息,放回到我們的頁面中
  • [3] 當(dāng)失敗的時候,我們顯示個錯誤信息提示框
  • [4] 我們向這個框內(nèi),填入信息

更多 controller 的介紹,后面章節(jié)還會有,這里我們要了解的是,我們頁面拿到的信息,不再是 js 片段,而是 json 格式的數(shù)據(jù)。

當(dāng)我們處理大量數(shù)據(jù)的時候,json 明顯要比 js 片段更節(jié)省傳輸空間,我們也可以把處理動作寫到獨(dú)立的 js 文件中,不過,json 格式返回給我們的,是 9.9,而我們頁面顯示的是格式化后的 CN¥ 9.90,如果我們想把處理好格式的數(shù)據(jù)返還回來,該如何處理呢?

我們可以使用 jbuilder 做這件事,我們新建一個 update.json.jbuilder

json.id @product.id
json.name link_to @product.name, product_path(@product) [1]
json.description @product.description
json.price number_to_currency(@product.price) [2]
  • [1] 我們把鏈接的地址用輔助方法生成
  • [2] 我們用 number_to_currency 方法把價格格式化,這里可以使用輔助方法

如何知道我們的確使用的是 json 數(shù)據(jù)呢?我們可以查看瀏覽器的控制臺,或者查看命令行的 log 輸出。

http://wiki.jikexueyuan.com/project/rails-practice/images/chapter_3/9.png" alt="" />

http://wiki.jikexueyuan.com/project/rails-practice/images/chapter_3/10.png" alt="" />

這里 可以找到我調(diào)試好的代碼。

在實(shí)踐開發(fā)中,我們會從服務(wù)端拿到很多的內(nèi)容,比如幾十條訂單信息,我們可以用上面的方法把它們顯示到頁面上,也可以使用 http://handlebarsjs.com/ 這種模板引擎,使頁面和邏輯更加的獨(dú)立,清晰。當(dāng)我們面對少量的內(nèi)容時,js 片段要比寫一大堆 coffeescript 來的更省事些。所以,我們在確定選用哪種方式處理,要看我們面對的是怎樣的問題。

最后附上兩個附表。

附表一,當(dāng)我們 render json:..., status: :ok, ... 時,status 和符號的對應(yīng),可以在這里找到,一般我們用 :ok, :create, :success, :error 就足夠了。

Response Class HTTP Status Code Symbol
Informational 100 :continue
101 :switching_protocols
102 :processing
Success 200 :ok
201 :created
202 :accepted
203 :non_authoritative_information
204 :no_content
205 :reset_content
206 :partial_content
207 :multi_status
208 :already_reported
226 :im_used
Redirection 300 :multiple_choices
301 :moved_permanently
302 :found
303 :see_other
304 :not_modified
305 :use_proxy
306 :reserved
307 :temporary_redirect
308 :permanent_redirect
Client Error 400 :bad_request
401 :unauthorized
402 :payment_required
403 :forbidden
404 :not_found
405 :method_not_allowed
406 :not_acceptable
407 :proxy_authentication_required
408 :request_timeout
409 :conflict
410 :gone
411 :length_required
412 :precondition_failed
413 :request_entity_too_large
414 :request_uri_too_long
415 :unsupported_media_type
416 :requested_range_not_satisfiable
417 :expectation_failed
422 :unprocessable_entity
423 :locked
424 :failed_dependency
426 :upgrade_required
428 :precondition_required
429 :too_many_requests
431 :request_header_fields_too_large
Server Error 500 :internal_server_error
501 :not_implemented
502 :bad_gateway
503 :service_unavailable
504 :gateway_timeout
505 :http_version_not_supported
506 :variant_also_negotiates
507 :insufficient_storage
508 :loop_detected
510 :not_extended
511 :network_authentication_required

附表二:ajax 的回調(diào)方法,我們使用了 :success 和 :error,當(dāng)然還有其他的一些,我們需要了解下。

event name extra parameters * when
ajax:before before the whole ajax business , aborts if stopped
ajax:beforeSend [event, xhr, settings] before the request is sent, aborts if stopped
ajax:send [xhr] when the request is sent
ajax:success [data, status, xhr] after completion, if the HTTP response was a success
ajax:error [xhr, status, error] after completion, if the server returned an error **
ajax:complete [xhr, status] after the request has been completed, no matter what outcome
ajax:aborted:required [elements] when there are blank required fields in a form, submits anyway if stopped
ajax:aborted:file [elements] if there are non-blank input:file fields in a form, aborts if stopped