初心者のためのExtJS入門

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

スポットライト[classic]

今回はExt.ux.Spotlightの紹介です。

Exampleを眺めていたら見つけて試したくなりました(http://examples.sencha.com/extjs/6.5.1/examples/classic/core/spotlight.html)。

これを使うと、特定の要素を目立たせることができます。

使い方

Ext.ux.Spotlightのインスタンスで目立たせたい要素を引数にしてshowメソッドを実行します。

var button1 = panel.down('#button1'),
    spot = Ext.create('Ext.ux.Spotlight');

spot.show(button1.getEl());

すると、下記のようにボタン1をマスク表示で囲うような表現となります。

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

スポットライト用のスタイルが見当たらなかったので、自分で定義する必要があるようです。

ここでは下記のようにしています。

.#{$prefix}spotlight {
  background-color: #999;
  z-index: 8999;
  position: absolute;
  top: 0;
  left: 0;
  @include opacity(.5);
  width: 0;
  height: 0;
  zoom: 1;
  font-size: 0;
}

応用

サービスへの初回ログイン時のナビゲーションに使えそうなので、それっぽいものを試してみました。

f:id:sham-memo:20170903142205g:plain

gifにしたので画質が悪くなっています。。。

しかし、それなりに形になりました。これから作るアプリケーションでは組み込んでいこうと思います。

メインのビュー

Ext.define('Sample.view.sample.Panel', {
    extend: 'Ext.panel.Panel',
    xtype: 'sample_panel',

    controller: 'sample_panel',

    cls: 'sample-panel',

    layout: 'fit',

    listeners: {
        afterlayout: 'onAfterLayout'
    },

    dockedItems: {
        xtype: 'toolbar',
        cls: 'nav-toolbar',
        items: [
            {
                xtype: 'label',
                cls: 'brand',
                text: 'ロゴ'
            },
            '->',
            {
                reference: 'envelope_button',
                iconCls: 'x-fa fa-envelope',
                scale: 'large'
            },
            {
                reference: 'comment_button',
                iconCls: 'x-fa fa-comment',
                scale: 'large'
            },
            {
                reference: 'info_button',
                iconCls: 'x-fa fa-info-circle',
                scale: 'large'
            }
        ]
    },

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

Ext.define('Sample.view.sample.PanelController', {
    extend: 'Ext.app.ViewController',
    alias: 'controller.sample_panel',

    requires: [
        'Ext.ux.Spotlight'
    ],

    helpRefs: [
        'envelope_button',
        'comment_button',
        'info_button'
    ],

    helpInfo: {
        'envelope_button': {
            text: 'メンテナンスや新機能追加のお知らせは、こちらからご確認いただけます。',
            align: 'tr-br',
            offset: [0, 10]
        },
        'comment_button': {
            text: 'ユーザからのコメントは、こちらからご確認いただけます。',
            align: 'tr-br',
            offset: [0, 10]
        },
        'info_button': {
            text: '分からない機能があれば、このボタンからヘルプをご確認いただけます。',
            align: 'tr-br',
            offset: [0, 10]
        }
    },

    config: {
        /**
         * @cfg {Ext.ux.Spotlight} スポットライトオブジェクト。
         */
        spot: null,

        /**
         * @cfg {Number} 現在表示しているヘルプ参照のインデックス。
         */
        currentHelpIndex: null,

        /**
         * @cfg {Sample.view.balloon.Panel} 吹き出しパネル。
         */
        balloon: null
    },

    /**
     * afterlayoutイベント時の処理。
     */
    onAfterLayout: function () {
        this.runHelp();
    },

    /**
     * ヘルプの表示を開始する。
     */
    runHelp: function () {
        var me = this,
            refInfo = me.currentHelpRefInfo();

        me.showHelp(refInfo);
    },

    /**
     * 前のヘルプを表示する。
     */
    showPrevHelp: function () {
        var me = this,
            refInfo = me.prevHelpRefInfo();

        me.showHelp(refInfo);
    },

    /**
     * 次のヘルプを表示する。
     */
    showNextHelp: function () {
        var me = this,
            refInfo = me.nextHelpRefInfo();

        me.showHelp(refInfo);
    },

    /**
     * ヘルプを表示する。
     * @param {object} helpRefInfo ヘルプ参照情報
     */
    showHelp: function (helpRefInfo) {
        var me = this,
            spot = me.getSpot(),
            ref = helpRefInfo.ref,
            target = me.lookupReference(ref),
            helpInfo = me.helpInfo[ref];

        me.setCurrentHelpIndex(helpRefInfo.index);

        // 前の吹き出しが残っていたら破棄する
        me.destroyPreBalloonPanel();

        // スポットライトを当てる
        spot.show(target.getEl());

        // 吹き出し表示
        me.showBallonPanel({
            target: target,
            text: helpInfo.text,
            first: helpRefInfo.first,
            last: helpRefInfo.last,
            align: helpInfo.align,
            offset: helpInfo.offset
        });
    },

    /**
     * 吹き出しを表示する。
     * @param {object} params パラメータ
     */
    showBallonPanel: function (params) {
        var me = this;

        Ext.defer(function () {
            var balloonPanel = Ext.widget('sample_balloon_panel', {
                html: params.text,
                first: params.first,
                last: params.last,
                listeners: {
                    prev: 'onPrevBalloonPanel',
                    next: 'onNextBalloonPanel',
                    end: 'onEndBalloonPanel',
                    destroy: 'onDestroyBalloonPanel',
                    scope: me
                }
            });

            me.setBalloon(balloonPanel);

            balloonPanel.showBy(params.target, params.align, params.offset);
        }, params.first ? 500 : 100);
    },

    /**
     * 前の吹き出しを破棄する。
     */
    destroyPreBalloonPanel: function () {
        var me = this,
            balloon = me.getBalloon();

        if (balloon) {
            balloon.destroy();
        }
    },

    /**
     * 吹き出しパネルprevイベント時の処理。
     * @param {Sample.view.balloon.Panel} panel 吹き出しパネル
     */
    onPrevBalloonPanel: function (panel) {
        var me = this;

        panel.destroy();

        me.showPrevHelp();
    },

    /**
     * 吹き出しパネルnextイベント時の処理。
     * @param {Sample.view.balloon.Panel} panel 吹き出しパネル
     */
    onNextBalloonPanel: function (panel) {
        var me = this;

        panel.destroy();

        me.showNextHelp();
    },

    /**
     * 吹き出しパネルendイベント時の処理。
     * @param {Sample.view.balloon.Panel} panel 吹き出しパネル
     */
    onEndBalloonPanel: function (panel) {
        var me = this,
            spot = me.getSpot();

        panel.destroy();

        me.setCurrentHelpIndex(null);

        spot.hide();
    },

    /**
     * 吹き出しパネルdestroyイベント時の処理。
     */
    onDestroyBalloonPanel: function () {
        this.setBalloon(null);
    },

    /**
     * 現在の参照情報を返す。
     * @return {object} 現在の参照情報
     */
    currentHelpRefInfo: function () {
        var me = this,
            currentHelpIndex = me.getCurrentHelpIndex();

        if (currentHelpIndex === null) {
            currentHelpIndex = 0;
        }

        return me.getHelpRefInfo(currentHelpIndex);
    },

    /**
     * 前の参照情報を返す。
     * @return {object} 前の参照情報
     */
    prevHelpRefInfo: function () {
        var me = this,
            currentHelpIndex = me.getCurrentHelpIndex(),
            index;

        if (currentHelpIndex > 0) {
            index = currentHelpIndex - 1;
        } else {
            index = 0;
        }

        return me.getHelpRefInfo(index);
    },

    /**
     * 次の参照情報を返す。
     * @return {object} 次の参照情報
     */
    nextHelpRefInfo: function () {
        var me = this,
            currentHelpIndex = me.getCurrentHelpIndex(),
            index;

        if (currentHelpIndex === null) {
            index = 0;
        } else if (currentHelpIndex >= 0) {
            index = currentHelpIndex + 1;
        }

        return me.getHelpRefInfo(index);
    },

    /**
     * 指定されたインデックスのヘルプ参照情報を返す。
     *
     * @param {number} index インデックス
     * @return {object} ヘルプ参照情報
     */
    getHelpRefInfo: function (index) {
        var me = this,
            helpRefs = me.helpRefs,
            refInfo = {
                ref: null,
                first: false,
                last: false,
                index: index
            };

        if (index === 0) {
            refInfo.first = true;
        } else if (index === helpRefs.length - 1) {
            refInfo.last = true;
        }

        refInfo.ref = helpRefs[index];

        return refInfo;
    },

    // @override
    getSpot: function () {
        var me = this,
            spot = me.callParent(arguments);

        if (!spot) {
            spot = Ext.create('Ext.ux.Spotlight', {
                easing: 'easeOut',
                duration: 300
            });
            me.setSpot(spot);
        }

        return spot;
    }
});
@charset "UTF-8";

.sample-panel {
  .nav-toolbar {
    background-color: $base-color;

    .brand {
      color: #fff;
    }
  }
}

.#{$prefix}spotlight {
  background-color: #999;
  z-index: 8999;
  position: absolute;
  top: 0;
  left: 0;
  @include opacity(.5);
  width: 0;
  height: 0;
  zoom: 1;
  font-size: 0;
}

吹き出し部分

ビューモデルやビューコントローラも使わず、スタイルも吹き出しっぽくしてなくて、結構手抜きです。

Ext.define('Sample.view.sample.balloon.Panel', {
    extend: 'Ext.panel.Panel',
    xtype: 'sample_balloon_panel',

    cls: 'sample-balloon-panel',

    floating: true,
    bodyPadding: 15,
    maxWidth: 300,

    // @override
    initComponent: function () {
        var me = this;

        Ext.apply(me, {
            buttons: [
                {
                    text: 'Prev',
                    hidden: me.first,
                    handler: function () {
                        var p = this.up('sample_balloon_panel');
                        p.fireEvent('prev', p);
                    },
                    scope: 'this'
                },
                {
                    text: 'Next',
                    hidden: me.last,
                    handler: function () {
                        var p = this.up('sample_balloon_panel');
                        p.fireEvent('next', p);
                    },
                    scope: 'this'
                },
                {
                    text: 'End',
                    hidden: !me.last,
                    handler: function () {
                        var p = this.up('sample_balloon_panel');
                        p.fireEvent('end', p);
                    },
                    scope: 'this'
                }
            ]
        });

        me.callParent(arguments);
    }
});
@charset "UTF-8";

.sample-balloon-panel {
  background-color: #fff;

  .#{$prefix}panel-body {
    color: #666;
    font-size: 16px;
    line-height: 1.2;
  }
}