2012年11月19日月曜日

画面オープン直後にProcessing.jsのスクリプトを実行するサンプル

jQueryで画面のオープン直後に処理を実行する場合は、$(document).ready を利用することが一般的なようだが、このイベントに合わせてProcessingのスクリプトを実行しようとしてもうまくいかない。

具体的には、下記のようなソースコードでは、
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<script type="text/javascript" src="jquery.js"></script>
<script src="processing-1.4.1.js" type="text/javascript"></script>
<script type="text/processing" data-processing-target="mycanvas">
void setup()
{
  size(200, 200);
  colorMode(RGB, 100);
  noLoop();
}

void draw()
{
  fill(90);
  ellipse(70, 70, 100, 100);
}

void addEllipse()
{
  fill(60);
  ellipse(90, 90, 70, 70);
}
</script>
<script type="text/javascript">
$(document).ready(function(){
  var p= Processing.getInstanceById("mycanvas");
  p.addEllipse();
})
</script>
</head>
<body>
  <p>
  <canvas id="mycanvas"></canvas>
  </p>
  </body>
</html>
次のようなエラーメッセ―ジが表示されてしまう。


$(document).ready イベントでは、画面上のオブジェクトの読み込みがすべて完了した時点ではなく、画面上のDOM(Document Object Model)の読み込みが完了した時点、つまり、HTMLのソースコードの読み込みが完了した時点となる。

このタイミングでは、Processingの画面描画が完了していないので、Processingの関数を呼ぼうとしても、エラーになってしまうようだ。

$(document).ready の代わりに、$(window).load を使ったらうまくいった。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<script type="text/javascript" src="jquery.js"></script>
<script src="processing-1.4.1.js" type="text/javascript"></script>
<script type="text/processing" data-processing-target="mycanvas">
void setup()
{
  size(200, 200);
  colorMode(RGB, 100);
  noLoop();
}

void draw()
{
  fill(90);
  ellipse(70, 70, 100, 100);
}

void addEllipse()
{
  fill(60);
  ellipse(90, 90, 70, 70);
}
</script>
<script type="text/javascript">
$(window).load(function(){
  var p= Processing.getInstanceById("mycanvas");
  p.addEllipse();
})
</script>
</head>
<body>
  <p>
  <canvas id="mycanvas"></canvas>
  </p>
  </body>
</html>

2012年11月12日月曜日

Railsでリストボックスを使って親子テーブルを同時に更新するサンプル(インタフェース改良版)


前回作成したサンプルで、一応リストボックスとしては機能しているものの、登録件数が多くなると何が選択されていて、何が選択されていないかがわかりにくくなる。

下記のようなインタフェースで、左側のリストボックスが未登録、右側のリストボックスが登録済となるように分割して表示したい。


まずは再改良版の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