チュートリアル:ビューモデルを使って、フォームパネルを編集に対応させる
今回は、保存しているメモを編集できるようにしたい、という内容です。
編集したいメモを選択すると入力フォームに転記されて保存ボタンを押すと編集内容が反映される、といったかんじにしようと思います。
これだけだと、メモを選択したあとに、新規登録に戻す方法を決めていません。今回は新規登録に切り替えるボタンを別途配置することにします。
データビューを選択したときの処理を実装
まずは、データビューでメモを選択する部分です。
データビュー(Ext.view.View)は、選択状態になるとselectionchangeというイベントが発生します。
このイベントが発生したときに、処理が実行されるようにしてみましょう。
/** * ビューポートクラス。 * * @class Memo.view.main.Main * @extend Ext.Panel */ Ext.define('Memo.view.main.Main', { extend: 'Ext.Panel', xtype: 'app_main', requires: [ 'Memo.view.main.ViewController' ], cls: 'app-main', controller: 'app_main', bodyPadding: 20, layout: 'border', items: [ { region: 'west', reference: 'form', xtype: 'form', title: 'メモ入力フォーム', bodyPadding: 15, width: 300, defaults: { anchor: '100%' }, items: [ { name: 'title', xtype: 'textfield', emptyText: 'タイトルを入力してください', allowBlank: false }, { name: 'body', xtype: 'textarea', emptyText: '本文を入力してください' }, { xtype: 'button', text: '保存', handler: 'onClickSaveButton' } ] }, { region: 'center', reference: 'list', xtype: 'dataview', cls: 'memo-list-dataview', tpl: [ '<tpl for=".">', '<div class="item">', '<h3 class="title">{title}</h3>', '<p class="body">{body}</p>', '</div>', '</tpl>' ], itemSelector: 'div.item', store: 'Memo', listeners: { selectionchange: 'onSelectionChangeDataView' } } ] }); /** * Memo.view.main.Mainのビューコントローラクラス。 * * @class Memo.view.main.ViewController * @extend Ext.app.ViewController */ Ext.define('Memo.view.main.ViewController', { extend: 'Ext.app.ViewController', alias: 'controller.app_main', /** * 保存ボタンクリック時の処理。 */ onClickSaveButton: function () { var me = this, form = me.lookupReference('form'), list = me.lookupReference('list'), store = list.getStore(); if (form.isValid()) { store.add(form.getValues()); } }, /** * データビューselectionchangeイベント時の処理。 * * @param {Ext.view.View} dataview データビュー * @param {Array} records 選択行のモデルリスト */ onSelectionChangeDataView: function (dataview, records) { console.log('selectionchangeイベントが発生したよ。'); } });
ビューコントローラにonSelectionChangeDataViewメソッドを追加して、ビューのlistenersコンフィグでselectionchange: 'onSelectionChangeDataView'と設定することで、イベント発生時にonSelectionChangeDataViewが呼ばれるようにしました。
API(http://docs.sencha.com/extjs/6.2.1/classic/Ext.view.View.html#event-selectionchange)を見ると、引数について確認できます。
ビューモデルを使ってフォームパネルに転記する
フォームパネルへの転記は、ビューモデルを使うことにします。
ビューモデルを使うことで、ビューモデルとビューの間に双方向データバインディングを実現でき、ビューモデルに値を設定することでビューに反映することができるようになります。
まずはビューモデルを用意します。
/** * ビューモデルクラス。 * * @class Memo.view.main.ViewModel * @extend Ext.app.ViewModel */ Ext.define('Memo.view.main.ViewModel', { extend: 'Ext.app.ViewModel', alias: 'viewmodel.app_main', data: { /** * @cfg {Memo.model.Memo} 選択中のメモモデル。 */ selectedRecord: null }, formulas: { /** * フォームのモード(新規か編集か)のラベルを返す。 * * @param {Function} get * @returns {string} フォームのモード(新規か編集か)のラベル */ labelFormMode: function (get) { var selectedRecord = get('selectedRecord'); return selectedRecord ? '編集' : '新規作成'; }, /** * 新規作成モードかどうかを返す。 * * @param {Function} get * @returns {boolean} 新規作成モードの場合はtrue */ isNew: function (get) { return !get('selectedRecord'); } } });
ビューモデルは、Ext.app.ViewModelを継承し、エイリアス名として「viewmodel.xxx」という形式にします。
dataには、ビューモデルで管理する項目を用意します。ここでは、データビューで選択したモデルを格納するselectedRecordプロパティを定義しました。
formulasには、dataに設定される値を使って別名の値を用意できます。
フォームパネルクラスを作成
次にビューとビューモデルを結びつけます。
そのために、フォームパネルのクラスを別途作成し、その中で結びつけるようにします。
/** * ビューポートクラス。 * * @class Memo.view.main.Main * @extend Ext.Panel */ Ext.define('Memo.view.main.Main', { extend: 'Ext.Panel', xtype: 'app_main', requires: [ 'Memo.view.main.Form', 'Memo.view.main.ViewController', 'Memo.view.main.ViewModel' ], cls: 'app-main', controller: 'app_main', viewModel: 'app_main', bodyPadding: 20, layout: 'border', items: [ { region: 'west', reference: 'form', xtype: 'main_form' }, { region: 'center', reference: 'list', xtype: 'dataview', cls: 'memo-list-dataview', tpl: [ '<tpl for=".">', '<div class="item">', '<h3 class="title">{title}</h3>', '<p class="body">{body}</p>', '</div>', '</tpl>' ], itemSelector: 'div.item', store: 'Memo', listeners: { selectionchange: 'onSelectionChangeDataView' } } ] }); /** * フォームパネルクラス。 * * @class Memo.view.main.Form * @extend Ext.form.Panel */ Ext.define('Memo.view.main.Form', { extend: 'Ext.form.Panel', xtype: 'main_form', bodyPadding: 15, width: 300, title: 'メモ入力フォーム', config: { /** * @cfg {Memo.model.Memo} メモモデル。 */ record: null }, tools: [ { xtype: 'button', text: '新規作成', scale: 'small', bind: { disabled: '{isNew}' }, handler: 'onClickNewButton' } ], bind: { title: 'メモ入力フォーム({labelFormMode})', record: '{selectedRecord}' }, defaults: { anchor: '100%' }, items: [ { name: 'title', xtype: 'textfield', emptyText: 'タイトルを入力してください', allowBlank: false }, { name: 'body', xtype: 'textarea', emptyText: '本文を入力してください' }, { xtype: 'button', text: '保存', handler: 'onClickSaveButton' } ], /** * recordコンフィグ設定時の処理。 * @param {Memo.model.Memo} record メモモデル */ setRecord: function (record) { var me = this; me.loadRecord(record); me.callParent(arguments); } });
まずはビューポートクラスですが、viewModelコンフィグに、ビューモデルのエイリアス名を指定することでビューモデルを適用できます。
さらにデータバインディングしたい項目にbindコンフィグを設定していきます。
bind: { title: 'メモ入力フォーム({labelFormMode})', record: '{selectedRecord}' }
上記のように{}の中にビューモデルのdataやformulasで定義した項目を設定すると、そこに値がバインディングされます。
bindにオブジェクトリテラルを指定すると、キーの項目に値が設定されます。上記の例だとtitleとrecordコンフィグにバインディングされることになります。文字列を指定した場合はdefaultBindPropertyの値にバインディングされるようになっています。defaultBindPropertyの値はコンポーネントごとに異なります。
データバインディングしたい項目が存在しない場合は、configコンフィグを定義することで追加することができます。追加した項目はsetメソッドとupdateメソッドが自動的に用意されます。今回の場合はrecordを追加したので、setRecordとupdateRecordです。recordの値が設定されたり変更されたりした場合に呼び出されます。
updateRecordメソッド内で、loadRecordメソッドを呼び出しています。loadRecordを使うと、モデルのフィールド名と入力フィールドのnameコンフィグの値が一致する場合に、その値がフィールドに設定されます(内部的には入力フィールドのsetValueが呼ばれる)。
最後に、ビューコントローラでビューモデルに値を設定するようにします。
/** * Memo.view.main.Mainのビューコントローラクラス。 * * @class Memo.view.main.ViewController * @extend Ext.app.ViewController */ Ext.define('Memo.view.main.ViewController', { extend: 'Ext.app.ViewController', alias: 'controller.app_main', /** * 保存ボタンクリック時の処理。 */ onClickSaveButton: function () { var me = this, form = me.lookupReference('form'), list = me.lookupReference('list'), store = list.getStore(); if (form.isValid()) { store.add(form.getValues()); } }, /** * 新規作成ボタンクリック時の処理。 */ onClickNewButton: function () { var me = this, viewModel = me.getViewModel(), form = me.lookupReference('form'), dataview = me.lookupReference('list'); // 選択を解除 dataview.deselect(dataview.getSelection()); viewModel.set('selectedRecord', null); }, /** * データビューselectionchangeイベント時の処理。 * * @param {Ext.view.View} dataview データビュー * @param {Array} records 選択行のモデルリスト */ onSelectionChangeDataView: function (dataview, records) { var me = this, viewModel = me.getViewModel(); if (records.length > 0) { viewModel.set('selectedRecord', records[0]); } else { viewModel.set('selectedRecord', Ext.create('Memo.model.Memo')); } } });
ビューモデルは、ビューコントローラのgetViewModelメソッドで取得できます(内部的にはビューのgetViewModelが呼ばれます)。
で、ビューモデルのsetメソッドで値を設定しています。
保存処理を更新に対応
モデルのphantomの値を使って新規か更新かを判断しています。
新規の場合はストアに追加、更新の場合はモデルに値を設定し、最後にcommitします。
/** * Memo.view.main.Mainのビューコントローラクラス。 * * @class Memo.view.main.ViewController * @extend Ext.app.ViewController */ Ext.define('Memo.view.main.ViewController', { extend: 'Ext.app.ViewController', alias: 'controller.app_main', /** * 保存ボタンクリック時の処理。 */ onClickSaveButton: function () { var me = this, form = me.lookupReference('form'), list = me.lookupReference('list'), store = list.getStore(); if (form.isValid()) { var record = form.getRecord(); if (record.phantom) { // 新規 var records = store.add(form.getValues()); records[0].commit(); } else { // 更新 record.set(form.getValues()); } } }, /** * 新規作成ボタンクリック時の処理。 */ onClickNewButton: function () { var me = this, viewModel = me.getViewModel(), form = me.lookupReference('form'), dataview = me.lookupReference('list'); // 選択を解除 dataview.deselect(dataview.getSelection()); viewModel.set('selectedRecord', null); }, /** * データビューselectionchangeイベント時の処理。 * * @param {Ext.view.View} dataview データビュー * @param {Array} records 選択行のモデルリスト */ onSelectionChangeDataView: function (dataview, records) { var me = this, viewModel = me.getViewModel(); if (records.length > 0) { viewModel.set('selectedRecord', records[0]); } else { viewModel.set('selectedRecord', Ext.create('Memo.model.Memo')); } } });