Eclipse+JSF+JPAで作るアプリ(16)―Primefaces データテーブルの設定 後半(3) グリッド編集(cellEditor,rowEditor)

Primefacesのデータテーブルのいろいろ、ひとまずの最後はグリッド編集(セル編集)です。
こちらも、基本的に簡単です。
PrimefacesのShowcaseを見ての通り、2通りの編集方法があります。
http://www.primefaces.org/showcase/ui/data/datatable/edit.xhtml

  • 鉛筆アイコンをクリックして行を変更、チェックアイコンかクローズアイコンで保存かキャンセルを選べるモード
  • クリックしてセルまたは行を編集するモード。

ユーザー編集は、前者で行うこととします。
実装すると、次の画面のようになります。
f:id:tshix:20150801122638p:plain

このセッションでは、エンターキーへの対応がShowcaseにない注目すべき点です。

セルの編集を有効にする。

セルの編集を有効にするには、以下の流れになります。

  1. <p:datatable>タグの editable="true" を設定する。
  2. <p:datatable>のタグ内に、<p:ajax>タグで event="rowEdit", event="rowEditCancel"のイベントを追加する。
  3. 各カラムに対して、<p:cellEditor>タグを定義する。
  4. <p:cellEditor>タグ内で、<f:facet name="output">, <f:facet name="input">で編集時、参照時のコントロールを定義する。
  5. 鉛筆マークのアイコンを出すため、<p:rowEditor/>とだけ書かれたカラムを定義する。

具体的には、次のようにxhtmlに記述します。

1. <p:datatable>タグの editable="true" を設定する。
	<p:dataTable id="userList" 
		value="#{editUserView.userModel}" var="user"
		editable="true"

	/>
2. <p:datatable>のタグ内に、<p:ajax>タグで event="rowEdit", event="rowEditCancel"のイベントを追加する。
	<p:dataTable …/><p:ajax event="rowEdit" listener="#{editUserView.onRowEdit}"
			update=":growl"/>
		<p:ajax event="rowEditCancel" listner="#{editUserView.onRowEditCancel}"
			update="growl"/></p:dataTable>

EditUserView.javaには、行編集のハンドラを実装します。

	public void onRowEdit(RowEditEvent event) {
		User user = (User)event.getObject();
		UserManager.updateUser(user);
		ViewUtil.AddMessage("ユーザの編集", "ユーザ "+ user.getName() + " を更新しました。");
	}
	 
	public void onRowEditCancel(RowEditEvent event) {
		ViewUtil.AddMessage("ユーザの編集", "編集をキャンセルしました。");
	}
3. 各カラムに対して、<p:cellEditor>タグを定義する。
4. <p:cellEditorタグ内で、<f:facet name="output">, <f:facet name="input">で編集時、参照時のコントロールを定義する。

同時に記述します。

	<p:column headerText="アカウント" sortBy="#{user.account}" 
		filterBy="#{user.account}" filterMatchMode="contains">
		<p:cellEditor>
			<f:facet name="output"><p:outputLabel value="#{user.account}" /></f:facet>
			<f:facet name="input"><p:inputText value="#{user.account}"/></f:facet>
		</p:cellEditor>
	</p:column>
  • facetのname="output"が表示用のコントロールです。
  • facetのname="input"が編集用のコントロールです。
5. 鉛筆マークのアイコンを出すため、<p:rowEditor/>とだけ書かれたカラムを定義する。
	<p:column style="width:32px">
		<p:rowEditor/>
	</p:column>

以上で、次の画面ように編集できるようになります。
f:id:tshix:20150801122638p:plain

Enterキーの制御

Enterキーでサブミットされるのを回避する。

このままでは、セルの編集中にEnterキーを押すとフォームがサブミットされ、以下のように削除ボタンが押されてしまいます。
f:id:tshix:20150801122255p:plain

これを回避するため、<h:form>タグのonkeypressアトリビュートに次のように記述します。

	<h:form id="userListForm" onkeypress="if( event.keyCode == 13 ) return false;">

もっと短い書き方はこうです。

	<h:form id="userListForm" onkeypress="return event.keyCode != 13;">
Enterキーでセルの編集を確定する。

また、セルの編集中、チェックアイコンをクリックしないと編集が確定されません。
これを、セルの編集中にEnterキーを押すと確定するようにするには、以下のようにxhtmlに記述します。

<h:outputScript>
$(document).on("keydown", ".ui-cell-editor-input input", function(event) {
    if (event.keyCode == 13) {
        $(this).closest("tr").find(".ui-row-editor .ui-icon-check").click();
    }
});
</h:outputScript>

jquery - Using return/enter key to save edited cell in Primefaces 3.4 in-cell editable datatable - Stack Overflowを参照ください。


最後に、これまでのデータテーブルの設定を変えたxhtmlの全体を貼り付けておきます。

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:p="http://primefaces.org/ui"
      >
<h2>ユーザー編集</h2>
<h:outputScript>
$(document).on("keydown", ".ui-cell-editor-input input", function(event) {
    if (event.keyCode == 13) {
        $(this).closest("tr").find(".ui-row-editor .ui-icon-check").click();
    }
});
</h:outputScript>

<p:growl id="growl" showDetail="true" autoUpdate="true"/>

<h:form id="addUserForm">
	<p:panelGrid columns="2" >
		<p:outputLabel value="アカウント:" />
		<p:inputText id="account" value="#{editUserView.account}" 
			required="true" requiredMessage="アカウント名は必須です。"/>
		
		<p:outputLabel value="名前:" />
		<p:inputText id="name" value="#{editUserView.name}" 
			required="true" requiredMessage="名前は必須です。"/>
		
		<p:outputLabel value="パスワード:" />
		<p:password value="#{editUserView.password}" />
	
		<p:outputLabel value="E-mail:" />
		<p:inputText id="email" value="#{editUserView.email}" />
		
		<p:outputLabel value="管理者権限:" />
		<p:selectBooleanCheckbox id="isAdmin" value="editUserView.isAdmin" style="align:center"/>
		
	</p:panelGrid>
	<p:messages />
	<p:commandButton value="ユーザの追加" action="#{editUserView.addUser}" 
		update=":userListForm:userList" />
</h:form>
<br />
<p:separator />
<br />
<h2>ユーザー一覧</h2>
<h:form id="userListForm" onkeypress="if( event.keyCode == 13 ) return false;">
	<p:commandButton id="removeButton"
		value="選択したユーザを削除"
		action="#{editUserView.removeUser}" update=":userListForm"
		disabled="#{empty editUserView.selectedUsers}">
		<p:confirm header="確認"
			message="選択したユーザを削除しますか?" icon="ui-icon-info" />
		<p:confirmDialog global="true" showEffect="explode" hideEffect="slide">
			<p:commandButton value="はい" type="button"
				styleClass="ui-confirmdialog-yes" icon="ui-icon-check" />
			<p:commandButton value="いいえ" type="button"
				styleClass="ui-confirmdialog-no" icon="ui-icon-close" />
		</p:confirmDialog>
	</p:commandButton>
	<br/>
	<br/>

	<p:dataTable id="userList" 
		value="#{editUserView.userModel}" var="user"
		editable="true"
		selection="#{editUserView.selectedUsers}"
		rowKey="#{user.id}"
		paginator="true" paginatorPosition="top"
		rows="10" rowsPerPageTemplate="5,10,15,30,50"
		sortMode="multiple" stickyHeader="true">
		
		<p:ajax event="contextMenu" update=":userListForm:removeButton" />
		<p:ajax event="rowSelect" update=":userListForm:removeButton" />
		<p:ajax event="rowUnselect" update=":userListForm:removeButton" />
		<p:ajax event="rowSelectCheckbox" update=":userListForm:removeButton" />
		<p:ajax event="rowUnselectCheckbox" update=":userListForm:removeButton" />
		<p:ajax event="toggleSelect" update=":userListForm:removeButton" />
		<p:ajax event="rowEdit" listener="#{editUserView.onRowEdit}"
			update=":growl"/>
		<p:ajax event="rowEditCancel" listner="#{editUserView.onRowEditCancel}"
			update="growl"/>
		
		<p:column selectionMode="multiple" style="width:16px;text-align:center"/>

		<p:column headerText="アカウント" sortBy="#{user.account}" 
			filterBy="#{user.account}" filterMatchMode="contains">
			<p:cellEditor>
			<f:facet name="output"><p:outputLabel value="#{user.account}" /></f:facet>
			<f:facet name="input"><p:inputText value="#{user.account}"/></f:facet>
			</p:cellEditor>
		</p:column>

		<p:column headerText="名前" sortBy="#{user.name}" 
			filterBy="#{user.name}" filterMatchMode="contains">
			<p:cellEditor>
				<f:facet name="output"><p:outputLabel value="#{user.name}" /></f:facet>
				<f:facet name="input"><p:inputText value="#{user.name}"/></f:facet>
			</p:cellEditor>
		</p:column>

		<p:column headerText="e-mail" sortBy="#{user.email}" 
			filterBy="#{user.email}" filterMatchMode="contains">
			<p:cellEditor>
				<f:facet name="output"><p:outputLabel value="#{user.email}" /></f:facet>
				<f:facet name="input"><p:inputText value="#{user.email}"/></f:facet>
			</p:cellEditor>
		</p:column>

		<p:column headerText="管理者権限" sortBy="#{user.isAdmin}" 
			filterBy="#{user.isAdmin}" filterMatchMode="exact" style="text-align:center"> 
			<p:cellEditor>
				<f:facet name="output"><p:selectBooleanCheckbox value="#{user.isAdmin}" disabled="true"/></f:facet>
				<f:facet name="input"><p:selectBooleanCheckbox value="#{user.isAdmin}" /></f:facet>
			</p:cellEditor>
		</p:column>
		
		<p:column style="width:32px">
			<p:rowEditor/>
		</p:column>
		
	</p:dataTable>
	
	<p:contextMenu for="userList">
		<p:menuitem value="パスワードの編集" icon="ui-icon-pencil"
			update="dialog"
			oncomplete="PF('passwordDialog').show()">
			<f:setPropertyActionListener value="#{editUserView.selectedUsers[0].password}" target="#{editUserView.newPassword}"></f:setPropertyActionListener>
		</p:menuitem>
	</p:contextMenu>
	
</h:form>

<p:dialog id="dialog" header="パスワードの編集" widgetVar="passwordDialog"
	modal="true" showEffect="fold" hideEffect="explode" resizable="false"
	dynamic="true">
	<h:form id="dialogForm">
		<p:outputPanel id="passwordEdit" style="text-align:center;">
			<p:panelGrid columns="2">
				<f:facet name="header">
					<p:outputLabel
						value="#{editUserView.selectedUsers[0].name.concat('のパスワード変更')}" />
				</f:facet>
				<p:outputLabel value="パスワード" />
				<p:password value="#{editUserView.newPassword}" redisplay="true" />
				<p:outputLabel value="見えるパスワード" />
				<p:inputText value="#{editUserView.newPassword}" />
			</p:panelGrid>
			<p:commandButton value="OK" icon="ui-icon-check"
				action="#{editUserView.updatePassword}"
				oncomplete="PF('passwordDialog').hide()" />
			<p:commandButton value="キャンセル" icon="ui-icon-close"
				oncomplete="PF('passwordDialog').hide()" />
		</p:outputPanel>
	</h:form>
</p:dialog>

</html>