複数のフィルターでさらに絞り込みたい!

csv_serverのファイル件数が増えてくれば、ユーザーからは、すぐにこんな要望の連絡が入りそうだ。「編集可能な経理部の利益管理表リストが見たい!」とか、「2007年の人事部のファイルで、あとで修正が入ったリストが見たい!」など。しかし、今の状態では1つの条件でしかフィルターできないので、「並び替えでも使って自分の目で探してください。」としか答えようが無い...。
考えてみれば、googleだって、複数条件や絞り込み検索ができなかったら誰も使わないと思う。求められているのは、自由に条件を組み合わせることが出来るフィルターなのです。シングルフィルターのままでは、高性能なSQLの検索機能も無駄になってしまう...。でも、マルチフィルターに変更するのは、ちょっと手間がかかりそうだ。まず、どのような機能にするか決めなくてはならない。

目指す所

そこで、とりあえず身近なところでお手本にしたのが、MacOSX10.4 TigerにインストールされているMailの「振り分けルール」を作成する時の条件フィルター。見た目はこんな感じのインターフェースになっている。

  • 最初はシンプルだけども、必要なフィルター条件の追加、削除が簡単にでき、複雑な条件を作成することも可能。
  • 入力フォームは、フィルター項目に合わせて最適なインターフェースに変化する。

昔よくあった、検索用の専用ページを用意して、全てのフィルター項目をはじめから羅列するようなインターフェースは嫌いだ。(項目があり過ぎて、どこに何を入力すればいいか、まったく分かり難いのだ...。)

フィルター条件を追加できるようにする。

やるべきことがあり過ぎて、何から手を付けて良いのか分からない。そんな時、自分では見た目(インターフェース)を最初に作り始める。まずは「フィルター条件を追加」のリンクを作成して、条件の入力フォームを追加できるようにしてみた。

  • 現状、フィルター条件と、フィルター実行/解除の操作が1行になってしまっている。条件の入力行と、実行/解除の操作行を分けた。画面の構成は以下のような感じになる。
  • フィルター条件を追加リンクをクリックすると、<span id="filters">タグの部分に、新たなフィルター条件が追加されるようにする予定。

list.rhtml

_list_filter.rhtml(フィルターの実行/解除/追加/削除という操作リンクの行)
<span id="filters">

_list_filter_condition.rhtml(フィルター条件の行)

</span>
フィルター条件を追加のリンク
フィルターを実行/解除のリンク

<table>リスト表示する部分

_listh.rhtml(列タイトル)

_listd.rthml(リストデータ)

</table>

前ページ/次ページのリンク
CSVファイルを新規登録のリンク

コーディング
app/views/csvs/_list_filter.rhtml
ビュー(「フィルター条件を追加」のリンクを描画)
  • link_to_remoteのオプション:position => :bottomを指定することで、<span id="filters">タグの一番下に、リンクをクリックする度に追加される。(:position => :bottomがないと、追加でなく、上書き更新になってしまい、フィルター条件は増えない。)
  • 以下は:positionの指定による挿入位置の違い。
      • :position => :beforeで挿入される位置(タグの直前)
    • <span id="filters">
      • :position => :topで挿入される位置(タグ内の一番上)
      • :position => :bottomで挿入される位置(タグ内の一番下)
    • </span>
      • :position => :afterで挿入される位置(タグの直後)
...(途中省略)...
<!--フィルター条件の行-->
<ol>
  <span id="filters">
    <%# :position => :bottomによって、ここにフィルター条件が追加されていく。 %>
    <%#= render :partial => 'list_filter_condition' %>
  </span>
  <small>
  <%= link_to_remote 'フィルター条件を追加', 
                      :update => 'filters',
                      :submit => 'filters', 
                      :url => {:action => 'add_filter'}, 
                      :position => :bottom %>
  </small>
</ol>
...(途中省略)...
app/controllers/csvs_controller.rb
コントローラー
  def add_filter
    # 配列の設定
    @date_input_sample = '<span id="date_input_sample">(入力例: 2007/4/1 , 4/1 , 6:10)</span>'
    @column_titles = %w{file_name file_comment editable file_size created_at file_updated_at management_section_id user_id}

    @filter_column = @column_titles[0]
    render :partial => 'list_filter_condition'
  end
app/views/csvs/_list_filter_condition.rhtml
ビュー(「ファイルが...を含む」等の条件の入力フォームを描画)
<li>
<%# フィルタ列の選択 %>
<% options = @column_titles.map{|t| [Csv.human_attribute_name(t), t]} %>
<%= select_tag "filter_column", 
               options_for_select(options, @filter_column) %><%# フィルタ列を選択するselect_tag :filter_columnを監視して、変化したら、再描画する。 %>
<%= observe_field "filter_column", 
                  :update => "filter_update", 
                  :submit => "filter_params", 
                  :url => {:action => 'change_filter_column'} %>

<%# フィルタ文字と比較方法を入力 %>
<%# 列タイトルごとに部分テンプレートを用意 _filter_フィールド名.rhtml %>
<%= render :partial => "filter_#{@filter_column}" %>
</li>

問題点

以上で、フィルター条件は追加される事はされるが、見た目だけ...。条件が複数あっても、フィルター条件として機能するのは最初の条件だけの状態。それにフィルターを実行してしまうと、今まで追加した条件は消えてしまう...。なぜか?

  • フィルター条件が複数あっても、パラメーターとして送信される時は、常に:filter_column, :filter_item, :filter_idというparams[]の識別子で送信されている。(例えば、2つの条件なら、params[:filter_column]が2重に送信され、取得できるのはおそらく1行目の値だけになってしまうのだろう。)パラメーターにフィルター条件の行番号の情報を付加する必要がある。
  • シングルフィルターでやっていた時は、ajaxと言いながら、フィルターを実行する度にページ全体を更新していた。これだと、部分更新で追加した条件は、消えてしまうのは当たり前か...。フィルターを実行した時にCSVファイルリストの部分だけに更新範囲を限定する必要がある。