SenchaTouch2 Ext.Menu


近年のWebサイト開発では、かつてガラケーサイト開発がそうだったように、スマートフォン開発、タブレット開発が一般化してきました。「○○のような感じで」「○○のアプリにあるような」 こんな話しを要件定義の段階でよく耳にすることも増えました。スマートフォン開発では、画面をシンプルかつ、ユーザーにわかりやすい設計にすることが大事ですが、Webアプリを含め、多くのアプリが生まれた中で、ある程度ユーザーが認識してきた操作、というのもあります。左右のスワイプで、画像送りをしたり、画面上部でさらに下にひっぱることで、その画面全体の更新がかかる、など。 そうした意味でボタンの意味にも、共通の意識が芽生えてきました。その１つがメニューボタンです。![menuicon.png](https://qiita-image-store.s3.amazonaws.com/0/34693/c0ed72d2-992b-2077-7b90-4adb174a3836.png "menuicon.png")
例えばこのようなボタンを見た時みなさんは、「メニューボタンだな」と認識できる人は多いのではないでしょうか。
「○○のような」という話しで言えば、「facebookアプリのような」メニューボタンです。 

そして、このボタンをタップした時に、どのようにメニューが表示されるのか。
これも想像できる人は多いはずです。
先述のfacebookアプリでは、タップした時にメイン画面が右にスライドされるとともに、左からメニューがスライドしてきます。

Sencha Touch2.3.xでは、このようなメニューを簡単に実装できる機能が実装されました。
その名もExt.Menu

2.3.0がリリースされてからしばらくは、APIドキュメントにも記載されていなかったこの機能でしたが、
最近になって、記載されるようになりました。

使い方については、サンプルにあるKitchen Sink にExt.Menuのサンプルがあるので、
まずはこちらを見ていただくのが良いと思います。
[Kitchen Sink Example](http://docs.sencha.com/touch/2.3.1/#!/example/kitchen-sink)のメニューにあるUserInterfaceから、さらに一番下にMenusがあるので、そこで確認ができます。

`Ext.Viewport.toggleMenu(‘left’);`などで、メニューを切り替えることができます。
そのためには、Ext.Viewport.setMenu()により、メニューを定義する必要があります。

Ext.Menuでは、このようなスライドメニューを簡単に作成できるようになりました。
しかし、みなさんが作るアプリケーションは、こんなに単純ではないかもしれません。

例えば、スライドで表示した画面が、メニューボタンだけとは限りません。

次のようなコードで、右のスライドメニューの中に、`xtype:datepickerfield`があったとしましょう。

```
Ext.define('MyApp.view.MyContainer', {
    extend: 'Ext.Container',
    alias: 'widget.my_container',
    requires: [
        'MyApp.view.MyTitleBar',
        'Ext.Menu'
    ],
    config: {
        html: 'example',
        items: [
            {
                xtype: 'mytitlebar'
            }
        ]
    },
    initialize: function() {
        this.callParent();
        var me = this;
        Ext.Viewport.setMenu(
            Ext.create('Ext.Menu', {
                width: 300,
                items:[
                    {
                        xtype: 'button',
                        text: 'Close',
                        handler: function () {
                            Ext.Viewport.hideMenu('left');
                        }
                    }
                ]
            }),
            {
                side: 'left',
                reveal: true
            }
        );
        Ext.Viewport.setMenu(
            Ext.create('Ext.Menu', {
                width: 300,
                items: [
                    {
                        xtype: 'textfield',
                        label: 'text'
                    },
                    {
                        xtype: 'datepickerfield',
                        label: 'date'
                    }
                ]
            }),
            {
                side: 'right',
                reveal: true
            }
        );
    }
});
```

すると、画面は下記のようになります。

![menu1.png](https://qiita-image-store.s3.amazonaws.com/0/34693/928878c3-ea22-5543-c86b-c903102722c9.png "menu1.png")

右の歯車のアイコンをタップすると、下図になります。
![menu2.png](https://qiita-image-store.s3.amazonaws.com/0/34693/f3e450b1-f3be-a227-bda2-ecf4ec8d1cb0.png "menu2.png")

ここに、datepickerfieldの箇所をタップすると、datepickerが現れるわけですが、、
![menu3.png](https://qiita-image-store.s3.amazonaws.com/0/34693/861e1ddf-0900-a670-a04d-c31c57e367b0.png "menu3.png")

このようにずれてしまいます。赤いしかくで囲った部分ですね。
こうなると、2つの対策を考えます。

* datepickerの位置を無理やりずらす
* Ext.Menuを諦める

datepickerの位置をずらす、という方法ですが、きっとやってできないことはないでしょうけど、
汎用性を考えると、ここはExt.Menuを使わずに、似たような動きをする作りにしてみます。
冗長な部分もあるかもしれませんが、あしからず・・・

まずは、app.js

```app.js
Ext.application({
    views: [
        'Main',
        'MyContainer',
        'MyTitleBar',
        'MyMenu'
    ],
    name: 'MyApp',

    launch: function() {

        Ext.create('MyApp.view.Main', {fullscreen: true});
    }

});
```
Main.jsは次のようにします。

```Main.js
Ext.define('MyApp.view.Main', {
    extend: 'Ext.Container',
    xtype: 'main',
    requires: [
        'MyApp.view.MyTitleBar',
        'MyApp.view.MyMenu'
    ],
    config: {
        items: [
            {xtype: 'my_container'},
            {xtype: 'my_menu'}
        ]
    }
});
```
`xtype: ‘my_container’`はメインの表示となるものです。

```MyContainer.js
Ext.define('MyApp.view.MyContainer', {
    extend: 'Ext.Container',
    alias: 'widget.my_container',

    requires: [
        'MyApp.view.MyTitleBar',
        'MyApp.view.MyMenu'
    ],

    config: {
        html: '<div style="height:768px; background: #eee;">example</div>',
        items: [
            {
                xtype: 'my_titlebar'
            }
        ]
    }
});
```
次に`xtype: ‘my_menu’`。スライドして出てくる部分です。datepickerfieldだけつけてあります。

```MyMenu.js
Ext.define('MyApp.view.MyMenu', {
    extend: 'Ext.form.Panel',
    alias: 'widget.my_menu',
    config: {
        width: '300px',
        height: '100%',
        hidden: true,
        right: '0',
        top: '0',
        scrollable: false,
        items: [
            {
                xtype: 'datepickerfield',
                label: '日付',
                name: 'Date',
                labelWidth: '35%',
                value: new Date(),
                dateFormat: 'Y/m/d',
                picker: {
                    slotOrder: ['year', 'month', 'day']
                }
            }
        ]
    }

});
```

`xtype: ‘my_titlebar’`は次のようにしました。

```MyTitleBar.js
Ext.define('MyApp.view.MyTitleBar', {
    extend: 'Ext.TitleBar',
    alias: 'widget.my_titlebar',

    config: {
        docked: 'top',
        title: 'MenuSample',
        items: [
            {
                xtype: 'button',
                align: 'right',
                handler: function(button, e) {
                    var menu = Ext.Viewport.down('my_menu’),
                        container = Ext.Viewport.down('my_container');
                    if (menu.isHidden()) {
                        menu.setZIndex(-1);
                        container.setStyle({
                            '-webkit-transition-property': 'translate',
                            '-webkit-transition-duration': '0.3s',
                            '-webkit-transition-timing-function': 'linear'
                        });

                        menu.show();
                        container.translate(-300, 0);
                        Ext.defer(function () {
                            menu.setZIndex(0);
                        }, 300);
                    } else {
                        menu.setZIndex(-1);
                        container.translate(0, 0);
                        Ext.defer(function () {
                            menu.hide();
                        }, 300);
                    }
                },
                iconCls: 'settings'
            }
        ]
    }
});
```
注目すべきは、このMyTitleBar.jsにある、handlerの中です。
メニュー部分と、コンテナー部分をそれぞれ取得しています。
ここでは、タップされるたびに、コンテナーに対してsetStyleしてしまっていますが、
どこか初期化部分や、CSS自体に設定してしまっても良いと思います。
そのsetStyleの中身ですが、

```
'-webkit-transition-property': 'translate',
'-webkit-transition-duration': '0.3s',
'-webkit-transition-timing-function': 'linear'
```
こうすることで、Ext.Menuにあったような、スライドを表現することができます。
（実は、Ext.Menuも中をみると、同じようなことをやっています。）
ちなみにleftの値を少しずつずらす方法だと、重たすぎます。
その上で、

```
container.translate(-300, 0);
```
とすることで、300px分、コンテナー左にスライドします。
その直前にメニューをshowしておけば、その後ろにメニューが存在するように見えるというわけです。

スタイルなどは、全然調整していないので不格好ではありますが、
![menu4.png](https://qiita-image-store.s3.amazonaws.com/0/34693/3c078973-60c1-ea9a-1b4a-3518eaeb407e.png "menu4.png")

ギアアイコンをタップすることで、左にスライドし、
![menu5.png](https://qiita-image-store.s3.amazonaws.com/0/34693/ac9aa12d-3286-5146-7660-98b39ea9031f.png "menu5.png")

datepickerfieldをタップしても、ちゃんと、はみ出さず表示します。
![menu6.png](https://qiita-image-store.s3.amazonaws.com/0/34693/d285a050-7c4b-127b-6c20-bbd4b0d6c29e.png "menu6.png")

Ext.Menuは便利なコンポーネントですが、適材適所で使っていきたいですね。