backbone.js 教程(1) View & Model & Collection

Backbone.js Overview

  • 它由Jeremy Ashkenas開發,最初發行於2010-10-13
  • 它是一個輕量的JavaScript類庫,只依賴於underscore.js,非強制依賴於jquery,大小隻有7.6Kb,做爲一個框架級的js文件,它已經很是小了
  • 它提供了Model-View-Presenter(MVP)的框架模式,能夠幫助前端開發搭建一個層次清晰的Web應用框架
  • 它提供了models和collections來封裝數據結構,提供了views來操做DOM,提供了自定義事件將數據結構和DOM操做綁定在一塊兒

Environment Setup

要完整使用BackboneJS,須要引入如下jshtml

  • Underscore.js(>= 1.8.3)或者lodash.js
  • Jquery.js(>= 1.11.0)
  • Json2.js(若是須要支持IE)

在沒有npm的環境下,能夠下載壓縮包或者使用CDN。前端

<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.0/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>

Bakbone.js View

負責操做DOM,採用 OO 的思想來操做一個視圖。jquery

Backbone.js View API

  • extend 每個自定義的View都必須由Backbone.View類extend而來,override父類中的屬性
  • initialize() View的構造函數,每當new一個實例時,就會調用該方法
  • render() 一般將view的渲染邏輯寫在此方法中,並在initialize中調用它
  • template() View渲染時的模板,可使用underscore的模板,也可使用其餘任意JS模板引擎
  • el View對應的DOM對象,可使用id選擇器,類選擇器來定義
  • $el View對應的jQuery對象,方便使用jQuery的方法來操做DOM
  • tagName View對應的DOM節點的標籤名稱,默認是「div」
  • id View對應的DOM節點的id屬性
  • className View對應的DOM節點的class屬性
  • events 給View綁定事件
  • remove() 移除一個view,其el將從DOM中移出,綁定的事件也將中止監聽

Create a View

只須要擴展視圖構造函數 Backbone.View, 傳入Dom相關的屬性。ajax

示例 假如,須要在DOM中動態添加一個id=「root」的div。npm

不使用backbone.js,咱們一般這樣實現。api

// app.js
function addRoot() {
  var el = document.createElement('div');
  el.id = 'root';
  el.innerHTML = 'Hello Backbone!!!';
  document.body.appendChild(el);
}
addRoot();

使用backbone.js,咱們這樣實現:數據結構

// app.js
var AppView = Backbone.View.extend({
  tagName: 'div',
  id: 'root',
  initialize: function () {
    this.render();
  },
  render: function () {
    this.el.innerHTML = 'Hello Backbone!!!';
    return this;
  }
});

var appView = new AppView();
document.body.appendChild(appView.el);
  • tagName 指定這個element 是一個div
  • id 指定這個div的id屬性值
  • 當 調用 new AppView() 時,執行initialize() 函數
  • render() 函數用於渲染這個element

Get Existed Element

假如,在html中已經定義了div#root這個element,想修改它的內容。app

使用Backbone.js怎麼來操做這個element呢?框架

// index.html
<body>
  <div id="root">loading...</div>
</body>
var AppView = Backbone.View.extend({
  el: '#root',
  initialize: function () {
    this.render();
  },
  render: function () {
    this.el.innerHTML = 'Hello Backbone!!!';
    return this;
  }
});
var appView = new AppView();

Bind events

格式:ide

events: {
    'event1 selector1': 'function name1',
    'event2 selector2': 'function name2',
        ...
}

示例 有這樣一個小應用,在input中輸入後,回車,添加一個new goal;點擊每個goal後面的remove,移除此項目。

// index.html
<div id="root" class="color-dark">
  <header>
    <h2>My Life Goals</h2>
    <input id="new-goal" type="text" placeholder="add a new goal">
  </header>
  <ul id="goal-list">
    <li class="goal-item">Goal one <a class="btn-remove">remove</a></li>
    <li class="goal-item">Goal two <a class="btn-remove">remove</a></li>
    <li class="goal-item">Goal three <a class="btn-remove">remove</a></li>
    <li class="goal-item">Goal four <a class="btn-remove">remove</a></li>
  </ul> 
</div>
  • 給input綁定一個 keypress 事件
  • 給每個 .btn-remove 綁定一個click事件
// app.js
var AppView = Backbone.View.extend({
  el: '#root',
  … …
  events: {
    'keypress #new-goal': 'addGoal',
    'click .btn-remove': 'clear',
  },
  addGoal: function(ev) {
    if (ev.keyCode != 13) return;
    console.log('addGoal');
    // To do
  },
  clear: function() {
    // To do
  }
});
var appView = new AppView;

How to change view

在引入Backbone.js的Model以前,咱們能夠這樣來實現 addGoal 方法。

addGoal: function(ev) {
  if (ev.keyCode != 13) return;
  var newGoal = $('#new-goal').val();
  if(newGoal === '') return;
  var goalHtml = '<li class="goal-item">'+   newGoal +'<a class="btn-remove">remove</a></li>';
  $('#goal-list').append(goalHtml);
  $('#new-goal').val('');
}

在Backbone.js 出現以前,當數據發生變化視圖須要從新渲染時,咱們一般使用js或jQuery來進行DOM操做,改變展現的內容。

這樣作data和視圖渲染混在一塊兒,顯得很亂;並且,若是視圖上要顯示的屬性不少,拼接的代碼就很長很長。

因此,使用Backbone.js 的Model和Collection 將data和View 進行分離。

Bakbone.js Model & Collection

Model的做用

  • 封裝數據結構
  • 處理業務邏輯
  • 從server 加載、保存數據
  • 當data發生變化時觸發事件,好比從新渲染視圖

Collection的做用

Collection是Model的有序集合,和Model同樣用於數據加載、保存,監聽數據變化,還可使用 Underscore.js 提供的方法來操做Collection。

主要適用於list、table等視圖的渲染。在本例中,就須要定義一個Collection來渲染列表,並監聽Collection的變化。

定義Model和Collection

// Goal Model
var Goal = Backbone.Model.extend({
  defaults: {
    title: ''
  }
});

// Goal Collection
var GoalCollection = Backbone.Collection.extend({
  model: Goal,
});

使用template

下面這段代碼,有一些地方是相同的,爲了不重複代碼,可使用模板來渲染。

<ul id="goal-list">
    <li class="goal-item">Goal one <a class="btn-remove">remove</a></li>
    <li class="goal-item">Goal two <a class="btn-remove">remove</a></li>
    <li class="goal-item">Goal three <a class="btn-remove">remove</a></li>
    <li class="goal-item">Goal four <a class="btn-remove">remove</a></li>
  </ul>

在html中定義模板

把重複的部分抽出來,定義模板時使用<script>標籤,但這裏的type是text/template,而後給它一個id,用於在View中經過id來獲取它。

<body>
  <div id="root" class="color-dark">
    <header>
      <h2>My Life Goals</h2>
      <input id="new-goal" type="text" placeholder="add a new goal">
    </header>
    <ul id="goal-list">
      <!-- template -->
    </ul>
    </div>

  <script type="text/template" id="item-template">
    <li class="goal-item"><%= title %><a class="btn-remove">remove</a></li>
  </script>
</body>
  • <%= %>表示插入變量
  • <% %>表示插入任意JS代碼段
  • <%- %>表示插值並進行轉義

View中定義解析函數template()

在js中定義GoalView,用於生成每個Goal對應的<li>節點。

BackboneJS中的template實際上調用的是underscore.js的template方法,該方法能夠將 JavaScript 模板編譯爲能夠用於頁面呈現的函數,它返回的是一個函數

_.template(templateString, [settings])

render時調用template

而後在render中調用template方法,把model對象做爲參數傳入。

// app.js

// Goal Model
var GoalModel = Backbone.Model.extend({
  defaults: {
    title: '' // 默認屬性值
  }
});

// Goal Collection
var GoalCollection = Backbone.Collection.extend({
  model: GoalModel,
});

var GoalView = Backbone.View.extend({
  tagName: 'li',
  initialize: function () {
    this.render();
  },
  template: function () {
    return _.template($('#item-template').html()); //根據模板的id來獲取模板定義的內容
  },
  render: function () {
    this.$el.html(this.template()(this.model.toJSON()));
  },
  events: {    
    'click .btn-remove': 'clear', // 綁定事件
  },
  clear: function() {
    // To do
  }
});

var AppView = Backbone.View.extend({
  el: '#root',
  … …
  events: {
    'keypress #new-goal': 'addGoal',
  },
  addGoal: function(ev) {
    if (ev.keyCode != 13) return;
    console.log('addGoal');
    // To do
  },
  
});

測試效果:

var view = new GoalView({ model: {title: 'My first goal'} });
this.$("#goal-list").append(view.$el);

bind Collection to View

在AppView中,修改addGoal的添加模式,將原來的直接操做DOM,修改成經過data的變化來觸發DOM的渲染。

  • 在AppView中實例化一個GoalCollection,命名爲goalList
  • Keypress事件觸發時,修改goalList,這裏調用了Backbone.Collection中的push()方法
var AppView = Backbone.View.extend({
  el: '#root',
  initialize: function () {
    this.goalList = new GoalCollection(); 
    this.render();
},
  render: function () {
    return this;
  },
  events: {
   'keypress #new-goal': 'addGoal'
  },
  addGoal: function (ev) {
    if (ev.keyCode != 13) return;
    var inputVal = $('#new-goal').val(); // 獲取輸入的值
    if (inputVal === '') return;
    this.goalList.push({ title: inputVal }); // push到Collection
    $('#new-goal').val('');
  },
});

可是,此時,你會發現雖然goalList發生了變化,可是頁面並無跟着渲染。

由於,View並無對Collection的變化進行監聽。

Model 和 Collection的事件監聽

View 監聽 Model或Collection的變化

在AppView中,經過listenTo()方法,監聽Collection的變化,當Collection發生變化時,觸發內部的某個方法。

object.listenTo(other, event, callback)

listenTo 用於一個對象,監聽另外一個對象的變化

中止監聽使用stopListening

object.stopListening([other], [event], [callback])

監聽add事件
var AppView = Backbone.View.extend({
    el: '#root',
    initialize: function () {
        this.goalList = new GoalCollection();
        this.render();
        this.listenTo(this.goalList, 'add', this.addOne);
        // or 
        // this.goalList.on('add', this.addOne, this);
    },
    render: function () {
        return this;
    },
    events: {
        'keypress #new-goal': 'addGoal'
    },
    addGoal: function (ev) {
        if (ev.keyCode != 13) return;
        var inputVal = $('#new-goal').val();
        if (inputVal === '') return;
        this.goalList.push({ title: inputVal });
        // or this.goalList.add({ title: inputVal });

        $('#new-goal').val('');
    },
    addOne: function (goal) {
        var view = new GoalView({ model: goal });
        this.$("#goal-list").append(view.$el);
    }
});

這裏爲何監聽的event是 add,而不是 push?

由於push()方法底層其實調用的是add()方法。

this.goalList.push({ title: inputVal });

修改成

this.goalList.add({ title: inputVal });

效果相同

監聽destroy事件

在上一步中,已經給GoalView綁定了Goal這個Model,那麼在View中就可使用Model來控制View的渲染。在GoalView中須要監聽GoalModel的變化,goalModel移除時,銷燬視圖。

var GoalView = Backbone.View.extend({
    tagName: 'li',
    initialize: function () {
        this.render();
        this.listenTo(this.model, 'destroy', this.remove);
        //or this.model.on('destroy', this.remove, this);
    },
    template: function () {
        return _.template($('#item-template').html());
    },
    render: function () {
        console.log('model', this.model.toJSON());
        this.$el.html(this.template()(this.model.toJSON()));
    },
    events: {
        'click .btn-remove': 'clear',
    },

    clear: function() {
        this.model.destroy(); 
    }
});

destroy model後,view 也會從DOM中移除,同時綁定的事件也會中止監聽。

this.remove 是View 內置的函數。

remove()方法不只能夠從DOM中移除view對應的節點,同時還能中止節點上綁定的事件監聽。

Model或Collection 自我監聽變化

在AppView中,還能夠經過調用on()方法,讓Collection監聽本身的變化。

object.on(event, callback, [context])

這種用法是本身監聽本身。

若是想中止監聽,使用off()方法

object.off([event], [callback], [context])

this.listenTo(this.goalList, 'add', this.addOne);

等效於

this.goalList.on('add', this.addOne, this);

this.listenTo(this.model, 'destroy', this.remove);

等效於

this.model.on('destroy', this.remove, this);

爲何要傳入context?

由於調用on()方法的是this.goalList,若是不傳入context,那麼在addOne()調用時,默認的this指代的是this.goalList,而不是AppView的實例了。

所以,爲了保證上下文都是View的實例,須要傳入context。

使用 bind() 或 bindAll() 修改context

能夠在使用on()時,不傳入context,而使用 .bind() 或 .bindAll() 來綁定context

在使用bind時,必須使用bind返回的函數

// 使用 bind
initialize: function () {
    this.render();
    this.remove = _.bind(this.remove, this); // 返回值是一個函數
    this.model.on('destroy', this.remove);
},

使用bindAll很是方便,沒必要考慮返回值

// 使用 bindAll
initialize: function () {
    this.render();
    _.bindAll(this, 'remove', 'clear'); // 能夠同時改變多個函數的context
    this.model.on('destroy', this.remove);
},
相關文章
相關標籤/搜索