初心者のためのExtJS入門

ExtJSを使うので、ついでにまとめていきます

バリデーション:classicのようにエラーテキストを画面に表示させる[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メソッドは共通化できそう。

f:id:sham-memo:20170311161401p:plain

f:id:sham-memo:20170311161409p:plain

f:id:sham-memo:20170311161728p:plain

なかなか良い感じになりました。