Full Ajax な CRUD アプリケーション その4
前回でデータ一覧を表示させる事が出来るようになりました。今回はデータの更新関係についてサンプルを作っていきます。
新規追加ウィンドウの作成
まずはモックアップから。ExtJSのダイナミックフォームを使用します。
フォームウィンドウのソース
Ext.QuickTips.init(); Ext.form.Field.prototype.msgTarget = 'side'; var FormWindow = function(){ var extWin = null; this.show = function(title, url){ if (extWin == null) { var form = new Ext.FormPanel({ method: 'POST', id: 'employee-form', baseCls: 'x-plain', labelWidth: 40, defaults: {width: 190}, defaultType: 'textfield', bodyStyle:'padding:5px 10px 0', items: [{ fieldLabel: 'Id', id: 'id', xtype: 'numberfield' },{ fieldLabel: '氏名', id: 'name', allowBlank: false },{ fieldLabel: '職種', id: 'jobType' }, { fieldLabel: '給料', id: 'salary', xtype: 'numberfield' }, { fieldLabel: '部署', id: 'department' }] }); extWin = new Ext.Window({ title: '会社員情報 : ' + title, width: 300, height:220, minWidth: 300, minHeight: 250, layout: 'fit', closeAction :'hide', plain:true, bodyStyle:'padding:5px;', buttonAlign:'center', items: form, buttons: [{ text: 'OK' },{ text: 'キャンセル' }] }); } extWin.show(); } }; var formWindow = new FormWindow();
Ext.Window()でウィンドウを作成、 Ext.FormPanel()でフォームを作成しウィンドウ内にセットします。これをFormWindow()というファンクションにしました。
イベントハンドラーのソース
var addActionHandler = function(){ formWindow.show('新規追加', './create'); Ext.getDom('id').readOnly = true; }
ツールバーの追加ボタンに割り当てるハンドラーです。ウインドウのタイトルとフォームアクションを指定しています。
イベントハンドラーを割り当てる
var addAction = new Ext.Action({ text: '追加', iconCls: 'addIcon', handler: addActionHandler });
ツールバーにaddActionHandlerを割り当てました。
employee.js - フォームウィンドウを追加
Ext.onReady(function(){ // HANDLER var addActionHandler = function(){ formWindow.show('新規追加', './create'); Ext.getDom('id').readOnly = true; } // MODEL, CONTROL var fields = [ 'id', 'name', 'jobType', 'salary', 'department' ]; var store = new Ext.data.Store({ proxy: new Ext.data.HttpProxy({ url: './employee.json', method: 'GET' }), reader: new Ext.data.JsonReader({ root: 'root', totalProperty: 'totalProperty', fields: fields }) }); // VIEW Ext.QuickTips.init(); Ext.form.Field.prototype.msgTarget = 'side'; var FormWindow = function(){ var extWin = null; this.show = function(title, url){ if (extWin == null) { var form = new Ext.FormPanel({ method: 'POST', id: 'employee-form', baseCls: 'x-plain', labelWidth: 40, defaults: {width: 190}, defaultType: 'textfield', bodyStyle:'padding:5px 10px 0', items: [{ fieldLabel: 'Id', id: 'id', xtype: 'numberfield' },{ fieldLabel: '氏名', id: 'name', allowBlank: false },{ fieldLabel: '職種', id: 'jobType' }, { fieldLabel: '給料', id: 'salary', xtype: 'numberfield' }, { fieldLabel: '部署', id: 'department' }] }); extWin = new Ext.Window({ title: '会社員情報 : ' + title, width: 300, height:220, minWidth: 300, minHeight: 250, layout: 'fit', closeAction :'hide', plain:true, bodyStyle:'padding:5px;', buttonAlign:'center', items: form, buttons: [{ text: 'OK' },{ text: 'キャンセル' }] }); } extWin.show(); } }; var formWindow = new FormWindow(); var colModel = new Ext.grid.ColumnModel([ {header: "Id", width: 75, sortable: true, dataIndex: 'id'}, {id:'name', header: "氏名", width: 160, sortable: true, dataIndex: 'name'}, {header: "職種", width: 75, sortable: true, dataIndex: 'jobType'}, {header: "給与", width: 75, sortable: true, dataIndex: 'salary'}, {header: "部署", width: 85, sortable: true, dataIndex: 'department'} ]); var addAction = new Ext.Action({ text: '追加', iconCls: 'addIcon', handler: addActionHandler }); var editAction = new Ext.Action({ text: '編集', iconCls: 'editIcon' }); var deleteAction = new Ext.Action({ text: '削除', iconCls: 'deleteIcon' }); var findAction = new Ext.Action({ text: '検索', iconCls: 'findIcon' }); var tbar = [ addAction, '-', editAction, deleteAction, '-', findAction ]; var pageSize = 20; var bbar = new Ext.PagingToolbar({ id: 'pagingToolbar', pageSize: pageSize, store: store, displayInfo: true, displayMsg: '社員の一覧 {2} 件中 {0} - {1} 件目', emptyMsg: "社員の一覧はありません" }); var grid = new Ext.grid.GridPanel({ title:'社員管理', stripeRows: true, autoExpandColumn: 'name', height:523, width:600, store: store, colModel: colModel, tbar: tbar, bbar: bbar }); // INIT grid.render('grid-employee'); store.load({params:{start:0, limit:pageSize}}); });
ブラウザからhttp://localhost:8080/ajax-app/employee/index.htmlを開き追加ボタンをクリックすると以下のようにフォームウィンドウが表示されます。
次はフォームのボタンにハンドラーをセットします。まずはOKボタンから。
OKボタンのハンドラー
text: 'OK', handler: function(){ form.getForm().submit({ url: url, waitMsg: 'サブミットしています...', // Submit時に表示するメッセージ。二重サブミット防止にもなる。 success: function(form, action) { Ext.select('.x-tbar-loading').item(0).dom.click(); extWin.close(); extWin = null; }, failure: function(form, action) { if (action.failureType == 'client') { // do nothing } else if (action.failureType == 'connect') { Ext.MessageBox.alert('コネクションエラー', '通信時にエラーが発生しました。'); } else { Ext.MessageBox.alert('失敗', title + 'できませんでした。'); Ext.select('.x-tbar-loading').item(0).dom.click(); extWin.close(); extWin = null; } } }); }
urlはFormWindow.show(title, url)のurlです。この様にボタン毎にフォームアクションの指定が出来ます。
Ext.select('.x-tbar-loading').item(0).dom.click(); は裏技的なツールバーのリフレッシュボタンクリックイベントです。
キャンセルボタンのハンドラー
text: 'キャンセル', handler: function(){ form.getForm().reset(); extWin.close(); extWin = null; }
employee.js - OKボタンとキャンセルボタンのハンドラーをセット
Ext.onReady(function(){ // HANDLER var addActionHandler = function(){ formWindow.show('新規追加', './create'); Ext.getDom('id').readOnly = true; } // MODEL, CONTROL var fields = [ 'id', 'name', 'jobType', 'salary', 'department' ]; var store = new Ext.data.Store({ proxy: new Ext.data.HttpProxy({ url: './employee.json', method: 'GET' }), reader: new Ext.data.JsonReader({ root: 'root', totalProperty: 'totalProperty', fields: fields }) }); // VIEW Ext.QuickTips.init(); Ext.form.Field.prototype.msgTarget = 'side'; var FormWindow = function(){ var extWin = null; this.show = function(title, url){ if (extWin == null) { var form = new Ext.FormPanel({ method: 'POST', id: 'employee-form', baseCls: 'x-plain', labelWidth: 40, defaults: {width: 190}, defaultType: 'textfield', bodyStyle:'padding:5px 10px 0', items: [{ fieldLabel: 'Id', id: 'id', xtype: 'numberfield' },{ fieldLabel: '氏名', id: 'name', allowBlank: false },{ fieldLabel: '職種', id: 'jobType' }, { fieldLabel: '給料', id: 'salary', xtype: 'numberfield' }, { fieldLabel: '部署', id: 'department' }] }); extWin = new Ext.Window({ title: '会社員情報 : ' + title, width: 300, height:220, minWidth: 300, minHeight: 250, layout: 'fit', closeAction :'hide', plain:true, bodyStyle:'padding:5px;', buttonAlign:'center', items: form, buttons: [{ text: 'OK', handler: function(){ form.getForm().submit({ url: url, waitMsg: 'サブミットしています...', // Submit時に表示するメッセージ。二重サブミット防止にもなる。 success: function(form, action) { Ext.select('.x-tbar-loading').item(0).dom.click(); extWin.close(); extWin = null; }, failure: function(form, action) { if (action.failureType == 'client') { // do nothing } else if (action.failureType == 'connect') { Ext.MessageBox.alert('コネクションエラー', '通信時にエラーが発生しました。'); } else { Ext.MessageBox.alert('失敗', title + 'できませんでした。'); Ext.select('.x-tbar-loading').item(0).dom.click(); extWin.close(); extWin = null; } } }); } },{ text: 'キャンセル', handler: function(){ form.getForm().reset(); extWin.close(); extWin = null; } }] }); } extWin.show(); } }; var formWindow = new FormWindow(); var colModel = new Ext.grid.ColumnModel([ {header: "Id", width: 75, sortable: true, dataIndex: 'id'}, {id:'name', header: "氏名", width: 160, sortable: true, dataIndex: 'name'}, {header: "職種", width: 75, sortable: true, dataIndex: 'jobType'}, {header: "給与", width: 75, sortable: true, dataIndex: 'salary'}, {header: "部署", width: 85, sortable: true, dataIndex: 'department'} ]); var addAction = new Ext.Action({ text: '追加', iconCls: 'addIcon', handler: addActionHandler }); var editAction = new Ext.Action({ text: '編集', iconCls: 'editIcon' }); var deleteAction = new Ext.Action({ text: '削除', iconCls: 'deleteIcon' }); var findAction = new Ext.Action({ text: '検索', iconCls: 'findIcon' }); var tbar = [ addAction, '-', editAction, deleteAction, '-', findAction ]; var pageSize = 20; var bbar = new Ext.PagingToolbar({ id: 'pagingToolbar', pageSize: pageSize, store: store, displayInfo: true, displayMsg: '社員の一覧 {2} 件中 {0} - {1} 件目', emptyMsg: "社員の一覧はありません" }); var grid = new Ext.grid.GridPanel({ title:'社員管理', stripeRows: true, autoExpandColumn: 'name', height:523, width:600, store: store, colModel: colModel, tbar: tbar, bbar: bbar }); // INIT grid.render('grid-employee'); store.load({params:{start:0, limit:pageSize}}); });
employee.jsは以上です。続いて呼び出されるフォームアクションを作成します。ソースは以下の通りです。
EmployeeAction.java
package webapplication.action; import javax.annotation.Resource; import org.seasar.framework.beans.util.Beans; import org.seasar.struts.annotation.ActionForm; import org.seasar.struts.annotation.Execute; import org.seasar.struts.util.ResponseUtil; import webapplication.entity.Employee; import webapplication.form.EmployeeForm; import webapplication.service.EmployeeService; public class EmployeeAction { @ActionForm @Resource protected EmployeeForm employeeForm; @Resource protected EmployeeService employeeService; @Execute(validator = false) public String create() { Employee entity = Beans.createAndCopy(Employee.class, employeeForm) .execute(); employeeService.insert(entity); ResponseUtil.write("{success:true}", "application/json"); return null; } }
EmployeeForm.java
package webapplication.form; import org.seasar.struts.annotation.IntegerType; import org.seasar.struts.annotation.Required; public class EmployeeForm { @Required @IntegerType public String id; public String name; public String jobType; @IntegerType public String salary; public String department; }
ごく一般的なSAStrutsのアクションのロジックと同じです。違う点は
- バリデートチェックはクライアント側のJavaScriptで行う為アクション側ではチェックしない。
- 返り値はnull。途中でResponseUtil.write("{success:true}", "application/json");とありJSONが返される。
です。
以上で実装は完了です。ブラウザでhttp://localhost:8080/ajax-app/employee/index.htmlをアクセスし追加ボタンを実行して確認します。