読者です 読者をやめる 読者になる 読者になる

初心者のためのExtJS入門

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

チュートリアル:ストアのプロキシを変更する

ExtJS

今回はストアのプロキシをローカルストレージに変更します。

これで、一応データを保存できるようになります。ローカルストレージにですが。。。(サーバ側が無いのでやむなし)

ストアのプロキシ変更

まずはストアのプロキシを変更して、ローカルストレージを使用するようにします。

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

    requires: [
        'Ext.data.proxy.LocalStorage',
        'Memo.model.Memo'
    ],

    model: 'Memo.model.Memo',

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

idはローカルストレージで使用する識別子になります。画面を表示してからDeveloper toolsで見てみると、下記のようにmemoというキーが自動的に用意されていることが分かります。

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

保存処理をプロキシに合わせて変更

ドキュメント(http://docs.sencha.com/extjs/6.2.1/classic/Ext.data.proxy.LocalStorage.html)を見ると分かりますが、ストアのsyncメソッドを呼び出すことでローカルストレージに反映されるので、保存処理を修正する必要があります。

/**
 * メモ登録のビューコントローラクラス。
 *
 * @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);
        }

        viewModel.set('record', record);
    },

    /**
     * 保存ボタンクリック時の処理。
     */
    onClickSaveButton: function () {
        var me = this,
            form = me.getView(),
            store = Ext.getStore('Memo');

        if (form.isValid()) {
            var record = form.getRecord();

            record.set(form.getValues());

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

            store.sync();

            Ext.Msg.alert('完了', '保存しました', function () {
                me.redirectTo('list');
            });
        }
    }

});

ついでにリファクタリングしてみました。(だいぶコードがすっきりしましたよ)

syncを呼ぶまでは、メモリ上で変更しただけになります。

syncを呼ぶことで初めてローカルストレージに書き込まれることに注意しましょう。

一覧画面の表示時にデータをロードする

あとは一覧画面を表示したときに、ローカルストレージからデータをロードする必要があります。

こういうときはshowscreenイベント時に処理できるようにしたのが便利に感じます。

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

    requires: [
        'Memo.view.list.View',
        'Memo.view.list.ViewController'
    ],

    controller: 'list',

    layout: 'fit',

    bodyPadding: 15,

    scrollable: true,

    listeners: {
        showscreen: 'onShowScreen'
    },

    title: 'メモ一覧',

    tools: [
        {
            xtype: 'button',
            text: '新規作成',
            handler: 'onClickCreateButton'
        }
    ],

    items: {
        xtype: 'list_dataview'
    }
});

/**
 * メモ一覧データビュークラス。
 *
 * @class Memo.view.list.View
 * @extend Ext.view.View
 */
Ext.define('Memo.view.list.View', {
    extend: 'Ext.view.View',
    xtype: 'list_dataview',

    cls: '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',

    emptyText: 'メモは登録されていません。新しいメモは「新規作成」ボタンから作成できます。',

    listeners: {
        itemdblclick: 'onItemDblClick'
    }

});

/**
 * メモ一覧のビューコントローラクラス。
 *
 * @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();
    },

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

    /**
     * データビューitemdblclickイベント時の処理。
     *
     * @param {Memo.view.list.View} dataview データビュー
     * @param {Memo.model.Memo} record メモモデル
     */
    onItemDblClick: function (dataview, record) {
        this.redirectTo('regist/' + record.getId(), true);
    }

});

ついでにデータビューのemptyTextプロパティを設定して、メモが未登録のときにテキストを表示するようにしました。

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

試しに登録してみます。

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

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

登録してみると、ローカルストレージにmemo-1というキーのデータが追加されたのがわかります。ブラウザをリロードしてもデータが残るようになりました。ちなみにmemo-1はid=1のデータとなります。もう1件登録してみるとmemo-2が追加されるのが分かります。

削除できるようにする

メモを削除できるように削除ボタンを追加します。

一覧画面に1件分を削除するボタンと全て削除するボタンの2種類用意していこうと思います。

/**
 * メモ一覧データビュークラス。
 *
 * @class Memo.view.list.View
 * @extend Ext.view.View
 */
Ext.define('Memo.view.list.View', {
    extend: 'Ext.view.View',
    xtype: 'list_dataview',

    cls: 'list-dataview',

    tpl: [
        '<tpl for=".">',
            '<div class="item">',
                '<i class="btn-remove fa fa-times"></i>',
                '<h3 class="title">{title}</h3>',
                '<p class="body">{body}</p>',
            '</div>',
        '</tpl>'
    ],

    itemSelector: 'div.item',

    store: 'Memo',

    emptyText: 'メモは登録されていません。新しいメモは「新規作成」ボタンから作成できます。',

    listeners: {
        itemclick: 'onItemClick',
        itemdblclick: 'onItemDblClick'
    }

});

/**
 * メモ一覧のビューコントローラクラス。
 *
 * @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();
    },

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

    /**
     * 全て削除ボタンクリック時の処理。
     */
    onClickRemoveAllButton: function () {
        Ext.Msg.confirm(
            '確認',
            '全てのメモを削除します。よろしいですか?',
            function (btn) {
                if (btn === 'yes') {
                    var store = Ext.getStore('Memo');

                    store.removeAll();
                    store.sync();
                }
            }
        );
    },

    /**
     * データビューitemclickイベント時の処理。
     *
     * @param {Memo.view.list.View} dataview データビュー
     * @param {Memo.model.Memo} record メモモデル
     * @param {HTMLElement} html アイテムのHTML要素
     * @param {number} index インデックス番号
     * @param {Ext.event.Event} e イベントオブジェクト
     */
    onItemClick: function (dataview, record, html, index, e) {
        var el = Ext.fly(e.target);

        if (el.hasCls('btn-remove')) {
            e.stopEvent();

            Ext.Msg.confirm(
                '確認',
                'メモを削除します。よろしいですか?',
                function (btn) {
                    if (btn === 'yes') {
                        var store = Ext.getStore('Memo');

                        store.remove(record);
                        store.sync();
                    }
                }
            );
        }
    },

    /**
     * データビューitemdblclickイベント時の処理。
     *
     * @param {Memo.view.list.View} dataview データビュー
     * @param {Memo.model.Memo} record メモモデル
     */
    onItemDblClick: function (dataview, record) {
        this.redirectTo('regist/' + record.getId(), true);
    }

});
@charset "UTF-8";

.list-dataview {
  .item {
    position: relative;
    display: inline-block;
    width: 200px;
    height: 150px;
    background-color: #fff;
    cursor: pointer;
    outline: none !important;
    margin-right: 10px;
    margin-bottom: 10px;
    @include box-shadow(0 0 2px #aaa);

    .title {
      background-color: #5fa2dd;
      color: #fff;
      padding: 5px;
      margin: 0;
    }

    .body {
      padding: 5px;
      margin: 0;
    }

    &.x-item-selected {
      .title {
        background-color: darken(#5fa2dd, 20%);
      }
    }

    .btn-remove {
      position: absolute;
      top: 3px;
      right: 3px;
      color: #fff;
      font-size: 20px;

      &:hover {
        color: #eee;
      }
    }
  }
}

基本的には、ストアから削除してからsyncメソッドを実行しています。

1件の削除では、FontAwesomeのアイコンで削除ボタンを設置してみました(ExtJSのパッケージとして、はじめからfont-awesomeが用意されています)。データビューのitemclickイベントで削除ボタンをクリックしていたら削除するようにしています。

onItemClickメソッドでは、Ext.flyを使っています。このメソッドの戻り値はExt.dom.Elementですが、これはHTMLElement、いわゆるDOMのラッパークラスです。Ext.dom.Elementの形式でDOMにアクセスすると、いろいろ便利なメソッドを使えるようになります。今回は、Ext.flyを使っていますが、Ext.getを使うこともできます。違いですが、イベントの割り当てなどが必要な場合はExt.getを使う必要があります。Ext.flyは一時的な参照の場合に使うようにドキュメントに記載があります。これはExt.flyはいろいろな箇所で呼ばれており、別の箇所で呼ばれると、その前のDOMへの参照やイベントは破棄されるようになっているためです。その分、Ext.flyのほうが処理は軽いという特性があります。

あと、e.stopEventですが、内部的にはpreventDefaultとstopPropagationを実行してくれます。

これでデータをローカルストレージに残せるようになり、また、削除もできるようにできました。