checkbox のある ListView

チェックBOXが存在するリストの表示&制御は、業務系Webの開発でよくあるもので、
案件の都度、相当量の JavaScript とサーバサイドを作らなくてはならなくあまり汎用的なモデルが存在しない。
とは言え、やはり少しでも使いそうなパターンはモデルとして残しておきたいということで。。。

f:id:posturan:20160313175437j:plain


チェックをONしたものだけを、refresh ボタンで、残すというリストの例です。
今回の Wicket のプログラムは、checkbox の状態保持として PropeertyModel を使用するのが、ミソなのですが、
注意が必要なのは、全てのチェックBOXのON/OFFを制御する時、AjaxCheckBoxの behavior で JavaScriptを実行しますが、
jQuery の prop で、checkbox の true/false をセットするだけは、チェックBOXの changeイベントが走りません。
そこで、prop で真偽を逆にセットして、trigger で click を実行することで、changeイベントを走らせて、WicletのListView populateItem の中で
設定する checkbox の change イベント捕捉処理を実行させるのです。


public class RefreshListPage extends WebPage{
   public List<Cell> mlist;
   protected Map<Integer, Cell> checkedMap;
   protected List<String> checkWicketIdlist;

   public RefreshListPage(){
      // リスト初期化
      initList();

      Form<Void> form = new Form<Void>("form");
      final WebMarkupContainer listContainer = new WebMarkupContainer("listContainer");
      listContainer.setOutputMarkupId(true);
      final ListView<Cell> listview = new ListView<Cell>("listview",  new PropertyModel<List<Cell>>(this, "mlist")){
         @Override
         protected void populateItem(final ListItem<Cell> item){

            final Cell cell = item.getModelObject();
            AjaxCheckBox checkBox = new AjaxCheckBox("check", new Model<Boolean>(cell.isSelected)){
               @Override
               protected void onUpdate(AjaxRequestTarget target){
                  // 選択なら Map に格納、未選択なら Mapから削除
                  cell.isSelected = getModelObject();
                  if (getModelObject()){
                     checkedMap.put(cell.id, cell);
                  }else{
                     checkedMap.remove(cell.id);
                  }
               }
            };
            checkWicketIdlist.add(checkBox.getMarkupId());
            item.add(checkBox);
            item.add(new Label("number", cell.id));
            // checkbox ON/OFF → 行背景色をセット
            item.add(new AttributeModifier("style", new AbstractReadOnlyModel<String>(){
               @Override
               public String getObject(){
                  return item.getModelObject().isSelected ? "background-color: #f0ffeb" : "background-color: #ffffff";
               }
            }
));
         }
         // ListView の表示タイミング checkbox 変化で行背景色を変化させる jQuery を実行する。
         @Override
         protected void onAfterRender() {

            super.onAfterRender();
            /* このページ用のJavaScript、RefreshListPage.js で定義したメソッドを実行する */
            JavaScriptUtils.writeJavaScript(getResponse(), "rowcolorChangeSetting();");
         }
      };
      listContainer.add(listview);
      form.add(listContainer);
      // All checkbox ・・・逆の真偽 !getModelObject() をセットして click tigger 実行する。
      form.add(new AjaxCheckBox("allCheck", new Model<Boolean>(false)){
         @Override
         protected void onUpdate(AjaxRequestTarget target){
            String value = Boolean.toString(!getModelObject());
            for(String idstr:checkWicketIdlist){
               target.appendJavaScript("$('#" + idstr + "').prop('checked'," + value + ").trigger('click');");
            }
         }
      });
      // Refresh
      form.add(new AjaxLink<Void>("refresh"){
         @Override
         public void onClick(AjaxRequestTarget target){
            // リスト mlist を一度全てクリア
            mlist.clear();
            checkWicketIdlist.clear();
            // check されたものだけを mlist に追加
            for(Integer key:checkedMap.keySet()){
               mlist.add(checkedMap.get(key));
            }
            target.add(listContainer);
         }
      });
      // Add
      form.add(new AjaxLink<Void>("add"){
         @Override
         public void onClick(AjaxRequestTarget target){
            
            // TODO リスト mlist に、追加 を行う。
            
            checkWicketIdlist.clear();
            target.add(listContainer);
         }
      });
      // submit
      form.add(new AjaxButton("submit", form){
         @Override
         protected void onSubmit(AjaxRequestTarget target, Form<?> f){
            for(Integer id:checkedMap.keySet()){
               // チェックされた分を認識
            }
         }
      });
      //
      add(form);
   }
   /* リスト初期化 */
   private void initList(){
      checkedMap = new HashMap<Integer, Cell>();
      mlist = new ArrayList<Cell>();
      
      // TODO リスト mlist 初期表示時に必要なだけ格納する。

      checkWicketIdlist = new ArrayList<String>();
   }
   @Override
   public void renderHead(IHeaderResponse response) {
      super.renderHead(response);
      response.render(CssHeaderItem.forReference(new CssResourceReference(RefreshListPage.class, "RefreshListPage.css")));
      response.render(JavaScriptHeaderItem.forReference(new JavaScriptResourceReference(RefreshListPage.class, "jquery-2.1.1.min.js")));
      response.render(JavaScriptHeaderItem.forReference(new JavaScriptResourceReference(RefreshListPage.class, "RefreshListPage.js")));
   }
}

----------------------------
css を書いた RefreshListPage.css は、以下のとおり。

@CHARSET "UTF-8";

table.grid{
   border-spacing: 0; border-collapse: collapse;
   box-sizing: border-box;
   display: inline-block;
   border: 1px #e3e3e3 solid;
}
.grid tbody{
   box-sizing: border-box;
   height: 200px;
   display: inline-block;
   overflow-y: scroll;

}
.grid th{ background-color: #c0d0d0; }
.grid th, .grid td{
   border: 1px solid #888888;
   padding: 5px 10px;
   box-sizing: border-box;
}
.grid th:nth-child(1){ width: 70px;  }
.grid th:nth-child(2){ width: 200px; }
.grid td:nth-child(1){ width: 70px; text-align: center; }
.grid td:nth-child(2){ width: 200px; }

---------------------------------------------
HTMLは以下のとおり。

<wicket:extend>

<form wicket:id="form">
<div>
   <table class="grid">
      <thead>
         <tr>
            <th><input type="checkbox" wicket:id="allCheck"></th>
            <th>ID</th>
         </tr>
      </thead>
      <tbody wicket:id="listContainer">
         <tr wicket:id="listview">
         <td><input type="checkbox" wicket:id="check"></td>
         <td wicket:id="number"></td>
         </tr>
      </tbody>
   </table>
</div>
<br/>
<div>
<table style="white-space: nowrap;">
<tr>
<td><a href="#" wicket:id="refresh"><button type="button">refresh</button></a></td>
<td width="100"></td>
<td><a href="#" wicket:id="add"><button type="button">add</button></a></td>
<td width="100"></td>
<td><button type="submit" wicket:id="submit">submit</button></td>
</tr>
</table>
</div>
</form>

</wicket:extend>

---------------------------------------------
RefreshListPage.jsは以下のとおり。

/* checkbox ON で、行背景色変更を設定する。*/
var rowcolorChangeSetting = function(){
   $("input[type='checkbox']").change(function(){
      $(this).parent().parent().css("background-color", $(this).prop("checked") ? "#f0ffeb" : "#ffffff");
   });
};
$(function(){
  &nbsprowcolorChangeSetting();
});