EXTJS4.1做的單表維護,在同一個gridpanel中完成CRUD操作,數據處理方面借鑑webbuildder和pb中的處理,由於是剛開始學習extjs,可能有不合理的地方,希望大家批評,指正,下面是界面的效果圖,還有MVC的相關代碼。
我的一點小感受:
1。EXTJS的grid中可以設置CRUD的相關api, 但是這樣的話增加,刪除,修改的操作是在不同的事務裏面,這樣不利於事務的控制,比如說我修改了一條記錄,又增加了一條記錄,然後保存,會出現修改保存成功而增加保存失敗,界面上會讓用戶感覺混亂;
而且這種方式提交時默認提交當前表格中的數據,不會將原始記錄一起提交,後臺無法比對數據,比如有10個字段,由於用戶不知道哪個字段修改了,只能通過主鍵把所有字段都修改了;
再比如說當前要修改的記錄已經被另一個用戶修改,而你此時修改的話是不應該保存成功的,但是你通過主鍵去修改肯定是可以保存成功的,會造成數據覆蓋的的情況發生。
2.我的這種做法主要是借鑑了pb裏面的處理方式,將所有的增刪改查的數據統一搜集發送到後臺,後臺統一操作,將操作控制在一個事務裏面,後臺執行順序:刪除所有要刪除的數據, 刪除所有在修改的記錄集裏面主鍵發生改變的記錄,更新要修改的所有記錄, 插入新增的記錄和主鍵發生改變的新紀錄。
修改和刪除根據原始記錄來查詢,一旦根據原始記錄沒有查詢出來,說明這條記錄在另一個地方被修改或者刪除了,程序會主動拋出異常,回滾事務。
@Override
public void saveOrgsByAll(List<Org> newDatasList,
List<Org> deleteDatasList, List<Org> modifyDatasList)
throws Exception {
/*
* 執行順序爲刪除,修改,新增
*
*/
for (int i = 0; i < deleteDatasList.size(); i++) {
int deleteResult;
Org org = null;
org = deleteDatasList.get(i);
deleteResult = orgDao.deleteOrgByAll(org);
if (deleteResult == 0) {
throw new DeleteException();
}
}
//主鍵衝突時先刪除原始記錄
for (int i = 0; i < modifyDatasList.size(); i++) {
int deleteResult;
Org org = null;
org = modifyDatasList.get(i);
if ( ! org.getHospcode().equals(org.getOrigin().getHospcode()) ) {
deleteResult = orgDao.deleteOrgByAll(org.getOrigin());
if (deleteResult == 0) {
throw new DeleteException();
}
}
}
for (int i = 0; i < newDatasList.size(); i++) {
int andResult;
Org org = null;
org = newDatasList.get(i);
andResult = orgDao.insertOrg(org);
if (andResult == 0) {
throw new InsertException();
}
}
for (int i = 0; i < modifyDatasList.size(); i++) {
int updateResult;
int addResult;
Org org = null;
org = modifyDatasList.get(i);
if ( ! org.getHospcode().equals(org.getOrigin().getHospcode()) ) {
//主鍵衝突時先刪除再插入,刪除操作放在前面統一刪除,避免主鍵發生衝突
addResult = orgDao.insertOrg(org);
if (addResult == 0) {
throw new InsertException();
}
} else {
updateResult = orgDao.updateOrgByAll(org);
if (updateResult == 0) {
throw new UpdateException();
}
}
}
}
<update id="updateRoleByAll" parameterType="com.sesan.slis.core.model.Role" >
update af_role
<set>
<if test="roleId != origin.roleId">
roleId = #{roleId, jdbcType=VARCHAR},
</if>
<if test="roleName != origin.roleName">
roleName = #{roleName, jdbcType=VARCHAR},
</if>
<if test="description != origin.description">
description = #{description, jdbcType=VARCHAR},
</if>
<if test="status != origin.status">
status = #{status,jdbcType=INTEGER}
</if>
</set>
<where>
<if test="origin.roleId !=null">
and roleId =#{origin.roleId, jdbcType=VARCHAR}
</if>
<if test="origin.roleId ==null">
and roleId is null
</if>
<if test="origin.roleName !=null">
and roleName =#{origin.roleName, jdbcType=VARCHAR}
</if>
<if test="origin.roleName ==null">
and roleName is null
</if>
<if test="origin.description !=null">
and description =#{origin.description, jdbcType=VARCHAR}
</if>
<if test="origin.description ==null">
and description is null
</if>
<if test="origin.status !=null">
and status =#{origin.status, jdbcType=VARCHAR}
</if>
<if test="origin.status ==null">
and status is null
</if>
</where>
3.在這裏很想給大家介紹pb裏面數據處理的方式,覺得pb在數據處理方便做的非常好:
1.修改或者刪除數據可以通過主鍵,也可以通過主鍵和可更新的列,也可以通過主鍵和修改的列。(我借鑑的是第二種,只要我剛開始查詢出來的記錄中有一列發生改變(可能被其他用戶修改),就不給更新,在我讀的很多代碼裏面大多是直接通過主鍵,這種做法是最簡單但卻是最不安全的)
2..當主鍵發生衝突時,可以通過先刪除原始記錄再插入新紀錄,也可以直接更新。(如果直接更新的話有時會失敗,比如兩條記錄中主鍵發生互換,無論先更新哪一條都不會成功)
4.在pb的數據窗口裏面有4個內存緩衝區,它們是:
主緩衝區(PrimaryBuffer):界面上能看到的數據,可能被用戶修改。
過濾緩衝區(FilterBuffer):存放從主緩衝區中過濾掉的數據。
刪除緩衝區(DeleteBuffer):存放從主緩衝區中刪除掉的數據。
原始緩衝區(OriginalBuffer):存放從數據庫裏檢索到的原始數據。
此外數據窗口上有行和列的修改狀態
NotModified!:行和列的值是最初從數據庫中檢索出的值,
DataModified!:行和列的值在最初檢索後發生了改變。
New!:行是新行單並未賦值。
NewModified!:新行且某些列被賦值。
在 pb中正是有了緩衝區和標誌位的存在,pb的後臺纔會很容易按照事先約定的方式(第3點提到的)來生成CRUD的sql語句了。
其實這些技術在EXTJS的gridpanel中有的地方也有實現,比如刪除的數據可以通過store.getRemovedRecords( )來得到,數據修改了可以通過record.dirty()來判斷。但是想獲取原始數據卻沒有辦法(也可能是我沒有找到,如果有的話希望各位高手能指導一下),而且新行的標誌位也沒有,所以我在程序中是通過自己添加新行標誌和保存原始數據來實現的,這個處理方式我在webbuilder中看到過,所以就借鑑過來。
store.load({
scope: store,
callback: function(records, operation, success) {
if (success==true){
this.each(function(b) {
b.__origin = Ext.apply({}, b.data);
b.__isNew = undefined
})
}
}
});
view 層代碼
Ext.define('rm.view.OrgMngMainView', {
extend: 'Ext.panel.Panel',
alias: 'widget.OrgMngMainView',
requires: [
'Ext.ux.CheckColumn'
],
height: 471,
width: 591,
layout: {
type: 'border'
},
title: '組織機構維護',
closable: true,
border : false,
initComponent: function() {
var me = this;
Ext.applyIf(me, {
dockedItems: [
{
xtype: 'toolbar',
dock: 'top',
items: [{
xtype : 'button',
iconCls : 'icon-add',
text : '添加',
action : 'addAction'
}, {
xtype : 'tbseparator'
}, {
xtype : 'button',
iconCls : 'icon-delete',
text : '刪除',
action : 'delAction'
}, {
xtype : 'button',
iconCls : 'icon-save',
text : '保存',
action : 'saveAction'
} , {
xtype : 'tbseparator'
}, {
xtype : 'button',
iconCls : 'icon-refresh',
text : '刷新',
action : 'refreshAction'
}
]
}
],
items: [
{
xtype: 'gridpanel',
region: 'center',
margin: '1 1 1 1',
title: '組織機構維護',
columnLines: true,
store: 'OrgStore',
columns: [{
xtype: 'rownumberer',
text:'行號',
width:50
},{
xtype: 'gridcolumn',
dataIndex: 'hospcode',
text: '醫院編碼',
editor: {
xtype: 'textfield'
}
},{
xtype: 'gridcolumn',
dataIndex: 'hospname',
text: '醫院名稱',
width: 300,
editor: {
xtype: 'textfield'
}
},{
xtype: 'checkcolumn',
header: '啓用',
dataIndex: 'status',
width: 60,
editor: {
xtype: 'checkbox',
cls: 'x-grid-checkheader-editor'
}
}
],
plugins: [
new Ext.grid.plugin.CellEditing({
clicksToEdit: 1
})
],
viewConfig: {
enableTextSelection: true
}
}
]
});
me.callParent(arguments);
}
});
Ext.define('rm.model.OrgModel', {
extend: 'Ext.data.Model',
requires: [
'Ext.data.Field'
],
fields: [
{
name: 'hospcode',
type: 'string'
},
{
name: 'hospname',
type: 'string'
},
{
name: 'status', type: 'boolean'
},
{
name:'text' ,
convert: function(value, record) {
return record.get("hospcode")+"|"+record.get("hospname");
}
}
]
});
Ext.define('rm.store.OrgStore', {
extend: 'Ext.data.Store',
requires: [
'rm.model.OrgModel',
'Ext.data.proxy.Ajax',
'Ext.data.reader.Json'
],
constructor: function(cfg) {
var me = this;
cfg = cfg || {};
me.callParent([Ext.apply({
model: 'rm.model.OrgModel',
storeId: 'OrgStore',
proxy: {
type: 'ajax',
url: 'org/getOrgs',
reader: {
type: 'json'
}
}
}, cfg)]);
}
});
Ext.define('rm.controller.OrgMainController', {
extend: 'Ext.app.Controller',
refs: [
{
ref: 'window',
selector: 'OrgMngMainView'
}
],
initWindow: function(component, eOpts) {
this.refresh();
},
refresh :function( button, e, eOpts){
var window = this.getWindow();
var grid = window.down("gridpanel");
var store = grid.getStore();
store.load({
scope: store,
callback: function(records, operation, success) {
if (success==true){
this.each(function(b) {
b.__origin = Ext.apply({}, b.data);
b.__isNew = undefined
})
}
}
});
},
add:function( button, e, eOpts){
var window = this.getWindow();
var grid = window.down('gridpanel');
var store = grid.getStore();
var row ;
var record;
var edit = grid.plugins[0] ;
var gridsel = grid.getSelectionModel().getSelection( ) ;
if (gridsel.length>0){
row =store.indexOf( gridsel[0] );
}else{
row = 0;
}
store.insert(row,{ });
grid.getSelectionModel().select(row);//滾動到當前行
record=store.getAt(row);
record.__isNew=true;
record.commit();
edit.startEditByPosition({row:row,column:1});
},
delete:function( button, e, eOpts){
var window = this.getWindow();
var grid = window.down('gridpanel');
var store = grid.getStore();
var sels= grid.getSelectionModel().getSelection( ) ;
var row;
var i;
if (sels.length<=0){
return ;
}
store.remove(sels);
},
save :function( button, e, eOpts){
var window = this.getWindow();
var grid = window.down('gridpanel');
var store = grid.getStore();
var j = store.getCount();
var i;
var record;
var data;
var m=[],n=[],d=[];
var deleteRerords ;
//獲取新增或修改的記錄
for(i=j-1;i>=0;i--){
record=store.getAt(i);
if(record.dirty){
data = Ext.apply({},record.data )
if(record.__isNew){
n.push(data);
}else{
data.origin=record.__origin;
m.push(data);
}
}else if( record.__isNew ){
store.remove(record);
}
}
//獲取刪除的記錄
deleteRerords = store.getRemovedRecords( );
for(i=0 ;i<deleteRerords.length;i++){
if( !deleteRerords[i].__isNew ){
d.push(deleteRerords[i].__origin);
}
}
//ajax提交數據
Ext.Ajax.request({
scope: this,
url: 'org/saveOrgs',
params: {
newDatas:Ext.encode(n),
deleteDatas:Ext.encode(d),
modifyDatas:Ext.encode(m)
},
success : function(response, options){
this.refresh( );
}
});
},
init: function(application) {
this.control({
'OrgMngMainView button[action=addAction]' : {
click : this.add
},
'OrgMngMainView button[action=delAction]' : {
click : this.delete
},
'OrgMngMainView button[action=saveAction]' : {
click : this.save
},
'OrgMngMainView button[action=refreshAction]' : {
click : this.refresh
},
"OrgMngMainView": {
afterrender: this.initWindow
}
});
}
});