今日のリファクタリング: Backbone.jsのビューとモデル

Backbone.js お試し中です。

RowView と Row(モデル)が紐付いていて、ビューのDOM要素をクリックするとモデルのプロパティ selected がトグルし、DOM要素の見た目も選択/非選択がトグルするという場合。

var RowView = Backbone.View.extend({
  // snip

  toggleSelection: function(ev){
    var selected = ! this.model.get("selected");
    this.model.set("selected", selected);
    if(selected){
      this.$el.addClass("selected");
    }else{
      this.$el.removeClass("selected");
    }
  }
});

一旦適当に書いたらこんな風になったけど、モデルのプロパティ値をトグルするのはモデルの役割だろうと思って次のようにしてみた。

var Row = Backbone.Model.extend({
  // snip

  toggleSelection: function(){
    this.set("selected", ! this.get("selected"));
  }
});

var RowView = Backbone.View.extend({
  // snip

  toggleSelection: function(ev){
    this.model.toggleSelection();
    if(this.model.get("selected")){
      this.$el.addClass("selected");
    }else{
      this.$el.removeClass("selected");
    }
  }
});

さらに、「DOM要素がクリックされたときに呼ばれる関数」でついでにクラスの付け外しをやってるのもたぶん良くなくて(ビューの更新処理はなるべく分散させない方が凝集度高くていい気がする)、次のようにしてみた。

var Row = Backbone.Model.extend({
  // snip

  toggleSelection: function(){
    // (2) model.selected が変更されて change イベント発生
    this.set("selected", ! this.get("selected"));
  }
});

var RowView = Backbone.View.extend({
  // snip

  initialize: function(){
    // (3) モデルの change イベント発生を捕捉してビューの更新処理を呼び出す
    this.listenTo(this.model, "change:selected",
      this.render.bind(this));
  },

  events: {
    // (1) クリックされたらまずモデルにトグルさせる
    "click": function(){ this.model.toggleSelection(); }
  }

  render: function(){
    // snip

    // (4) ビューを更新
    if(this.model.get("selected")){
      this.$el.addClass("selected");
    }else{
      this.$el.removeClass("selected");
    }

    return this;
  }
});

まだまだ不慣れ感ある。