lxyuma BLOG

開発関係のメモ

Backbone.View入門

(この資料は、社内のBackbone入門の勉強会の為に作った物の下書きです。)

これから、Backbone.Viewについての入門記事を書きます。

内容としては、Backboneを初めて学ぶ人の為の噛み砕いた入門記事にするつもりです。

Backbone.Viewの使い方

基本的な使い方は、ModelやCollectionと一緒です。流れとしては、Backboneのコンポーネントをextendした物を宣言して、必要な時にnewしていくという形になります。

var PostView = Backbone.View.extend({
  // your source
});
var postView = new PostView

Backbone.Viewの役割

Backbone.Viewの役割は、まとめると以下の通りです。

  • 役割
    1. htmlを描写する
    2. DOMと関数を紐づける
    3. Model/Collectionと紐づける

これらについて、それぞれ1つずつ見ていきたいと思います。

役割1:htmlを描写する

htmlを描写するには、render関数を使います。

render

ここに、どんなソースを書いても良いのですが、一般的には、templateを準備してこれを描写するという事がよく行われます。例を見てみましょう。

  • 元になるhtml
<div id="main"></div>
  • Viewクラス
define([
    'jquery',
    'underscore',
    'backbone',
    'templates'
], function ($, _, Backbone, JST) {
    'use strict';

    var BlogView = Backbone.View.extend({
        template: JST['app/scripts/templates/blog.hbs'],
        el: "#main",
        render: function(){
            this.$el.html(this.template(this.model.toJSON()));
            return this;
        }
    });

    return BlogView;
});
  • templateファイル
name is {{name}} !

※これは、yeomanのBackbone generatorで作ったソースの一部です。全部試したい方は、gistに書いたのでご参考に。

他の言語やフレームワークを触った事がある人は、このソースを見たら大体想像が付くと思いますが、一応、簡単に説明をしたいと思います。(なんとなく分かる方は読み飛ばして下さい)

  • 別fileとして存在する静的なtemplateを準備しておきます。これは、呼ばれる迄画面に出る事はありません。
  • この定義があった後で、Backbone.Viewのrender()の中からこのtemplateの呼び出しが行われます。
    • この時、値を受け渡す事が出来て、これをキーに紐づけると、templateの中の同名のキーの中にその値が出力されます。
    • ※よくBackboneではModelのtoJSONやattributesを渡して、そのキーをtemplateで書いておき、その値を表示させる事をします。
  • 最後に、実際のhtmlとして出力します。

template

ここでは、handlerbars.jsを使いました。

templateは、多くの種類から自由に選ぶ事が出来ます。今日、よく聞く選択肢として以下の物があるでしょう。

  • ejs
  • handlebars
  • mustache

以下の比較記事を見ると良いかと。

el

BackboneのViewで独特でやっかいなのが、elという要素です。 これは、このViewが紐づくDOM要素のセレクタを指定するためのプロパティです。 Viewがnewされる際に、$elに、elに指定したセレクタを元に取得した要素を格納します。 この後、説明していくEventや、View内の$等は、全部、$elに対して実施する操作になります。

elの指定のない場合

elは指定しない事もできます。その時、$elに、tagNameやidの指定値を元にDOM要素を作り出して格納します。もし、tagNameの指定が無ければ、勝手にdivタグの要素を作成します。 これは、実際のhtmlと結びついていない状態になりますので、どこかで、この紐付きを書いてあげる必要があります。

  • elの指定のないView
define([
    'jquery',
    'underscore',
    'backbone',
    'templates'
], function ($, _, Backbone, JST) {
    'use strict';

    var BlogView = Backbone.View.extend({
        template: JST['app/scripts/templates/blog.hbs'],
        render: function(){
            this.$el.html(this.template(this.model.toJSON()));
            return this;
        }
    });

    return BlogView;
});
  • viewを呼び出す所
            var blog = new Blog({name: "yamada"});
            var view = new BlogView({model: blog});
            $('body').html(view.render().$el);

View自体は、elが無いだけで、特に変化はありません。 Viewを呼び出す所を見てみると、render()の後に、$elが付いています。 これで、このelの指定が無いViewで作成されたDOM要素を取り出してます。

return thisについて

render関数は、慣例として、最後にreturn thisを書かないといけません。これは、何故かというと、別のViewからel要素を取り出したい時があるためです。 丁度、上のel指定のないViewソースがそのシチュエーションに該当します。renderがthisを返しているから、render().$elしてDOM要素が取れるのです。

役割2:DOMとEventを紐づける

次にDOMとEventの紐付きについて見ていきましょう。

普段、jQueryを書いていると、長いjavascriptファイルの中で、 どこでDOMとEventと関数の紐付きを書いたのか分からなくなる事はありませんか? Backbone.Viewは、こういった混乱をおさえるために、DOMとEventと関数の紐付きを書く所を決めています。 この決まった所に書く事で、どこのDOMで、どんなイベントに、どのような処理を任せたいのかが明確になります。これも例を見てみましょう。

    var BlogView = Backbone.View.extend({
        events: {
            "click #btn" : "onClickButton"
        },
        onClickButton: function(){
            console.log('click!!!');
        }
    });

Eventsの中にplainなjsのオブジェクトを書きます。 キーは、空白を挟んで2つの要素(この例ではclick#btn)があり、 それぞれ、イベントとDOMセレクタを記載します。 値としては、このイベントで実行したい関数名を書きます。

どうでしょう?とても、直感的なルールではないでしょうか?

thisについて

勘の良い人は、ここで呼ばれた関数のthisコンテキストはどうなるのか、気になる人がいらっしゃるかもしれません。 以前のversionのBackboneでは、ここで呼ばれる関数のthisを自分でbindする必要がありました。が、最近のBackboneは、このbind処理が本体に入っているので、特に意識する必要はありません。

※ただし、events内の指定以外の所で、callbackから呼び出す関数等のbindingは自分で書く必要がありますので、注意して下さい。

うまくイベントがひっかからない場合

初めてViewを書く時によくあるのですが、Event書いたのにうまく実行されない事があります。その時は、以下をチェックしましょう。

  • elが存在するのか?
  • eventで指定した要素がelの配下に存在するのか?

$elに対して、イベントのbindをしているので、elが実際のhtmlに存在しなかったり、elの中に無い要素だとうまくいきません。

役割3:Model/Collectionと紐づける

BackboneのViewはModelを監視して、状態変化に伴い処理を実行します。これを書くために、Viewには、ModelやCollection用のプロパティが準備されています。 実際の使われ方としては、以下の様な流れになる事が多いでしょう。

  1. Viewをnewする時に、model(collection)を指定
  2. View内のinitializeで、modelに対してeventをbinding
  3. 何かしらのmodelの状態変化があれば、上記のbindingを辿って関数を実行

この時、最後の関数へのthisのbindingは自分で書く必要があるので、忘れないようにしましょう。

var PostView = Backbone.View.extend({
  initialize : function() {
    this.listenTo(this.model, 'change', this.onChangeModel)
  },
  onChangeModel : function() {
    console.log('has changed!');
  }
});
post = new Backbone.Model({name: "first"});
post.set('name', 'seconds');

データバインディング

さて、modelやcollectionとの紐付けについて、基本的な話から少し脱線したいと思います。 最近、Angularjsの勢いが激しく、非常に活発になってきました。 AngularjsとBackboneの違いでよく出てくるのが、データバインディングの有無です。Backbone本体にはデータバインディングが無いので、一見劣っているように見えますが、実は、プラグインを入れると簡単に使う事が出来ます。 実際に実装する時に、データバインディングがあるかないかでソースの量が大分違うので、簡単に見てみましょう。

stickit

NYTimes社のpluginです。 使い方がとてもsimpleで良いので、見てみましょう。

http://lxyuma.hatenablog.com/entry/2013/11/07/233930

※ここは後で書く。

実装パターン

Viewを書く上で非常に頻繁に出て来るシチュエーションが、親子階層を伴うViewです。最後に、このパターンについて見てみましょう。

親子View

Backboneで開発していると、どうしても、親子の階層を持つViewを書く必要が出てきます。ここでいう「親子の階層を持つView」とは、例えば、ulタグが親のViewでliタグが子供のViewであるような階層を持ったViewの事です。さて、何故、このViewを親子の階層に分ける必要があるのでしょうか?

もし、1つのViewでこの親子階層を表現しようとすると、毎回、どのViewがどのModelを指すのかの紐付け処理を書く必要がでてきます。最悪、htmlの属性要素にカスタム属性を入れなくてはならず、せっかくjavascriptのframeworkを使っているのにスマートな形ではありません。そこで、親と子供のそれぞれをViewとして分離して、1つの子供Viewのインスタンスが、elとmodelを紐づけて持っておき、Eventに対応した処理もそのViewの中でやってしまう事で非常にsimpleでスマートな実装にする事ができるのです。

さて、実際これをソースで書くと結構面倒くさい事になります。親から見て子供の追加があったら関数A、削除があったら関数B、等々毎回同じソースを書く事になり、また、書き方も沢山あるので、場合によっては非常に複雑なソースが出来てしまいます。

そこで、MarionetteのCollectionViewとItemViewが使えます。

※ここも後で書く。

最後に

一通り、Viewがどんなものか分かった所で、是非、ソースを読んでみると良いと思います。 Backbone.Viewが恐ろしくsimpleで、非常にソース行数が少ない事に驚くと思います。 細かな動き等は、適宜Backbone.Viewを見て確かめる事をお勧めします。その方が早いです。

そうです。Backboneが非常にsimple/compactだからこそ、出来る事です。困ったらソースをgrepしてしまいましょう。