今回は、SPA対応のmodern版です。

まあ、classicとほとんど同じなのですが。。。

ルーティングの設定

メインパネルにビューコントローラを設定し、そのビューコントローラにルーティングを定義します。

/**
 * メインパネルクラス。
 *
 * @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
    ],

    controller: app_main,

    layout: card
});

/**
 * メインパネルのビューコントローラクラス。
 * 
 * @class Memo.view.main.ViewController
 * @extend Ext.app.ViewController
 */
Ext.define(Memo.view.main.ViewController, {
    extend: Ext.app.ViewController,
    alias: controller.app_main,

    requires: [
        Memo.view.list.Panel,
        Memo.view.regist.Panel
    ],

    routes: {
        list: showList,
        regist: showRegist,
        regist/:id: showRegist
    },

    /**
     * 一覧画面を表示する。
     */
    showList: function () {
        this.switchScreen(list_panel);
    },

    /**
     * 登録画面を表示する。
     * @param {Number} [id] メモID
     */
    showRegist: function (id) {
        var me = this,
            params = {};

        if (Ext.isDefined(id)) {
            params.id = +id;
        }

        me.switchScreen(regist_panel, params);
    },

    /**
     * 画面を切り替える。
     *
     * @param {String} screenXType 画面のxtype
     * @param {Object} [params] パラメータ
     */
    switchScreen: function (screenXType, params) {
        var me = this,
            view = me.getView(),
            screen;

        params = params || {};

        // 画面の存在チェック
        screen = view.down(screenXType);

        if (!screen) {
            // 画面を生成
            screen = Ext.widget(screenXType);

            view.add(screen);
        }

        view.setActiveItem(screen);

        screen.fireEvent(showscreen, params);
    }
});

説明を忘れていましたが、Memo.view.main.Mainは「メインパネル」としています。ビューポートじゃないのか?というと、ビューポートではありません。modernでは、自動的にビューポートが作成されており、Ext.Viewportでグローバルに参照できるようになっています。Memo.view.main.Mainは、ビューポートに自動的に配置されるビューで、大枠のビューとして使うことにしています。ちなみに、この自動生成はapp.jsというファイルに定義されています。

さて、ルーティングについてですが、これはclassicの時とほぼ同じです。異なる箇所ですが、classicではview.getLayout().setActiveItem(screen)だったコードが、view.setActiveItem(screen)となっています。こういうのがclassicとmodernでのAPIの微妙な違いによるものです。

プロキシを変更

localstorageに変更しておきます。

/**
 * メモモデルクラス。
 *
 * @class Memo.model.Memo
 * @extend Ext.data.Model
 */
Ext.define(Memo.model.Memo, {
    extend: Ext.data.Model,

    fields: [
        {
            name: id,
            type: int
        },
        {
            name: title,
            type: string
        },
        {
            name: body,
            type: string
        }
    ],

    validators: {
        title: presence
    }
});

/**
 * メモストアクラス。
 *
 * @class Memo.store.Memo
 * @extend Ext.data.Store
 */
Ext.define(Memo.store.Memo, {
    extend: Ext.data.Store,

    requires: [
        Memo.model.Memo
    ],

    model: Memo.model.Memo,

    proxy: {
        type: localstorage,
        id: memo
    }
});

一覧画面のビューコントローラ作成

ビューコントローラを追加し、一覧画面の処理を実装します。

/**
 * メモ一覧パネルクラス。
 *
 * @class Memo.view.list.Panel
 * @extend Ext.Panel
 */
Ext.define(Memo.view.list.Panel, {
    extend: Ext.Panel,
    xtype: list_panel,

    requires: [
        Memo.view.list.List,
        Memo.view.list.ViewController
    ],

    controller: list,

    title: メモ一覧,

    tools: [
        {
            xtype: button,
            iconCls: x-fa fa-plus,
            ui: action,
            handler: onTapCreateButton
        }
    ],

    items: {
        xtype: list_list
    },

    listeners: {
        showscreen: onShowScreen
    }
});

/**
 * メモ一覧リストクラス。
 *
 * @class Memo.view.list.List
 * @extend Ext.List
 */
Ext.define(Memo.view.list.List, {
    extend: Ext.List,
    xtype: list_list,

    cls: list-list,

    itemTpl: [
        <h2 class="title">{title}</h2>,
        <p class="body">{body}</p>
    ],

    store: Memo,

    listeners: {
        itemtap: onItemTap
    }
});

/**
 * メモ一覧ビューコントローラクラス。
 *
 * @class Memo.view.list.ViewController
 * @extend Ext.app.ViewController
 */
Ext.define(Memo.view.list.ViewController, {
    extend: Ext.app.ViewController,
    alias: controller.list,

    /**
     * showscreenイベント時の処理。
     */
    onShowScreen: function () {
        Ext.getStore(Memo).load();
    },

    /**
     * 新規作成ボタンタップ時の処理。
     */
    onTapCreateButton: function () {
        this.redirectTo(regist, true);
    },

    /**
     * リストitemtapイベント時の処理。
     *
     * @param {Memo.view.list.List} list リスト
     * @param {Number} index インデックス番号
     * @param {Ext.dom.Element} target タップ要素
     * @param {Memo.model.Memo} record メモモデル
     */
    onItemTap: function (list, index, target, record) {
        this.redirectTo(regist/ + record.getId(), true);
    }
});

Memo.view.list.Listのxtypeが残念なことになっていますが、まあ、これで進めましょうw

classicで「click」と名の付いたイベントは、modernでは「tap」となります。Ext.Listで行をタップしたときは、itemtapイベントが発生するので、イベントハンドラの割り当てもitemtapに行います。

ボタンの場合は、classicと同じようにhandlerコンフィグにイベントハンドラを割り当てられますが、内部的にはtapイベント発生時に処理されています。

登録画面のビューコントローラ、ビューモデル作成

/**
 * メモ登録フォームパネルクラス。
 *
 * @class Memo.view.regist.Panel
 * @extend Ext.form.Panel
 */
Ext.define(Memo.view.regist.Panel, {
    extend: Ext.form.Panel,
    xtype: regist_panel,

    requires: [
        Memo.view.regist.ViewController,
        Memo.view.regist.ViewModel
    ],

    controller: regist,
    viewModel: regist,

    title: メモ登録,

    bind: {
        title: メモ登録({labelFormMode}),
        record: {record}
    },

    listeners: {
        showscreen: onShowScreen
    },

    tools: [
        {
            xtype: button,
            ui: action,
            iconCls: x-fa fa-check,
            handler: onTapSaveButton
        }
    ],

    bodyPadding: 10,

    items: [
        {
            xtype: textfield,
            name: title,
            placeHolder: タイトルを入力してください
        },
        {
            xtype: textareafield,
            name: body,
            placeHolder: 本文を入力してください
        }
    ]

});

/**
 * メモ登録のビューコントローラクラス。
 *
 * @class Memo.view.regist.ViewController
 * @extend Ext.app.ViewController
 */
Ext.define(Memo.view.regist.ViewController, {
    extend: Ext.app.ViewController,
    alias: controller.regist,

    /**
     * showscreenイベント時の処理。
     * @param {Object} params パラメータ
     */
    onShowScreen: function (params) {
        var me = this,
            viewModel = me.getViewModel(),
            store = Ext.getStore(Memo),
            record = null;

        if (Ext.isNumber(params.id)) {
            record = store.getById(params.id);
        }

        if (!record) {
            record = Ext.create(Memo.model.Memo);
        }

        viewModel.set(record, record);
    },

    /**
     * 保存ボタンタップ時の処理。
     */
    onTapSaveButton: function () {
        var me = this,
            store = Ext.getStore(Memo),
            form = me.getView(),
            record = form.getRecord(),
            validation;

        record.set(form.getValues());

        validation = record.getValidation();

        if (!validation.dirty) {
            if (record.phantom) {
                // 新規
                store.add(record);
            }

            store.sync();

            Ext.Msg.alert(完了, 保存しました, function () {
                me.redirectTo(list, true);
            });
        } else {
            var errorMsg = [];
            Ext.Object.each(validation.data, function (key, value) {
                if (value !== true) {
                    errorMsg.push(key + : + value);
                }
            });

            if (errorMsg.length > 0) {
                Ext.Msg.alert(エラー, errorMsg.join(<br>));
            }
        }
    }

});

/**
 * メモ登録のビューモデルクラス。
 *
 * @class Memo.view.regist.ViewModel
 * @extend Ext.app.ViewModel
 */
Ext.define(Memo.view.regist.ViewModel, {
    extend: Ext.app.ViewModel,
    alias: viewmodel.regist,

    data: {
        /**
         * @cfg {Memo.model.Memo} 選択中のメモモデル。
         */
        record: null
    },

    formulas: {
        /**
         * フォームのモード(新規か編集か)のラベルを返す。
         *
         * @param {Function} get
         * @returns {string} フォームのモード(新規か編集か)のラベル
         */
        labelFormMode: function (get) {
            var record = get(record);
            return record ? 編集 : 新規作成;
        }
    }
});

大きく異なる部分は、入力チェックの部分です。

classicでは入力フィールドにバリデーションチェックの定義を設定しますが、modernではモデルのvalidatorsにバリデーションチェックの定義を設定します。

具体的には、モデルのgetValidationの戻り値であるExt.data.Validationのdirtyでエラーがあるかどうかを判定します。

そして、エラーがあった場合の表示方法が乏しいのがmodernの特徴です(ノД`)

classicの場合、エラーのあった入力フィールドにエラーメッセージを表示する機能が標準で備わっていますが、modernでは無いためユーザ自身で実装する必要があります。

先のコードでは、メッセージボックスで簡単にエラー表示しています。

defaultTokenを設定しておく

ここはclassicと同じです。

/**
 * アプリケーションクラス。
 *
 * @class Memo.Application
 * @extend Ext.app.Application
 */
Ext.define(Memo.Application, {
    extend: Ext.app.Application,

    name: Memo,

    stores: [
        Memo
    ],

    defaultToken : list
});

最終的に、↓のようになりました。

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

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

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

エラーメッセージがひどい状態ですが、今回はここまで。

次回は削除できるようにします。