Backbone.js と jQuery.mmenu を使ったスライドナビゲーション

Backbone でモバイル版の UI を作っていて、 FacebookGmailiPhone アプリみたいな、 スライド表示されるメニューを実装したくなった。

そういったメニューを実装するための jQuery プラグインは既にいくつか存在していて、

あたりを試してみたけど、どれもシックリこない。

そんな中、最近 jQuery.mmenu っていう jQuery プラグインを知った。

触ってみた感じ、自分が求めているものに一番近い。今のところは。

そこで jQuery.mmenu と Backbone の組み合わせに挑戦。 Backbone と jQuery.mmenu を組み合わせたサンプルは次の通り。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8"/>
    <title>MMenu Backbone Sample</title>
    <link rel="stylesheet" href="mmenu.css" />
    <link rel="stylesheet" href="examples.css" />
  </head>
  <body>
    <div id="main">
      <div id="header">
        <a href="#menu"></a>
        MMenu Backbone
      </div>
      
      <div id="content">
      </div>

      <nav id="menu">
        <ul></ul>
      </nav>
    </div>

    <script id="menu_template" type="text/template">
      <a class="menu-title" href="#/menu/<%= id %>"><%= name %></a>
    </script>
    <script type="text/javascript" src="jquery.js"></script>
    <script type="text/javascript" src="jquery.mmenu.js"></script>
    <script type="text/javascript" src="underscore.js"></script>
    <script type="text/javascript" src="backbone.js"></script>
    <script type="text/javascript">
      var Menu = Backbone.Model.extend({
        defaults: {
          id: 0,
          name: ""
        }
      });

      var MenuCollection = Backbone.Collection.extend({
        model: Menu    
      });
      
      var Main = Backbone.Model.extend({
        defaults: {
          selectedMenu: null
        },
        initialize: function() {
          this.menus = new MenuCollection();
        },
        selectedMenu: function() {
          return this.get("selectedMenu");
        },
        selectMenu: function(id) {
          var menu = this.menus.get(id);
          this.set("selectedMenu", menu);
        }
      });
    
      var MenuView = Backbone.View.extend({
        template: _.template($("#menu_template").html()),
        tagName: "li",
        events: {
          "click a.menu-title": "onClick"
        },
        render: function() {
          var json = this.model.toJSON();
          var html = this.template(json);
          this.$el.html(html);
          return this;
        },
        onClick: function(e) {
          // メニューをクリックされたら手動で閉じる
          $("#menu").trigger("close");
        }
      });

      var MainView = Backbone.View.extend({
        el: $("#main"),
        initialize: function() {
          this.model.on("change:selectedMenu", this.renderContent, this);
        },
        render: function() {
          this.renderMenu();
          return this;
        },
        renderMenu: function() {
          var $ul = this.$el.find("#menu>ul");
          $ul.empty();

          this.model.menus.each(function(menu) {
            var view = new MenuView({ model: menu });
            view.render();
            $ul.append(view.el);
          }, this);
        },
        renderContent: function() {
          var $content = this.$el.find("#content");
          $content.empty();

          var menu = this.model.selectedMenu();
          if (!menu) {
            return;
          }

          $content.append(menu.get("name"));
        }
      });

      var MainRouter = Backbone.Router.extend({
        routes: {
          "menu/:id": "showMenu"
        },
        initialize: function() {
          // サンプルメニューを作成
          this.main = new Main();
          for (var i = 0; i < 20; i++) {
            this.main.menus.add({ id: i, name: "Menu" + i });
          }

          this.mainView = new MainView({ model: this.main });
          this.mainView.render();
        },
        showMenu: function(id) {
          this.main.selectMenu(id);
        }
      });

      $(function() {
        window.router = new MainRouter();
        Backbone.history.start();  

        // メニューをクリックして自動で閉じると
        // location.hash が #menu になってしまうので
        // メニューをクリックして自動で閉じないようにしておく。
        $("#menu").mmenu({
             closeOnClick  : false
        });
      });
    </script>
  </body>
</html>

ブラウザで表示してみた。

f:id:griefworker:20130522203007p:plain

左上のボタン?をクリックして、メニューを開いてみる。

f:id:griefworker:20130522203014p:plain

メニューをクリックすると

f:id:griefworker:20130522203023p:plain

メニューが閉じて、選択したメニューの名前が表示された。

jQuery.mmenu の自動開閉を有効にしていると、localtion.hash が #menu になってしまい、Backbone の Router が反応してくれなくて嵌った。使い方はシンプルだから、スンナリ実装できると思っていたんで、予想外の躓きだったな。