要完整使用BackboneJS,須要引入如下jshtml
在沒有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>
負責操做DOM,採用 OO 的思想來操做一個視圖。jquery
只須要擴展視圖構造函數 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);
假如,在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();
格式: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>
// 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;
在引入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 進行分離。
Model的做用
Collection的做用
Collection是Model的有序集合,和Model同樣用於數據加載、保存,監聽數據變化,還可使用 Underscore.js 提供的方法來操做Collection。
主要適用於list、table等視圖的渲染。在本例中,就須要定義一個Collection來渲染列表,並監聽Collection的變化。
// Goal Model var Goal = Backbone.Model.extend({ defaults: { title: '' } }); // Goal Collection var GoalCollection = Backbone.Collection.extend({ model: Goal, });
下面這段代碼,有一些地方是相同的,爲了不重複代碼,可使用模板來渲染。
<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>
把重複的部分抽出來,定義模板時使用<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中定義GoalView,用於生成每個Goal對應的<li>
節點。
BackboneJS中的template實際上調用的是underscore.js的template方法,該方法能夠將 JavaScript 模板編譯爲能夠用於頁面呈現的函數,它返回的是一個函數。
_.template(templateString, [settings])
而後在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);
在AppView中,修改addGoal的添加模式,將原來的直接操做DOM,修改成經過data的變化來觸發DOM的渲染。
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的變化進行監聽。
在AppView中,經過listenTo()方法,監聽Collection的變化,當Collection發生變化時,觸發內部的某個方法。
object.listenTo(other, event, callback)
listenTo 用於一個對象,監聽另外一個對象的變化
中止監聽使用stopListening
object.stopListening([other], [event], [callback])
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 });
效果相同
在上一步中,已經給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對應的節點,同時還能中止節點上綁定的事件監聽。
在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。
能夠在使用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); },