Wicket-stuff の Editable Grid をカスタマイズして使用する

Webページ上で、編集可能な表といえば、
Handsontable | JavaScript Data Grid Component For Web Apps
や、
http://tabulator.info/
が、有名なので、このどちらかを提案することが多いのだが、
Wicket は、これより昔から、DataView 使用と Form 入力フィールドの混合での構成で
Wicket-stuff から、Editable Grid が提供されてる。
Wicket の最新 latest バージョン 8.4.0 に対応して Wicket-Stuff も 8.4.0 で出してはいる。
Wicket-stuff のGit-Hub からサンプルソースを入手して動かしてみると
CSSスタイルは、何もなく原始的な table のままで、後から記述しないとならない。
Handsontable や Tabulator 等、メジャーで普及しているのは、機能も豊富で賛同するところもあり、
右クリックコンテキストメニューによる操作がフレームワークとして提供されているのは嬉しいが、
右クリックコンテキストメニューの存在を、エンドユーザに説明する必要があって
本当にBest なのか?いつも疑問が残る。

Editable Grid · wicketstuff/core Wiki · GitHub

Wicket-stuff の Editable Grid を サンプルをそのまま動かすと、、、
f:id:posturan:20190421112847j:plain
編集操作は全て、単純なリンクである。
見た目が簡素であるが、右クリックコンテキストメニューを採用しない代わりに
一目で操作方法は掌握できる。

これを、fontawesome や、Bootsrtap を使ってもう少し見栄えを良くしていく。
Wicket ページ HTML に、

<wicket:head>
<link href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.5.0/css/all.min.css" rel="stylesheet">
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script>
</wicket:head>

を記述する。
グリッドコンテンツのページ HTML は、

<div class="container">
	<div wicket:id="grid" id="grid"></div>
	<div style="margin-top:20px">
	<button wicket:id="submit" type="button" class="btn btn-primary">submit</button>
	</div>
</div>

今回、FeedbackPanel は使用せずに除去してある。
CSSは、

@charset "UTF-8";
#grid .navigator a{
	font-size: 1rem;
	margin: 1px 10px;
}
#grid .navigator span.goto a[disabled='disabled']{
	color: #ff00ff !important;
}
#grid .headers th{
   background-color: #cccccc;
}
#grid tbody tr:nth-child(even){
	background-color: #cce7ff;
}
#grid tbody tr td:last-child div div a{
	margin: 0 0.5rem;
	color: #aaaaaa;
}
#grid th, #grid td{ white-space: nowrap; }

リンク → アイコン にする JavaScript

/**
 * custom-grid.js
 */
var renderCount = function(){
	var navigationlabel = $('.navigatorLabel div').html();
	if (navigationlabel==undefined){
		$('#grid thead').prepend('<tr class="navigation"><td colspan="4"><div class="navigatorLabel">Total : ' + $('#grid tbody tr').length + '</div></td></tr>')
	}else{
		$('.navigatorLabel div').html('Total : ' + 	navigationlabel.split(/ /)[5]);
	}
};
var setActionCell = function(){
	$('#grid a[id^="edit"]').html('<i class="fa fa-edit" title="編集"></i>');
	$('#grid a[id^="save"]').html('<i class="fa fa-save" title="保存"></i>');
	$('#grid a[id^="cancel"]').html('<i class="fa fa-undo" title="キャンセル"></i>');
	$('#grid a[id^="delete"]').html('<i class="fa fa-trash-alt" title="削除"></i>');
	$('#grid a[id^="add"]').html('<button type="button" class="btn btn-secondary">追加</button>');
	setEdlitAction();
};
var setEdlitAction = function(){
	$('#grid a[id^="edit"]').click(function(e){
		setTimeout('setActionCell();', 100);
	});
};

ページ表示は、こんな感じ、、、
f:id:posturan:20190421114327j:plain

編集のアイコンをクリックした時、クリックした行は入力フィールドになる。
f:id:posturan:20190421114401j:plain

ページの Javaソースは、、
EditableGrid 設置のカスタマイズ
onDelete, onSave, onAdd で、対象リストを操作は不要!である。
onError は、Validatorによる入力エラー検知→FeedbackPanel 設置してなければ Override 意味がない。

queue(new EditableGrid<Person, String>("grid", getColumnProperty()
, new EditableListDataProvider<Person, String>(personlist), 5, Person.class){
   @Override
   protected void onError(AjaxRequestTarget target){
      // target.add(feedbackPanel); 
   }
   @Override
   protected void onCancel(AjaxRequestTarget target){
      target.appendJavaScript("setActionCell();");
   }
   @Override
   protected void onDelete(AjaxRequestTarget target, IModel<Person> rowModel){
      target.appendJavaScript("renderCount();setActionCell();");
      // 削除の Person → rowModel.getObject();
   }
   @Override
   protected void onSave(AjaxRequestTarget target, IModel<Person> rowModel){
      target.appendJavaScript("setActionCell();");
      // 更新の Person → rowModel.getObject();
   }
   @Override
   protected void onAdd(AjaxRequestTarget target, Person newRow){
      target.appendJavaScript("renderCount();setActionCell();");
      // 削除の Person → newRow
   }
   @Override
   protected void onAfterRender(){
      super.onAfterRender();
      // style 適用のJS実行
      JavaScriptUtils.writeJavaScript(getResponse(), "renderCount();setActionCell();");
   }
});
queue(new Button("submit").add(AjaxEventBehavior.onEvent("click", t->{
   personlist.stream().forEach(e->{
      logger.debug("■ id="+e.id+"  "+e.name+" "+e.address+"  "+e.age);
   });
   t.appendJavaScript("alert('submit');");
})));

EditableGrid コンストラクタで指定する行追加及び編集の入力フィールド Component 設置するための
List<AbstractEditablePropertyColumn> を取得するメソッド
FeedbackPanel 設置しないので RequiredEditableTextFieldColumn を使わずに
EditableTextFieldPropertyColumn 等を使用する。

private List<AbstractEditablePropertyColumn<Person, String>> getColumnProperty(){
   List<AbstractEditablePropertyColumn<Person, String>> columns
 = new ArrayList<AbstractEditablePropertyColumn<Person, String>>();
   columns.add(new EditableTextFieldPropertyColumn<Person, String>(new Model<String>("Name"), "name"));
   columns.add(new EditableTextFieldPropertyColumn<Person, String>(new Model<String>("Address"), "address"));
   columns.add(new AbstractEditablePropertyColumn<Person, String>(new Model<String>("Age"), "age"){
      @SuppressWarnings("rawtypes")
      @Override
      public EditableCellPanel getEditableCellPanel(String componentId){
         EditableRequiredDropDownCellPanel<Person, String> panel
 = new EditableRequiredDropDownCellPanel<Person, String>(componentId, this
, Arrays.asList("10", "11", "12", "13", "14", "15"));
         ((DropDownChoice)panel.get("dropdown")).setNullValid(true);
         return panel;
      }
   });
   return columns;
}

EditableListDataProvider で指定する List<Person> personlist は、
EditableGrid を queue する前に生成しておく。
age の選択、DropDownChoice が、未選択=「選んでください。」でなく空白にしたいので、
EditableRequiredDropDownCellPanel でセットする DropDownChoice を
setNullValid(true) にする。

Maven pom.xml の記述は、、

<dependency>
    <groupId>org.wicketstuff</groupId>
    <artifactId>wicketstuff-editable-grid</artifactId>
    <version>8.4.0</version>
</dependency>

Wicket-stuff の Editable Grid のメリットは、、
行の編集、更新、追加、削除、→ 対象がDBだった場合に、
EditableListDataProvider の onメソッドで、
リアルタイムに1レコードずつの操作→反映が、簡単に記述構成できる点である。

Handsontable や、Tabulator では、アクションに対するAJAX通信を大量に書かなくては
ならないはずで、入力エラーも考慮するとかなり面倒くさい。。