2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ExtJS - キーボードでボタンのクリックをするプラグイン

Posted at

この記事は Sencha Advent Calendar 2014 18日目の記事です。


キーボード入力でボタンを押せる
ExtJSのプラグインを作ってみようと思います。

要件

プラグインの要件を以下と定めます。

  1. ファンクションキーの押下でボタンのクリックがされること
  2. ボタンとキーの対応は自由に設定できること
  3. ボタンがクリックできる条件下(表示・活性)でのみ動作すること

実装

簡単に作り方を解説していきます。

1. プラグインクラス定義

いつも通りExt.defineを使ってクラス定義をしていきます。
extendはExt.plugin.Abstractを指定します。
プラグインのベースクラスです。

これから作るプラグイン名は BtnKeyMapperとします(命名センスのなさはお察し)。

Ext.define('Adventer.plugin.BtnKeyMapper', {

    // {{{ extend

    extend  : 'Ext.plugin.Abstract',

    // }}}
    // {{{ alias

    alias: 'plugin.btnkeymapper'

    // }}}

});

これで何もしない空のプラグインがとりあえずできました。

前回作ったフォームにボタンを置いて読み込んでみます。

  /* Form.js */

    // {{{ tbar

    tbar: [
        {
            text: '保存',
            plugins: [{
                ptype: 'btnkeymapper'
            }]
        }
    ],

    // }}}

スクリーンショット 2014-12-18 0.17.35.png

もちろんまだ何も変わりません。
ここからが本番です。

2. initメソッド作成

initメソッドは、プラグインの起点になります。
pluginを設定したコンポーネントが引数(ここでは client)として渡されます。
このclientになんやかんやしていきます。
ちなみに、initが呼ばれるタイミングは コンポーネントの initComponentが呼ばれた後になります。レンダリング前ですね。

/* plugin.BtnKeyMapper.js*/

// {{{ init

init: function (client) {
    var me = this;

    console.log(clinent);
},

// }}}

スクリーンショット 2014-12-18 0.12.37.png

3. どのキーを割り当てるか?

この設定はボタン側でやりますね。
とりあえずプラグインにコンフィグを渡す形を想定。

/* Form.js*/
        {
            text: '保存',
            plugins: [{
                ptype: 'btnkeymapper',
                keyCode: Ext.event.Event.F1
            }]
        }

プラグイン側でとれるか確認。
アクセサーメソッドが欲しかったので、configに入れてinitConfigをコンストラクタで呼んでおきます。

/* plugin.BtnKeyMapper.js*/

// {{{ config

config: {
    keyCode: null
},

// }}}
// {{{ constructor

constructor: function (config) {
    this.callParent(config);
    this.initConfig(config);
},

// }}}
// {{{ init

init: function (client) {
    var me = this;

    console.log('KeyCode:' + me.getKeyCode());

// }}}

},

スクリーンショット 2014-12-18 0.26.02.png

無事とれたので、このキーコードのイベントをまず拾います。

4. キーイベントを拾う

キーイベントを拾うには Ext.util.KeyMapを使うと簡単です。

/* plugin.BtnKeyMapper.js*/

// {{{ init

スクリーンショット 2014-12-18 0.37.57.png

init: function (client) {
    var me = this,
        keyCode = me.getKeyCode(),
        bind;

    bind = {
        target: document,
        key   : keyCode,
        fn    : me.doEmulate,
        defaultEventAction: 'stopEvent',
        scope : me
    };

    me.keyMap = new Ext.util.KeyMap(bind);

},

// }}}
// {{{ doEmulate

// キーイベントハンドラー
doEmulate: function(keyCode, e) {
    var me = this;

    console.log('type : ' + keyCode);

}

// }}}

Ext.util.KeyMap にコンフィグオブジェクトを渡して、キーイベントと、呼び出す関数を設定します。

スクリーンショット 2014-12-18 0.37.57.png

ひとまず、document全体のイベントですが拾えました。
この辺はあとで調整したい。

5. ボタンをクリックさせる

実はpluginは、cmp というプロパティに設定されたコンポーネントを持っています。
そいつに対してクリックの動作をしてやればOKです。

まずはクリックイベントのハンドラーを用意しておきます。
本来はViewControllerに書くべきですが、試しなのでViewに直接書いてしまいます。

/* Form.js*/

{
    text: '保存',
    plugins: [{
        ptype: 'btnkeymapper',
        keyCode: Ext.event.Event.F1
    }],
    listeners: {
        'click': function(btn) {
            Ext.Msg.alert(
                'イベント',
                '保存ボタンがクリックされました!'
            );
        }
    }
}

そして、プラグイン側でクリックイベントに変換します。

/* plugin.BtnKeyMapper.js*/

// {{{ doEmulate

// キーイベントハンドラー
doEmulate: function(keyCode, e) {
    var me = this,
        button = me.cmp;// ボタン

    button.fireEvent('click', button, e);

}

// }}}

ボタンクリック・F1キー入力でこれが出ます。

スクリーンショット 2014-12-18 0.49.37.png

問題発生!

/* Form.js*/

{
    text: '保存',
    plugins: [{
        ptype: 'btnkeymapper',
        keyCode: Ext.event.Event.F1
    }],
    handler: function(btn) {
        Ext.Msg.alert(
            'イベント',
            '保存ボタンがクリックされました!'
        );
    }
}

これが動きませんでした。。

修正!

/* plugin.BtnKeyMapper.js*/

// キーイベントハンドラー
doEmulate: function(keyCode, e) {
    var me = this,
        button = me.cmp;// ボタン

    // TODO: 見た目変える

    // event発火/handler実行
    button.fireHandler(e);

}

// }}}

fireHandlerでclickイベントの発火とhandlerの呼び出しが行われます。

見た目

見た目上で押されたように見せるのはちょっと無理矢理ですが、
クラスの付け替えをdeferを使って行います。
ここはもう少しなんとかしたい所……

    // 見た目変える
    button.addCls('x-btn-pressed’);// 押された

    Ext.defer(function() {
        button.removeCls('x-btn-pressed’);// 離れた
    },300);

ここまでで

要件1と要件2はクリアかな。

6. ボタンの状態チェック

ボタンの状態チェックを入れます。
以下2点をチェック。

  1. ボタンが表示されていること
  2. ボタンがenableであること

チェックは doEmulate の前でやります。
1の条件はこのボタンをもつコンテナーが表示されているかまで見ないといけません。
どこまで見るかは設定できるようにしましょうか。

parentQuery というコンフィグで設定できるようにします。

/* Form.js*/

        {
            text: '保存',
            plugins: [{
                ptype: 'btnkeymapper',
                keyCode: Ext.event.Event.F1,
                parentQuery: 'form'
            }],
            handler: function(btn) {

プラグイン側ではそのクエリをもとに親コンテナの状態を確認します。

/* plugin.BtnKeyMapper.js*/

// {{{ config

config: {
    keyCode: null,
    parentQuery: 'container'
},

// }}}
…
// {{{ doEmulate

// キーイベントハンドラー
doEmulate: function(keyCode, e) {
    var me = this,
        button = me.cmp;// ボタン

    if (!me.checkBtnEnable(button, keyCode, e)) {
        return false;
    }

    // 見た目変える
    button.addCls('x-btn-pressed');

    Ext.defer(function() {
        button.removeCls('x-btn-pressed');
    },300);

    // event発火/handler実行
    button.fireHandler(e);

},

// }}}
// {{{ checkBtnEnable

checkBtnEnable: function(button, keyCode, e) {
    var me              = this,
        parent          = button.up(me.getParentQuery()),
        parentIsVisible = true,
        clickable       = true;

    // 親コンテナーの状態チェック
    parentIsVisible = parent.isVisible();

    // 表示状態 かつ 活性状態であること
    clickable = parentIsVisible && button.isVisible() && !button.disabled;

    return clickable;
}

// }}}

要件3 もクリアです!

7. お掃除

いろいろお掃除します。
ボタンが破棄されたらイベントハンドラも破棄したりとか。

以下、全コードです。

Ext.define('Adventer.plugin.BtnKeyMapper', {

    // {{{ extend

    extend  : 'Ext.plugin.Abstract',

    // }}}
    // {{{ alias

    alias: 'plugin.btnkeymapper',

    // }}}
    // {{{ mixins

    mixins: {
        observable: 'Ext.util.Observable'
    },

    // }}}
    // {{{ requires

    requires: [
        'Ext.util.KeyMap'
    ],

    // }}}
    // {{{ config

    config: {
        /**
        * @cfg {Number} keyCode
        * 監視するキーイベント
        * @accessor
        */
        keyCode: null,

        /**
        * @cfg {String} parentQuery
        * ボタンが表示状態かを確認する親コンテナ
        * @accessor
        */
        parentQuery: 'container'
    },

    // }}}
    // {{{ constructor

    constructor: function (config) {
        this.callParent(config);
        this.mixins.observable.constructor.call(this, config);
        this.initConfig(config);
    },

    // }}}
    // {{{ init

    init: function (client) {
        var me = this,
            bind;

        // ボタンがレンダリングされている間だけ実行するようにする
        me.mon(client, {
            destroy    : me.stopEmulation,
            afterrender: me.startEmulation,
            scope: me
        });

    },

    // }}}
    // {{{ startEmulation

    startEmulation: function(button) {
        var me = this,
            keyCode = me.getKeyCode(),
            bind   = {
                target: document,
                key   : keyCode,
                fn    : me.doEmulate,
                defaultEventAction: 'stopEvent',
                scope : me
            };

        me.keyMap = new Ext.util.KeyMap(bind);

    },

    // }}}
    // {{{ stopEmulation

    stopEmulation: function(button) {
        var me = this;
        // keyイベント削除
        me.keyMap.destroy();
    },

    // }}}
    // {{{ doEmulate

    // キーイベントハンドラー
    doEmulate: function(keyCode, e) {
        var me = this,
            button = me.cmp;// ボタン

        if (!me.checkBtnEnable(button, keyCode, e)) {
            return false;
        }

        // 押されて
        button.addCls('x-btn-pressed');
        // 離す
        Ext.defer(function() {
            button.removeCls('x-btn-pressed');
        },300);

        // event発火/handler実行
        button.fireHandler(e);

    },

    // }}}
    // {{{ checkBtnEnable

    checkBtnEnable: function(button, keyCode, e) {
        var me              = this,
            parent          = button.up(me.getParentQuery()),
            parentIsVisible = true,
            clickable       = true;

        // 親コンテナーの状態チェック
        parentIsVisible = parent.isVisible();
        // 表示状態 かつ 活性状態であること
        clickable = parentIsVisible && button.isVisible() && !button.disabled;

        return clickable;
    }

    // }}}


});

まとめ

  • pluginは Ext.plugin.Abstract を継承して作ります。
  • plugin内で this.cmp に対してなんやかんやして作っていきます。
  • keyイベントを拾うのは Ext.util.KeyMap を使うと楽。

作る過程を書いていった結果、無駄に長くなってしまいました。反省。
コードの無駄も多いのでまだまだ調整の必要ありですがご勘弁を。

明日は toshimitsusato さんです。

2
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?