Created
November 15, 2011 10:11
-
-
Save seraphy/1366637 to your computer and use it in GitHub Desktop.
ExtJS4のストアとグリッドの使い方メモ
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ASP.NET Ajaxの非同期通信の終了時ハンドラを登録し、 | |
// 非同期通信時のエラーを表示する | |
Sys.WebForms.PageRequestManager.getInstance() | |
.add_endRequest(function (sender, args) { | |
if (args.get_error() != undefined) { | |
// エラーが発生していれば、それを表示する. | |
var msg = args.get_error().message; | |
args.set_errorHandled(true); | |
Ext.MessageBox.show({ | |
title: "エラー", | |
msg: msg, | |
buttons: Ext.MessageBox.OK, | |
icon: Ext.MessageBox.ERROR | |
}); | |
} | |
}); | |
// 理由・日付の複合フィールドの型を定義 | |
// (データモデルとして作成しない. JSON変換後、項目がたくさん出てしまうため) | |
Ext.define('DateAndReason', { | |
dt: null, | |
reason: null, | |
statics: { | |
// プリミティブなオブジェクトから当モデルを構築 | |
convert: function (v, rec) { | |
var inst = Ext.create('DateAndReason'); | |
inst.dt = v.dt; | |
inst.reason = v.reason; | |
return inst; | |
}, | |
// 等値比較 | |
isEqual: function (currentValue, newValue) { | |
if (currentValue && newValue) { | |
// 双方とも値があれば、その中身で比較する. | |
var dt1 = currentValue.dt; | |
var dt2 = newValue.dt; | |
var reason1 = currentValue.reason; | |
var reason2 = newValue.reason; | |
// 日付はオブジェクトなので、そのまま比較できない. | |
// エポックタイムの数値にしてから比較する. | |
var dtMatched = false; | |
if (Ext.isDate(dt1) && Ext.isDate(dt2)) { | |
dtMatched = (dt1.getTime() === dt2.getTime()); | |
} else { | |
dtMatched = (dt1 == dt2); | |
} | |
return dtMatched && (reason1 == reason2); | |
} | |
if (!currentValue && !newValue) { | |
// 双方ともnullまたはundefined | |
return true; | |
} | |
// いずれか一方がnullまたはundefinedであれば不一致 | |
return false; | |
} | |
} | |
}); | |
// 理由・日付の複合フィールドの型のソートタイプを追加 | |
Ext.apply(Ext.data.SortTypes, { | |
asDateAndReason: function (v) { | |
if (v && v.dt) { | |
if (v.reason) { | |
// 理由ありの場合は日付を無視する. | |
return ""; | |
} | |
return v.dt; | |
} | |
return ""; | |
} | |
}); | |
// ソートは単純な関数でもOK. | |
var statusSorter = function (value) { | |
var v = value.text; | |
if (v) { | |
return v; | |
} | |
return ""; | |
}; | |
// null項目はソート時に移動対象とならないため. | |
var stringSorter = function (value) { | |
if (value) { | |
return value; | |
} | |
return ""; | |
}; | |
// レコードタイプ | |
Ext.define('FooType', { | |
extend: 'Ext.data.Model', | |
idProperty: 'rowid', // レコードを一意に決めるIDフィールドの名前、省略時は「id」が使われる. | |
fields: [ | |
{ name: 'rowid' }, // レコードのphantom(新規)/dirty(更新)を判定するために一意のIDとして使われる. | |
{ name: 'status', sortType: statusSorter }, | |
{ name: 'enforcementDate', convert: DateAndReason.convert, sortType: 'asDateAndReason' }, | |
{ name: 'recipientKind', defaultValue: 0 }, | |
{ name: 'recipientName' }, | |
{ name: 'recipientComment' } | |
], | |
// setメソッドでダーティ判定するときに呼び出される等値比較メソッド. | |
// (※ isEqualメソッドは、アンドキュメンテッド) | |
// DateAndReasonの複合型の変更判定を、インスタンスが等しいかではなく | |
// その内容で等しいか比較するようにオーバーライドする. | |
isEqual: function (currentValue, newValue) { | |
if (currentValue && currentValue instanceof DateAndReason) { | |
return DateAndReason.isEqual(currentValue, newValue); | |
} | |
return this.callParent(arguments); | |
} | |
}); | |
// データストア | |
var store = Ext.create('Ext.data.Store', { | |
model: 'FooType', | |
proxy: { | |
type: 'ajax', | |
url: 'FooType.ashx', // 例えばASP.NETのジェネリックハンドラでJSONを返す | |
reader: { | |
type: 'json' | |
} | |
} | |
}); | |
// データ変更イベント | |
store.on('update', function (iStore, iModel, iOpe, iOpts) { | |
// updateInputField(); | |
// データが変更されるたびに呼び出されるので、ここで | |
// 新規・編集中レコード数を確認して、ボタン等の制御をすることができる. | |
}); | |
// 検索処理 | |
var logicSearch = function () { | |
// ASP.NET Ajaxで処理中であるかの判定 | |
var ajaxMan = Sys.WebForms.PageRequestManager.getInstance(); | |
if (ajaxMan.get_isInAsyncPostBack()) { | |
Ext.Msg.alert("通信中です、しばらくお待ちください。"); | |
return; | |
} | |
// ExtJS4のストアのロード | |
store.load({ | |
params: { | |
userid: 'aaa', // パラメータ | |
subjectid: 'bbb' | |
}, | |
scope: this, | |
callback: function (records, operation, success) { | |
if (!success) { | |
Ext.MessageBox.show({ | |
title: "エラー", | |
msg: "通信に失敗しました。", | |
buttons: Ext.MessageBox.OK, | |
icon: Ext.MessageBox.ERROR | |
}); | |
} | |
} | |
}); | |
// store.loadData([{rowid: ....}]); 形式で配列からロードすることも可 | |
}; | |
// 検索ボタン | |
Ext.create('Ext.button.Button', { | |
text: '検索', | |
width: 80, | |
id: 'searchBtn', | |
// disabled: ディセーブルの制御, | |
handler: function () { | |
// 編集中のレコードの有無の確認 | |
var modifiedRecs = store.getUpdatedRecords(); | |
var newRecs = store.getNewRecords(); | |
if (modifiedRecs.length > 0 || newRecs.length > 0) { | |
return Ext.Msg.confirm( | |
"確認", | |
"未保存のデータはキャンセルされます。", | |
function (ret) { | |
if (ret == "yes") { | |
logicSearch(); | |
} | |
} | |
); | |
} else { | |
logicSearch(); | |
} | |
}, | |
renderTo: 'searchBtnRef' // タグの書き出し先 | |
}); | |
// ページのアンロード時の編集中データの警告 | |
// Ext.EventManager.on(window, 'beforeunload', function (e) { | |
// (ExtJS標準の方法は、Chomeで不良なのでレガシーな方法で代用する.) | |
window.onbeforeunload = function () { | |
var modifiedRecs = store.getUpdatedRecords(); | |
var newRecs = store.getNewRecords(); | |
if (modifiedRecs.length > 0 || newRecs.length > 0) { | |
return "未保存のデータが破棄されます。"; | |
} | |
}; | |
/////////////////////////////////////////////////////////////// | |
// データモデルを表示するグリッド | |
// チェックボックス式選択モデル | |
var selModel = | |
Ext.create('Ext.selection.CheckboxModel', { | |
checkOnly: true // チェックボックスによってのみ行選択する. | |
}); | |
// グリッド | |
var grid = Ext.create('Ext.grid.Panel', { | |
// title: 'タイトル', | |
columnLines: true, // セル間の縦線を引く | |
store: store, // バインドされるストア | |
stateful: true, | |
stateId: 'FooGridAAAAAAA', // グリッドのヘッダの幅などの状態を記憶する名前 | |
columns: [ // カラムはreconfigureメソッドで再配置可能 | |
{ | |
text: '状況', | |
align: 'center', | |
width: 40 * fontScale, | |
sortable: true, | |
draggable: false, | |
hideable: false, | |
dataIndex: 'status', | |
renderer: function (value, metaData, record, rowIndex, colIndex, store, view) { | |
if (record.dirty) { | |
// 非表示のものを含む、レコードのいずれかのフィールドが変更されていれば | |
// このセルにダーティマークをつける. | |
// metaDataでスタイルなどの情報を書き込むことができる. | |
metaData.tdCls += " x-grid-dirty-cell"; | |
} | |
// 他のカラムの情報にアクセスできる. | |
var enforcementDate = record.data['enforcementDate']; | |
var dt = enforcementDate.dt; | |
var reason = enforcementDate.reason; | |
if (!dt && !reason) { | |
// レンダラーはHTMLを返すことができる. | |
return '<div style="color: red;">未</div>'; | |
} | |
return ""; | |
} | |
}, | |
{ | |
text: '実施日', | |
width: 80 * fontScale, | |
align: 'left', | |
sortable: true, | |
draggable: false, | |
hideable: false, | |
dataIndex: 'enforcementDate', | |
renderer: function (value, metaDara, record, rowIndex, colIndex, store, view) { | |
// カラムのオブジェクトが複合型である場合は | |
// レンダラーを設定することで任意に描画可能. | |
// (その場合、データのいずれかが変更されると変更とみなされ、ダーティマークが付く) | |
// 複合データはセルエディタが厄介なことになるので状況により他カラムに分離しておく. | |
// (レンダラーはrecord変数を通じて他のカラムの情報を参照することも可能であるため) | |
var dt = value.dt; | |
if (dt) { | |
return Ext.util.Format.dateRenderer('Y/m/d')(dt); | |
} | |
if (value.reason) { | |
return '<div style="text-align: center;">伝達不要</div>'; // value.reason; | |
} | |
return ""; | |
} | |
}, | |
{ | |
header: '実施者<br>変更', | |
align: 'center', | |
width: 50 * fontScale, | |
sortable: false, | |
draggable: false, | |
hideable: false, | |
dataIndex: 'dmNo', | |
xtype: 'actioncolumn', | |
items: [{ | |
// 定義するボタンが返すCSS. | |
getClass: function (v, meta, rec) { | |
if (Ext.isEmpty(rec.data['status'].text)) { | |
// this.items[1].tooltip = ''; | |
// itemsのインデックスで複数ボタンを制御できる. | |
return 'grid-change-dm-disabled'; | |
} else { | |
return 'grid-change-dm'; | |
} | |
}, | |
handler: function (grid, rowIndex, colIndex) { | |
// ハンドラ省略 (cellclickイベントで一括処理することもできる) | |
} | |
}] | |
} | |
], | |
width: 970, | |
height: 500, | |
padding: 10, | |
selModel: selModel, | |
renderTo: 'resultTable', // タグの書き込み先 | |
viewConfig: { | |
stripeRows: true // 行を交互の背景色をつける | |
}, | |
buttons: [ | |
{ | |
text: "更新", | |
handler: function () { | |
// 追加または更新されたレコードがあれば登録する. | |
var modifiedRecs = store.getUpdatedRecords(); | |
var newRecs = store.getNewRecords(); | |
if (modifiedRecs.length > 0 || newRecs.length > 0) { | |
// 更新データ、新規データのデータ部のみを取り出し | |
// リストに詰める | |
var datas = []; | |
Ext.each(modifiedRecs, function (rec) { | |
datas.push(rec.data); | |
}); | |
Ext.each(newRecs, function (rec) { | |
datas.push(rec.data); | |
}); | |
// Ajaxリクエスト処理 | |
Ext.Ajax.request({ | |
url: "anzen_jisshijoukyou_update.ashx", | |
params: { | |
// データ部のリストをJSON形式にして送信 | |
data: Ext.JSON.encode(datas) | |
}, | |
success: function (response) { | |
// 通信が成功した場合 | |
// 返答内容を解析する. | |
var res = response.responseText; | |
var result = Ext.JSON.decode(res); | |
if (result.succeeded) { | |
// 登録に成功した場合 | |
// 編集行を確定させる. | |
Ext.each(newRecs, function (rec) { | |
rec.commit(); | |
}); | |
Ext.each(modifiedRecs, function (rec) { | |
rec.commit(); | |
}); | |
// セレクションをクリアする. | |
selModel.deselectAll(); | |
// 完了通知 | |
Ext.Msg.alert('Status', '更新しました。'); | |
} else { | |
// 失敗した場合 | |
var mes = result.message; | |
if (Ext.isEmpty(mes)) { | |
mes = "更新に失敗しました。" | |
} | |
Ext.MessageBox.show({ | |
title: "エラー", | |
msg: mes, | |
buttons: Ext.MessageBox.OK, | |
icon: Ext.MessageBox.ERROR | |
}); | |
} | |
}, | |
failure: function (response) { | |
// 通信エラー | |
Ext.MessageBox.show({ | |
title: "エラー", | |
msg: "通信に失敗しました。", | |
buttons: Ext.MessageBox.OK, | |
icon: Ext.MessageBox.ERROR | |
}); | |
} | |
}); | |
} | |
} | |
}, | |
{ | |
text: "クリア", | |
handler: function () { | |
var modifiedRecs = store.getUpdatedRecords(); | |
var newRecs = store.getNewRecords(); | |
// var removedRecs = store.getRemovedRecords(); | |
if (modifiedRecs.length > 0 || newRecs.length > 0) { | |
Ext.Msg.confirm("確認", "編集を破棄しますか?", function (ret) { | |
if (ret == "yes") { | |
// 編集されたレコードをリジェクトする. | |
Ext.each(modifiedRecs, function (rec) { | |
rec.reject(); | |
}); | |
// 追加されたレコードは除去する. | |
Ext.each(newRecs, function (rec) { | |
store.remove(rec); | |
}); | |
} | |
}); | |
} | |
// セレクションをクリアする. | |
selModel.deselectAll(); | |
} | |
} | |
] | |
}); | |
// グリッド上のセルのクリックによるフォームでの編集ハンドラ | |
grid.on('cellclick', function (iView, iCellEl, iColIdx, iStore, iRowEl, iRowIdx, iEvent) { | |
// 選択しているフィールド名 | |
var fieldName = iView.getGridColumns()[iColIdx].dataIndex; | |
if (!fieldName) { | |
// 有効なフィールド以外 (チェック欄など) | |
return; | |
} | |
// 現在選択レコード | |
var rec = iView.getRecord(iRowEl); | |
var cellData = rec.data[fieldName]; | |
var selRows = selModel.getSelection(); // グリッドのセレクションモデル | |
// 選択レコード数と、未選択の場合は現在行を選択とみなす. | |
if (selRows.length == 0) { | |
selRows = [rec]; | |
} | |
// ここでセルのフィールド名と選択行情報から、いろいろ処理する. | |
}); | |
// フォームのOKボタンによるストアへの書き戻し処理例 | |
// ((以下関数内のselRows, multiRowsなどはレキシカルスコープの変更)) | |
var actUpdate = function () { | |
// 検証に合格したら | |
if (editForm.getForm().isValid()) { | |
// フォームの情報をレコードに反映する. | |
Ext.each(selRows, function (selRow) { | |
// レコードの編集開始 (更新最適化のため) | |
selRow.beginEdit(); | |
// 単一・複数選択共通項目の反映 | |
// (※ 複合項目の場合) | |
var reason = noneedReasonCombo.getValue(); | |
var dt = enforcementDate.getValue(); | |
var val = Ext.apply({}, rec.get('enforcementDate')); // 元データをコピー | |
if (noneedReason) { | |
val.dt = null; | |
} else { | |
val.dt = dt; | |
} | |
val.reason = reason; | |
selRow.set('enforcementDate', val); // 新しいデータをセット | |
if (!multiRows) { | |
// 単一選択の場合のみ | |
// フォームの値を一括取得 | |
// ラジオグループ、チェックボックスグループなどは | |
// フォームから値をとるほうが簡単である. | |
var values = editForm.getForm().getValues(); | |
// チェックボックスグループから値を算定する. | |
var kind = 0; | |
Ext.each( | |
[values['cb1'], values['cb2'], values['cb3']], | |
function (x) { | |
if (x) { | |
kind += parseInt(x, 10); | |
} | |
} | |
); | |
// ※ 逆にチェックボックスグループにセットするのは簡単. | |
// recipientKind.setValue({ | |
// cb1: kind & 0x01, | |
// cb2: kind & 0x02, | |
// cb3: kind & 0x04 | |
// }); | |
// レコードに反映 | |
selRow.set('recipientComment', recipientComment.getValue()); | |
selRow.set('recipientName', recipientName.getValue()); | |
selRow.set('recipientKind', kind); | |
} | |
// レコードの編集完了 | |
selRow.endEdit(); | |
}); | |
// セレクションをクリアする. | |
selModel.deselectAll(); | |
// 閉じる | |
editWnd.close(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment