結合 Ajax


Rails 3.1預設的JavaScript程式庫是jQuery,在 版型(Layout) 中看過,預設會包括jquery.js,而一些頁面的「神奇魔法」,則是由jquery_ujs.js來處理與銜接jQuery,例如 版型(Layout)中介紹過,以link_to輔助方法為例,在按下其產生的鏈結時,會執行jquery_ujs.js中的程式碼,動態建立表單並使用表單物件的submit發送:

    // Handles "data-method" on links such as:
    // <a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a>
    handleMethod: function(link) {
      var href = link.attr('href'),
        method = link.data('method'),
        target = link.attr('target'),
        csrf_token = \$('meta[name=csrf-token]').attr('content'),
        csrf_param = \$('meta[name=csrf-param]').attr('content'),
        form = \$('<form method="post" action="' + href + '"></form>'),
        metadata_input = '<input name="_method" value="' + method + '" type="hidden" />';

      if (csrf_param !== undefined && csrf_token !== undefined) {
        metadata_input += '<input name="' + csrf_param + '" value="' + csrf_token + '" type="hidden" />';
      }

      if (target) { form.attr('target', target); }

      form.hide().append(metadata_input).appendTo('body');
      form.submit();
   
},

handleMethod是以submit發送表單,也就是所謂同步表單或非Ajax表單,有些輔助方法可以設定:remote => true,此時就會以非同步方式來發送,也就是所謂Ajax方式,以link_to輔助方法為例,若加上:remote => true,則產生的超鏈結會加上data-remote="true"form_for、button_to等也有:remote可以設定)。例如:

<a href="/messages/1" data-remote="true">Show</a>

標記為data-remote="true"的鏈結,會使用handleRemote方法處理,簡單來說,就是以非同步物件發送:

    // Submits "remote" forms and links with ajax
    handleRemote: function(element) {
      var method, url, data,
        crossDomain = element.data('cross-domain') || null,
        dataType = element.data('type') || (\$.ajaxSettings && \$.ajaxSettings.dataType),
        options;
        ...

        options = { 
         ....略
        };
        // Only pass url to `ajax` options if not blank
      if (url) { options.url = url; }
        return rails.ajax(options);
      } else {
        return false;
      }
    },

按下鏈結之後,由於是非同步回應,所以不會換頁,你要根據回應自行處理畫面,預設收到的回應是JavaScript,也可以指定JSON等其它格式。

舉例來說,可修改 觀摩 Scaffold 中的index.html.erb如下:

  • index.html.erb
<h1>Listing messages</h1>

<div id="message"></div>

<table>
  <tr>
    <th>Name</th>
    <th>Title</th>
    <th></th>
    <th></th>
    <th></th>
  </tr>

<% @messages.each do |message| %>
  <tr>
    <td><%= message.name %></td>
    <td><%= message.title %></td>
    <td><%= link_to 'Show', message, :remote => true %></td>
    <td><%= link_to 'Edit', edit_message_path(message) %></td>
    <td><%= link_to 'Destroy', message, confirm: 'Are you sure?', method: :delete %></td>
  </tr>
<% end %>
</table>

<br />

<%= link_to 'New Message', new_message_path %>

如此按下Show鏈結就不會換頁,而會以非同步方式發送請求,接著可以修改messages_controller.rb:

  • messages_controller.rb
class MessagesController < ApplicationController
  ...

  def show
    @message = Message.find(params[:id])

    respond_to do |format|
      format.html # show.html.erb
      format.js   # show.js.erb
      format.json { render json: @message }
    end
  end
...
end

非同步請求預設希望取得JavaScript回應,否則取得HTML回應,如上設定之後,若有個show.js.erb:

  • show.js.erb
\$('#message').html("<p><b>Content:</b><%= @message.content %></p>")
             .css({ backgroundColor: '#ffff99' });

接下來如果按下Show鏈結,就會在同頁面中<div id="message"></div>載入訊息內容。

如果想取得的回應是JSON格式,例如:
  • index.html.erb
<h1>Listing messages</h1>

<div id="message"></div>

<table>
  <tr>
    <th>Name</th>
    <th>Title</th>
    <th></th>
    <th></th>
    <th></th>
  </tr>

<% @messages.each do |message| %>
  <tr>
    <td><%= message.name %></td>
    <td><%= message.title %></td>
    <td><%= link_to 'Show', message_path(:id => message, :format => :json), :remote => true %></td>
    <td><%= link_to 'Edit', edit_message_path(message) %></td>
    <td><%= link_to 'Destroy', message, confirm: 'Are you sure?', method: :delete %></td>
  </tr>
<% end %>
</table>

<br />

<%= link_to 'New Message', new_message_path %>

<script>
  \$(function() {
    \$('a[data-remote]').bind("ajax:success", function(event, data) {            
      \$('#message').html('<p><b>Content:</b>' + data.content + '</p>')
                   .css({ backgroundColor: '#ffff99' });
      });
  });
</script>

按下Show鏈結,就會在同頁面中<div id="message"></div>載入訊息內容。

Rails 3中基本上對Ajax沒有提供太多神奇的魔法,DHH建議自己要親自撰寫JavaScript來處理一些事,這意謂著你對Ajax必須有更多的瞭解,你要嘛將JavaScript放在伺服端,結合Erb之類的來輔助產生內容,要嘛將JavaScript放在客戶端,並與伺服端協定好資料格式,或必要時撰寫一些頁面輔助方法來簡化頁面與伺服端的溝通。

如果要提供JSONP,只要在render時指定:json與:callback即可。例如:
  • messages_controller.rb
class MessagesController < ApplicationController
  ...

  def show
    @message = Message.find(params[:id])

    respond_to do |format|
      format.html # show.html.erb
      format.js   # show.js.erb
      format.json { render json: @message, :callback => params[:callback] }
    end
  end
...
end

如果使用者指定請求參數callback=process_message,則一個回應範例如下:

process_message({"content":"TEST!!!","created_at":"2012-02-09T06:31:37Z","id":1,"name":"Justin","title":"TEST","updated_at":"2012-02-09T06:31:37Z"})