バリデーション:classicのようにエラーテキストを画面に表示させる[modern]
※ExtJS6.5.0からmodernのエラー表示の機能が強化され、標準機能で実現できるようになっています。
modernの場合、標準で提供されている入力エラーの表示方法が乏しいです。
でも画面にも表示できたほうがUIとしては良いので、エラーテキストを入力フィールドの下に表示させてみました。
Ext.field.Textをオーバーライド
まずはエラーテキストを表示するための機能を、オーバーライドで追加しました。
/** * テキストフィールドのオーバーライドクラス。 * * @class Sample.overrides.field.Text * @override Ext.field.Text */ Ext.define('Sample.overrides.field.Text', { override: 'Ext.field.Text', /** * @cfg {String} エラー時に付与されるセレクタ名。 */ errorCls: Ext.baseCSSPrefix + 'has-error', // @override getElementConfig: function() { return { reference: 'element', children: [ { reference: 'labelElement', cls: Ext.baseCSSPrefix + 'label-el', children: [{ reference: 'labelTextElement', cls: Ext.baseCSSPrefix + 'label-text-el', tag: 'span' }] }, { reference: 'bodyElement', cls: Ext.baseCSSPrefix + 'body-el' }, { reference: 'errorElement', cls: Ext.baseCSSPrefix + 'error-el' } ] }; }, /** * エラーをマークする。 * @param {String|Array} messages エラーメッセージ */ markInvalid: function (messages) { var me = this, errorElement = me.errorElement; if (Ext.isString(messages)) { messages = [messages]; } if (messages.length > 0) { me.addCls(me.errorCls); } errorElement.setHtml(messages.join('<br>')); }, /** * エラーをクリアする。 */ clearInvalid: function () { var me = this, errorElement = me.errorElement; me.removeCls(me.errorCls); errorElement.setHtml(null); } }); @charset "UTF-8"; .x-textfield { &.x-has-error { color: #c00; .x-input { border-color: #c00; } } }
getElementConfigメソッドのオーバーライドで、エラーテキストの表示要素を用意し、エラーの表示・クリアのメソッドを追加しました。
これでmarkInvalidメソッドを呼び出すことでエラーテキストを表示できます。
モデル
/** * ユーザモデルクラス。 * * @class Sample.model.User * @extend Ext.data.Model */ Ext.define('Sample.model.User', { extend: 'Ext.data.Model', fields: [ { name: 'name', type: 'string' }, { name: 'kana', type: 'string' }, { name: 'gender', type: 'string' }, { name: 'age', type: 'int' }, { name: 'postalCode', type: 'string' }, { name: 'address', type: 'string' }, { name: 'note', type: 'string' } ], validators: { name: [ { type: 'presence' }, { type: 'length', max: 10 } ], kana: [ { type: 'length', max: 10 } ], gender: 'presence', age: [ { type: 'range', min: 0 } ] } });
ビュー
/** * メインパネル。 * * @class Sample.view.main.Panel * @extend Ext.Panel */ Ext.define('Sample.view.main.Panel', { extend: 'Ext.Panel', xtype: 'main_panel', requires: [ 'Sample.view.main.Form', 'Sample.view.main.ViewController', 'Sample.view.main.ViewModel' ], controller: 'main', viewModel: 'main', layout: 'fit', items: { reference: 'form', xtype: 'main_form' }, listeners: { show: 'onShow' } }); /** * ユーザ登録フォームクラス。 * * @class Sample.view.main.Form * @extend Ext.form.Panel */ Ext.define('Sample.view.main.Form', { extend: 'Ext.form.Panel', xtype: 'main_form', requires: [ 'Ext.field.Text', 'Ext.field.Select', 'Ext.field.Number', 'Ext.field.TextArea', 'Ext.Button' ], cls: 'main-form', title: 'ユーザ登録', scrollable: true, bind: { record: '{user}' }, items: [ { xtype: 'textfield', name: 'name', label: '氏名' }, { xtype: 'textfield', name: 'kana', label: 'かな' }, { xtype: 'selectfield', name: 'gender', label: '性別', autoSelect: false, options: [ { text: '男性', value: '1' }, { text: '女性', value: '2' } ] }, { xtype: 'numberfield', name: 'age', label: '年齢' }, { xtype: 'textfield', name: 'postalCode', label: '郵便番号' }, { xtype: 'textfield', name: 'address', label: '住所' }, { xtype: 'textareafield', name: 'note', label: '備考' }, { xtype: 'button', text: '保存', handler: 'onClickSaveButton' } ] }); /** * ビューコントローラクラス。 * * @class Sample.view.main.ViewController * @extend Ext.app.ViewController */ Ext.define('Sample.view.main.ViewController', { extend: 'Ext.app.ViewController', alias: 'controller.main', /** * showイベント時の処理。 */ onShow: function () { var me = this, user = Ext.create('Sample.model.User'); me.getViewModel().set('user', user); }, /** * 保存ボタンクリック時の処理。 */ onClickSaveButton: function () { var me = this, form = me.lookupReference('form'); if (me.isValid()) { // MEMO: エラーがない場合の処理を記述 } else { Ext.Msg.alert('エラー', '入力エラーの箇所があります'); } }, /** * 入力チェック。 * @returns {boolean} エラー箇所が無い場合はtrue */ isValid: function () { var me = this, form = me.lookupReference('form'), user = form.getRecord(), validation, firstInvalidField; // 一旦エラー表示をクリア Ext.each(form.query('field'), function (field) { if (Ext.isFunction(field.clearInvalid)) { field.clearInvalid(); } }); user.set(form.getValues()); validation = user.getValidation(); if (validation.dirty) { Ext.Object.each(validation.data, function (k, v) { if (Ext.isString(v)) { var field = form.down('field[name="' + k + '"]'); if (field) { field.markInvalid(v); if (!firstInvalidField) { // TODO: 先頭フィールドにフォーカスさせようとしているが、これだと保障されないかも firstInvalidField = field; } } } }); if (firstInvalidField) { firstInvalidField.focus(); } return false; } else { return true; } } }); /** * ビューモデルクラス。 * * @class Sample.view.main.ViewModel * @extend Ext.app.ViewModel */ Ext.define('Sample.view.main.ViewModel', { extend: 'Ext.app.ViewModel', alias: 'viewmodel.main', data: { /** * @cfg {Sample.model.User} ユーザモデル */ user: null } });
バリデーションチェック自体は、モデルのgetValidationメソッドを使った基本通りで、エラーがある場合に、オーバーライドで追加したメソッドを呼び出すようにしています。
面倒になって一部手抜きになっていますが、今回の趣旨とは違うところなのでまあいいかな。あと、おそらくビューコントローラのisValidメソッドは共通化できそう。
なかなか良い感じになりました。