「UFO大好き霊子さん」をRails2.x系から3.x系に移植するに当たってぶち当たった問題のひとつです。かなり汎用性のある内容と思われるので、今回こちらにメモしておきます。
めんどくさい人は最後だけ見てね。
もくじ
■rails2系でのお手軽Ajax
Rails2系では、こんなヘルパを書くことで簡単なAjax通信ができます。
<%= link_to_remote 'イイネ!', :url => {:controller => 'votes', :action => 'create', :moderate => 'yes', :post_id => @post.id}, :update => "post_voting_#{@post.id}" %>
こうすることで、’イイネ!’というリンクが現れます。これをクリックすると、VotesコントローラのCreateアクションがpost_idとmoderate=yesというパラメータで呼ばれ、その結果が”post_voting_#{@post.id}”というIDを持つ要素のinnerHTMLに書きこまれます。殆どJavaScriptを意識すること無く書けるので、とても便利で楽でした。
■rails3系では”:remote=>true”で出来ると言うものの…
rails3系では「link_to_remote」が廃止され、代わりに「link_to」に:remote=>trueというオプションが加わりました。が、完全互換ではなく、:updateオプションはありません。
rails3.x系で正攻法で今までと同じことをするには、ビューの元の部分を
<%= link_to 'イイネ!', {:controller => 'votes', :action => 'create', :moderate => 'yes', :post_id => @post.id}, :remote=>true %>
と書き換え、さらにこの上でVotes#createがJavaScriptをrenderして返さなければなりません。たとえば、”view/votes/create.js.erb”を作って、
Element.update($("post_voting_<%= @post.id %>"),'投票ありがとう!');
みたいな感じですね。正直めんどくさい。
■移植のため、Prototype.jsだけで機械的になんとか移植するには!
正直めんどくさいので、最小の手順でなんとかしましょう。Rails3系では、JavaScriptとHTML部分が完全に分離され、上記のlink_toで:remote=>trueをしても出力されたHTMLにはこのようにJavaScriptは一切含まれていません。こんな出力です:
<a href="/votes?moderate=true&post_id=1" data-remote="true" rel="nofollow">イイネ!</a>
ではどうやってJavaScriptで通信してるのかというと、rails.jsを見ればわかりますが、”data-remote”が指定されてる要素に対して”click”イベントハンドラを追加することで処理しています。こんな感じ:
document.on("click", "a[data-remote]", function(event, element) { if (event.stopped) return; handleRemote(element); event.stop(); });
で、Ajax通信がとりあえず終わったらajax:competeイベント、成功するとajax:successイベントが、失敗するとajax:failureイベントが投げられます。rails.jsのイカの部分でゲソ:
function handleRemote(element) { (略) new Ajax.Request(url, { method: method, parameters: params, evalScripts: true, onComplete: function(request) { element.fire("ajax:complete", request); }, onSuccess: function(request) { element.fire("ajax:success", request); }, onFailure: function(request) { element.fire("ajax:failure", request); } }); (略) }
じゃ、このイベントを活用すれば良いんじゃなイカ?というわけで、イベントを追加して結果を流しこめばおk。こんなのをapplication.jsにでも追加しましょう:
document.on("ajax:success", "a[data-remote]", function(event, element) { alert(event.memo.responseText); //結果が出てくる });
あれ?結果を書きこむ先は?
link_toでは任意の属性を出力される要素に追加することができるので、これを使いましょう。また、createはpostメソッドなので、それも指定します。
<%= link_to_remote 'イイネ!', {:controller => 'votes', :action => 'create', :moderate => 'yes', :post_id => @post.id}, :remote=>true, 'data-update'=>"post_voting_#{@post.id}", 'data-method'=>:post %>
application.jsではこのようにして結果を書き込みます:
document.on("ajax:success", "a[data-update]", function(event, element) { Element.update($(element.readAttribute('data-update')), event.memo.responseText); });
これで目的は達成されました。
■手順まとめ
■1:link_to_remoteは廃止されたので、link_toに書き換えて:remote=>trueを追加する。
URLオプションの渡し方も微妙に変わってるので注意†1。:updateは’data-update’に変更し、アクションがpostの場合は’data-method’=>’post’も末尾に追加。
<%= link_to_remote 'イイネ!', :url => {:controller => 'votes', :action => 'create', :moderate => 'yes', :post_id => @post.id}, :update => "post_voting_#{@post.id}" %>
を、次のように変更します。
↓
<%= link_to 'イイネ!', {:controller => 'votes', :action => 'create', :moderate => 'yes', :post_id => @post.id}, :remote=>true, 'data-update'=>"post_voting_#{@post.id}", 'data-method'=>:post %>
■2:application.jsにこんな内容を追加する。
document.on("ajax:success", "a[data-update]", function(event, element) { Element.update($(element.readAttribute('data-update')), event.memo.responseText); });
以上です。Rails3のAjaxでHTMLを返してjQueryで処理する – とはえ領域を参考にしつつ、jQueryを使わない方法を模索しました。
- †1: link_toが三つ引数を取るようになり、その二つ目がurlオプション