前回作成したサンプルで、一応リストボックスとしては機能しているものの、登録件数が多くなると何が選択されていて、何が選択されていないかがわかりにくくなる。
下記のようなインタフェースで、左側のリストボックスが未登録、右側のリストボックスが登録済となるように分割して表示したい。
まずは再改良版のGameクラスとLineupクラスのscaffold。(実行結果は省略。)
rails g scaffold bestgame date:date
rails g scaffold bestlineup bestgame:references player:references
rake db:migrate
Gameクラスの
_form.html.erbは、下記のように修正する。
<%= form_for(@bestgame) do |f| %>
<% if @bestgame.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@bestgame.errors.count, "error") %> prohibited this bestgame from being saved:</h2>
<ul>
<% @bestgame.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :date %><br />
<%= f.date_select :date, :use_month_numbers => true %>
</div>
<div class="field">
<table>
<tr><td align="center">
<%= f.label "Bench Lineups" %><br />
<%= f.select(:non_lineup_ids, @non_lineup_select_data, {}, {:multiple => true, :size => Player.all.size}) %>
</td><td>
<input id="btnMoveRight" type="button" value="->" onClick="moveItems('right')" />
<br />
<input id="btnMoveLeft" type="button" value="<-" onClick="moveItems('left')" />
</td><td align="center">
<%= f.label "Starting Lineups" %><br />
<%= f.select(:player_ids, @lineup_select_data, {}, {:multiple => true, :size => Player.all.size}) %>
</td></tr>
</table>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
左側のリストボックスは、Lineupクラスに登録されていないPlayerを表示する。>
右側のリストボックスは、Lineupクラスに登録されているPlayerを表示する。
右側のリストボックスの送信データのみ、model側で扱うようにする。
左右のリストボックスのデータの移動は、矢印のボタンを押すと、下記のJavaScriptの関数が呼ばれるようにする。
// リストボックスの項目選択
function moveItems(direction) {
var leftBox = document.getElementById("bestgame_non_lineup_ids");
var rightBox = document.getElementById("bestgame_player_ids");
var fromBox, toBox;
if (direction == "right") {
fromBox = leftBox; toBox = rightBox;
}
else if (direction == "left") {
fromBox = rightBox; toBox = leftBox;
}
if ((fromBox != null) && (toBox != null)) {
if(fromBox.length < 1) {
alert("リストボックスにアイテムがありません!");
return false;
}
if(fromBox.selectedIndex == -1) {
alert("移動するアイテムを選択してください!");
return false;
}
while ( fromBox.selectedIndex >= 0 ) {
var newOption = new Option();
newOption.text = fromBox.options[fromBox.selectedIndex].text;
newOption.value = fromBox.options[fromBox.selectedIndex].value;
toBox.options[toBox.length] = newOption;
fromBox.remove(fromBox.selectedIndex);
}
}
return false;
}
// Submit時のリストボックスの項目自動選択
jQuery(function(){
$('[id*=bestgame]').submit(function(){
var selectedIDs = new Array();
$('#bestgame_player_ids option:not(:selected)').each(function() {
selectedIDs.push(this.value)
});
$('#bestgame_player_ids').val(selectedIDs)
});
});
moveItemsの関数では、選択されたリストボックスの値と表示テキストを移動先に追加、移動元から削除している。データがない場合とデータ未選択の場合のエラーチェックも行なっている。
$('[id*=bestgame]').submitの関数では、フォームのsubmit時に、右側のリストボックスの値を自動的に全選択するようにしている。
右側のリストボックスにデータを移しただけでは、フォームに値が送信されない。リストボックスは、あくまで選択された値だけがフォームに送信されるからである。
要素名を部分一致にしているのは、railsの場合、newのフォームとeditのフォームで、名前が異なるため。
modeには、下記のメソッドを追加する。
# Lineupクラスに存在しないPlayerのIDを配列でget/setできる属性を追加
def non_lineup_ids
@non_lineup_ids || players.collect{|p| p.id} - bestlineups.collect{|p| p.id}
end
def non_lineup_ids=(id_array)
@non_lineup_ids = id_array.collect{|id| id.to_i};
end
Lineupに存在しないプレイヤーを、playersとlineupsの配列の引き算で算出している。ただし、Controllerからは使用していない。
最後に、Controllerの修正は下記の通りとなる。(new~update部分のみ)
# GET /bestgames/new
# GET /bestgames/new.json
def new
@bestgame = Bestgame.new
@non_lineup_select_data = Player.all.collect{|p| [p.name, p.id]}
@lineup_select_data = ""
respond_to do |format|
format.html # new.html.erb
format.json { render json: @bestgame }
end
end
# GET /bestgames/1/edit
def edit
@bestgame = Bestgame.find(params[:id])
@non_lineup_select_data = Player.all.collect{|p| [p.name, p.id]} - Bestlineup.find_all_by_bestgame_id(params[:id]).collect{|l| [l.player.name, l.player.id]}
@lineup_select_data = Bestlineup.find_all_by_bestgame_id(params[:id]).collect{|l| [l.player.name, l.player.id]}
end
新規追加の場合は、登録済のデータが存在しないので、lineup_select_dataの配列を空で初期化している。
修正の場合は、未登録のデータを、playersとlineupsの配列を引き算することで導き出している。modelクラスの関数を使わずに、ここで計算しているのは、modelクラスの関数はidのみ扱っていて、名前を保持していないため。
これで、2つのリストボックスでデータを移動できるインタフェースが完成した。
今回は下記のホームページが参考になった。
リストボックス間でアイテムを移動
http://jsajax.com/Articles/listbox/339