フィールドの値を取得してAjaxで指定エレメントへ処理結果を挿入する。
今回はテキストフィールドへ入力されたURLに対して接続確認を行い、結果を表示する処理を作ってみました。ボタンが使いたかったので、link_to_remoteではなくremote_functionを使ってます。
やりたいこと
・テキストフィールドの入力値を取得してアクションへ渡す。
・ボタンを2度押し出来ないようにする。
・loading画像を表示する。
・結果を指定エレメントに表示する。
コード
●controller
def test if site_check(params[:url]) render :text => "<span class='success'>接続に成功しました。</span>" else render :text => "<span class='failuer'>接続に失敗しました。URLを確認して下さい。</span>" end end private def site_check(url, limit = 10) return false if limit == 0 begin response = Net::HTTP.get_response(URI.parse(url)) rescue return false else case response when Net::HTTPSuccess return true when Net::HTTPRedirection site_check(response['Location'], limit - 1) else return false end end end
contorllerでは受け取ったurlに対してNet::HTTPで接続確認を行い、結果に応じてrender :text でテキストを直接返してます。なので今回はtestに対応するviewは用意してませんが、viewを使う場合はlayoutを使用しないよう以下のようにcontrollerへ記述しておかないとデザインがおかしな事になります。
class SitesController < ActionController::Base layout "sites", :except => [:test] end
Net::HTTPを使った処理は殆どlibrary net/httpのままです。
●view
〜略〜 <%= javascript_include_tag "prototype" %> <%= stylesheet_link_tag "style" %> 〜略〜 <%= text_field 'site', 'url', :size => 50 %> <div id='result'></div> <input type="button" value="TEST" id="testBtn" title="入力したURLの接続テストが行えます" onclick="<%= remote_function :update => "result", :url => { :action => 'test' }, :with => "'url=' + $F('site_url')", :loading => "$('testBtn').disabled='disabled'; Element.addClassName('testBtn','disabled'); Element.update('result',''); Element.addClassName('result','loading');", :loaded => "Element.removeClassName('result','loading'); Element.removeClassName('testBtn','disabled'); $('testBtn').disabled=false; "%>">
何は無くともprototype.jsを読み込むのを忘れずに。今回はボタンが使いたかったのでremote_functionをonclickイベントに設定していますが、link_to_remoteを使ってもオプションの指定は同じです。各オプションは以下のとおりです。
:update | 更新対象のエレメントIDを指定します。今回の場合、空のdiv要素が更新されます。 |
:url | ajaxで接続するアクションを指定します。 |
:with | パラメータとして渡す値を指定します。prototype.jsの$F()関数を使用してテキストフィールドのvalue値を取得しています。 |
:loading | リクエストの応答待ち中に実行する処理を指定します。まず、ボタンをdisabledにして、IEだと白抜き画像になってしまうので、classを追加してグレーにしています。その後div要素を空にして、classを指定し背景にloding画像を表示しています。div要素を空にしているのは、2回目の実行をすると前回の結果とlodingが画像がダブって表示されてしまう為です。 |
:loaded | 応答完了後の処理を指定します。classを削除してloading画像を非表示にし、ボタンのdisabledを解除しています。これをしないと実行結果が表示された後も、logind画像が回り続け、ボタンも押せない状態のままです。 |
このremote_funcitonで以下のようなjavascriptが生成されます。parametersにrails2.0から導入されたCSRF対策の為の認証トークンが付加されてます。
new Ajax.Updater( 'result', '/sites/test', { asynchronous:true, evalScripts:true, onLoaded:function(request){ Element.removeClassName('result','loading'); Element.removeClassName('testBtn','disabled'); $('testBtn').disabled=false; }, onLoading:function(request){ $('testBtn').disabled='disabled'; Element.addClassName('testBtn','disabled'); Element.update('result',''); Element.addClassName('result','loading'); }, parameters:'url=' + $F('site_url') + '&authenticity_token=' + encodeURIComponent('6c83ca10a63e8eed3d1161d41e0e2fc03a76e761') } )
●css
.success{ color: #00aaff; } .failuer{ color: #ff00aa; } .disabled{ background-color: #eee; border: 1px solid #ccc; } .loading { height: 16px; background-image: url("../images/loader.gif"); background-repeat: no-repeat; }
cssでは特に気をつける事もないですが、loding画像は空のdiv要素の背景に指定しているのでheightを指定しないと画像が表示されません。
そして
ここまで書いてRuby on Rails Documentationをスクロールしていたら、submit_to_remoteの存在に気が付いてしまいました...orz
subumit_to_remoteで書くとこんな感じです。ちょっとシンプル。APIはよく読めって事ですね。
<%= text_field 'site', 'url', :size => 50 %> <div id='result'></div> <%= submit_to_remote 'testBtn', 'TEST', :update => "result", :url => { :action => 'test' }, :with => "'url=' + $F('site_url')", :loading => "$('testBtn').disabled='disabled'; Element.addClassName('testBtn','disabled'); Element.update('result',''); Element.addClassName('result','loading');", :loaded => "Element.removeClassName('testBtn','disabled'); Element.removeClassName('result','loading'); $('testBtn').disabled=false;", :html => { :id => 'testBtn', :title => '入力したURLの接続テストが行えます' } %>
まあsubmit_to_remoteにしてもlink_to_limoteにしても、内部的にはremote_functionを使ってるので、remote_functionの使い方を理解しておくのは良い事だという事で。
def link_to_remote(name, options = {}, html_options = nil) link_to_function(name, remote_function(options), html_options || options.delete(:html)) end def submit_to_remote(name, value, options = {}) options[:with] ||= 'Form.serialize(this.form)' options[:html] ||= {} options[:html][:type] = 'button' options[:html][:onclick] = "#{remote_function(options)}; return false;" options[:html][:name] = name options[:html][:value] = value tag("input", options[:html], false) end