Selectボックスの監視

Selectボックスで選択を行うとその内容によって他のSelectボックスの内容を更新するというのをobserve_fieldヘルパーを使って実装してみました。
これは、フィールドの値変更を監視、変更が行われるとJavascriptの非同期通信API(Ajax)を使って他方のフィールドのデータをサーバからとりこみ表示するというものです。
今回カテゴリーが選択されるとサブカテゴリーのSelectボックスが更新されるというものを例として実装しました。

prototype.jsのinclude

前提としてprototype.jsが取り込まれるようにしておきます。Viewのはじめのほう(通常headタグのところ)に以下のようにいれておくと、prototypeとscript.ac.ulo.us関連のスクリプトが読みこまれるようになります(もちろんこれらのファイルがサーバの適当な場所の配置されている必要があります)

  <%= javascript_include_tag :defaults %>

Viewの作成

selectボックス2つ(監視されるほうと、更新されるほう)をつくり、observe_fieldで監視されるようviewを実装します。

<% form_for (:product, @product) do |f| %>
  <table>
   ....省略....

  <td>
    <%= f.select  :category_id, @categories.collect { |e| [e.name, e.id] } %>
  </td>
  <td >
    <%= f.select  :sub_category_id, @sub_categories.collect { |e| [e.name, e.id] } %>
  </td>
  <%= observe_field :product_category_id ,
    :update => :product_sub_category_id,
    :url => {:action => 'sub_categories'},
    :on => "change",
    :with => :product_category_id
  %>
  ....
<% end %>
    1. obverve_fieldの1つ目のパラメータは、監視されるフィールドのタグidです。form_forの名前が:product, selectボックスの名前が:category_idとしてしているのでその監視されるselectボックスのidは「product_category_id」となります。
    2. 「:update =>」 で更新されるselectボックスのタグidを指定します。
    3. 「:url =>」 で実行するアクションを指定します。
    4. 「:on => "change"」 でchangeイベントを監視することを指定しています。
    5. 「:with =>」 では、値をパラメータとして送信するタグのidを指定しています。ここで指定するとが、コントローラ内のparamメソッドでの取得ができます。

observe_fieldヘルパー挿入の結果として、以下のようなAjaxのにUpdaterメソッドをイベントに登録するJavascriptが生成されます。

new Form.Element.EventObserver( 'product_category_id', 
  function(element, value) {
    new Ajax.Updater('product_sub_category_id', 
    '/admin/product/update_sub_categories', 
    {asynchronous:true, evalScripts:true, 
    parameters:'product_category_id=' + value + '&authenticity_token=' + encodeURIComponent('df4b585074f127bf0c4930496732486b0416dd69')})}, 
    'change')

更新先フィールドのためのView

更新先のSelectの内容を更新するためのViewを作成します。option_for_selectをつかってOptionタグがレンダリングされるようにします。今回アクション名が「sub_categories」なのでviewのファイル名は「sub_categries.html.erb」となります。

<%= options_for_select  @sub_categories.collect { |e| [e.name, e.id] } %>

アクションを作成

  def sub_categories
    @sub_categories = SubCategory.find_all_by_category_id(params[:product_category_id])
    render :layout => false
  end

renderメソッドを「:layout」オプションパラメータにfalseを指定して、レイアウト部分が含まれないよう(対象のviewのみがレンダリングされるよう)にします