backbone-boilerplateではじめるHTML5アプリケーション開発 その5

backbone.layoutmanagerを理解する

f:id:qualitas:20140521042907p:plain

前回にひきつづきBackbone-boilerplateでTODOアプリを作成していきます。

前回では、TodoMVCで公開されているBackboneのTodoアプリを、Backbone-boilerplateと、lodash-template-loaderを利用する形に置き換えました。
今回は、これに、backbone.layoutmanagerを適用したいと思います。

backbone.layoutmanagerとは

backbone.layoutmanagerは、BackboneのViewを利用しやすくするための機能を追加してくれるプラグインです。
Backbone.Marionetteと似ていますが、layoutManagerは、Viewを機能拡張することに特化しているのが特徴です。

BackboneのViewを使うとき、どのアプリでも同じように実装する必要のある処理というのがあると思います。
例えば、以下のような処理です

  • テンプレートの読み込みや展開
  • 一覧画面表示用のViewに、その一覧の各項目を表示するためのViewを追加する

LayoutManagerは、以下のような、Viewを利用する際にアプリが実装する必要のある基本的な処理を提供してくれます。

  • テンプレートの読み込み
  • 読み込んだテンプレートの内容を対象の要素へ展開
  • ヘッダ、コンテンツ表示部、フッタのようなViewのレイアウト
  • 階層構造のViewの表示
  • View表示前、表示後のイベントハンドラ

LayoutManager については以下のドキュメントに記載があります。
https://github.com/tbranyen/backbone.layoutmanager/wiki

backbone.layoutmanagerの使い方

Viewのレイアウト

backbone.layoutmanagerを利用してViewをレイアウトする簡単なサンプルを示します。

例えば、以下のように、メインとなるレイアウトと、ログイン画面のテンプレートの2つのViewを表示する場合。

  • メイン画面のテンプレート
<section class="content"></section>

<!-- Login template below will be injected here -->
<aside class="secondary"></aside>
  • ログイン画面のテンプレート
<form class="login">
    <p>
        <label for="user">Username</label><input type="text" name="user">
    </p>
    <p>
        <label for="pass">Password</label><input type="text" name="pass">
    </p>
    <p>
        <input class="loginBtn" type="submit" value="Login">
    </p>
</form>

Login画面を表示するためのViewは以下のように定義します。

define(function(require, exports, module) {
    'use strict';

    var Backbone = require("backbone");

    var LoginView = Backbone.Layout.extend({
        template: require("ldsh!/app/templates/login"),

    });

    module.exports = LoginView;
});

そして、メイン画面のレイアウトを以下のように定義します。

define(function(require, exports, module) {
    'use strict';

    var Backbone = require("backbone");
    var LoginView = require("modules/view/LoginView");
    
    var MainView = Backbone.Layout.extend({
        el: 'main',
        
        template: require("ldsh!/app/templates/main"),

        // In the secondary column, put a new Login View.
        views: {
          ".secondary": new LoginView()
        }
    });

    module.exports = MainView;
});

LayoutManagerのviewsプロパティに、レイアウト内のViewのインスタンスを指定します。
この例の場合、.secondary classが定義された要素に、ログイン画面のテンプレート内容が挿入されます。

画面の描画は、レイアウトを定義したメイン画面のViewのrenderメソッドを呼び出した際に行われます。

define(function(require, exports, module) {
  "use strict";

  // External dependencies.
  var Backbone = require("backbone");
  var Layout = require("layoutmanager");
  var MainView = require("modules/view/MainView");
  
  var Router = Backbone.Router.extend({
      initialize : function() {
          // Use main layout and set Views.
          this.mainView = new MainView();
          // Render to the page.
          this.mainView.render();
      },
      routes: {
          "": 'index'
      },

      index: function () {

      }
  });
  
  // Defining the application router.
  module.exports = Router;
});

この仕組みを利用して、ヘッダ、コンテンツ、フッタのような構造を持つ画面をレイアウトするようなことができます。

Backbone.Layout.extend({
  views: {
    "header": new HeaderView(),
    "section": new ContentView(),
    "footer": new FooterView()
  }
});

動的にViewを置き換えたい場合、setViewメソッドを利用します。

myLayout.setView("header", new HeaderView());

LayoutManagerによるrender処理の前にはbeforeRenderメソッドが呼び出されます。また、render処理の後は、afterRender メソッドが呼び出されます。これらのメソッドをオーバライドして、イベントに対する処理を記載することができます。

    var ListView = Backbone.Layout.extend({

        beforeRender : function() {

        },
        afterRender : function() {

一覧内の各項目のViewを追加する場合、insertViewメソッドを利用します。

Backbone.Layout.extend({
  beforeRender: function() {
    this.collection.each(function() {
      this.insertView(new ListItemView());
    }, this);
  }
});

この場合、LayoutManagerのbeforeRenderメソッド内でviewの挿入処理を行うと、全ての要素の挿入後に画面表示処理が行われ、効率的です。

beforeRenderメソッド以外のタイミングでViewの挿入を行う場合、明示的にrenderメソッドを呼び出す必要があります。

  afterRender: function() {
    this.collection.each(function() {
      this.insertView(new ListItemView()).render();
    }, this);
  }

この場合、1つの要素の挿入のたびに画面の描画が行われるため、表示速度が低下したり、画面ちらつきの原因となります。

テンプレートにパラメタを渡したい場合、serializeメソッドを実装します。

Backbone.Layout.extend({
        serialize : function() {
            return this.model.toJSON();
        },

今回作成したbackbone.layoutmanagerを利用したサンプルソースは以下で確認できます。
https://github.com/si-ro/backbone.layoutmanager-sample

Todoアプリでbackbone.layoutmanagerを利用する

TodoアプリのTodoViewをLayoutManagerを利用するように変更してみます。

app/app.js

layoutmanagerを利用するため、requireを追加します。

define(function(require, exports, module) {
  "use strict";

  // External dependencies.
  var _ = require("underscore");
  var $ = require("jquery");
  var Backbone = require("backbone");
  var Layout = require("layoutmanager");
TodoView.js

既存のrenderメソッドコメントアウトし、serialize ,beforeRender , afterRender メソッドを追加します。
renderメソッドの処理の一部はafterRenderに移します。
テンプレートの読み込みと表示の部分は、LayoutManagerが行います。

/*global define*/
define(function(require, exports, module) {
    'use strict';
    var Backbone = require("backbone");
    var Common = require("modules/Common");
    var TodoView = Backbone.Layout.extend({

        tagName:  'li',

        template: require("ldsh!/app/templates/todos"),

        // The DOM events specific to an item.
        events: {
            'click .toggle':    'toggleCompleted',
            'dblclick label':   'edit',
            'click .destroy':   'clear',
            'keypress .edit':   'updateOnEnter',
            'blur .edit':       'close'
        },

        // The TodoView listens for changes to its model, re-rendering. Since there's
        // a one-to-one correspondence between a **Todo** and a **TodoView** in this
        // app, we set a direct reference on the model for convenience.
        initialize: function () {
            this.listenTo(this.model, 'change', this.render);
            this.listenTo(this.model, 'destroy', this.remove);
            this.listenTo(this.model, 'visible', this.toggleVisible);
        },
        serialize : function() {
            return this.model.toJSON();
        },
        beforeRender : function() {
            
        },
        afterRender : function() {
            this.$el.toggleClass('completed', this.model.get('completed'));

            this.toggleVisible();
            this.$input = this.$('.edit');
        },
        // Re-render the titles of the todo item.
//        render: function () {
//            this.$el.html(this.template(this.model.toJSON()));
//            this.$el.toggleClass('completed', this.model.get('completed'));
//
//            this.toggleVisible();
//            this.$input = this.$('.edit');
//            return this;
//        },
<省略>
    });

    module.exports = TodoView;
});

今回、Todoアプリの作り上、全てのViewをbackbone.layoutmanagerに置き換えるのが難しかったため、TodoViewのみLayoutManagerに変更してみました。
サンプルとしては物足りないですが、LayoutManagerが、テンプレートの読み込みと表示処理を行ってくれることがわかると思います。

今回、backbone.layoutmanagerを利用するように変更したTodoアプリのソースは以下で確認できます。
https://github.com/si-ro/todo-app2

backbone.layoutmanagerについては、以下のサイトも参考になります。
http://code.tutsplus.com/tutorials/make-backbone-better-with-extensions--net-30723