背景:
最近在做一个大型网上银行项目前端的优化,需要使用一个胖客户端的优化,大概思路就是前端通过Ajax 请求去后端获取数据,以Jason的格式返回,然后显示在页面上。由于这个系统非常庞大,胖客户端方案难免需要在客户端写大量的JS代码。我想对于任何团队来说,大量的,非结构化的代码维护起来都非常的不方便。所以BackBone进入了我的视线。
BackBone简介:
BackBone不能看做是一个framework,它是一个轻量级的MVC JS lib,它提供了一种途径可以让你结构化你的JS代码,让你以面向对象的方式来组织你的前端JS代码。这就好比我们在前端应用Domain Driven Design. 我们可以把一个非常大的项目按模块切分。 每个模块里面又可以按照BackBone的要求切分成View, Model, Collection Router.
1. BackBone View
View 就是你的页面,用来显示你的数据model中的数据,另外它还包括对页面上用户action的响应。此外通过JS 模板引擎,View可以由页面template组合而成,把显示逻辑彻底从页面上分离了开来,方便我们做View layer的extension. 下面就是一个页面,跟一个模板,以及一个back bone的view,任何view都必须继承Backbone.View。
window.BookDetailView = Backbone.View.extend({ template : _.template($('#item-detail-template').html()), render: function() { $(this.el).html(this.template(this.model.toJSON())); // 把每个单元格的值赋予隐藏的输入框 //this.setText(); return this; }, });
</head> <body> <div style="width: 700px;margin: 0 auto;" id="app"> <p id="book-form"> <label for="username">书名:</label> <input id="username" class="username" name="username"/> <label for="level">等级:</label> <select id="level" name="level" class="level"> <option value="0">好</option> <option value="1">差</option> </select> <label for="publisher">出版社:</label> <input id="publisher" name="publisher" class="publisher"/> <label for="isbn">ISBN:</label> <input id="isbn" name="isbn" class="isbn" /> <label for="author">作者:</label> <input id="author" name="author" class="author" /> <button id="add-btn">增加</button> </p> <table class="book-table" border="1" cellspacing="0" cellpadding="0"> <caption style="font-size: 14px;font-weight: bold;">图书信息表(双击编辑)</caption> <thead> <tr> <th>ID</th> <th>书名</th> <th>等级</th> <th>出版社</th> <th>ISBN</th> <th>作者</th> <th>操作</th> <th>显示详细</th> <th>隐藏详细</th> </tr> </thead> <tbody> </tbody> </table> <div id="pager"></div> <div id="detail-content">
</div> <script type="text/template" id="item-template"> <td><%= bookid %></td> <td class="username"><div class="display"><%= username %></div><div class="edit"><input class="username" name="username"></input></div></td> <td class="level"><div class="display"><%= level=="1" ? "差":"好" %></div><div class="edit"><select name="level" class="level" style="width:45px"><option value="0">好</option><option value="1">差</option></select></div></td> <td class="publisher"><div class="display"><%= publisher %></div><div class="edit"><input class="publisher" name="publisher"></input></div></td> <td class="isbn"><div class="display"><%= isbn %></div><div class="edit"><input class="isbn" name="isbn"></input></div></td> <td class="author"><div class="display"><%= author %></div><div class="edit"><input class="author" name="author"></input></div></td> <td><a href="#" class="del">删除</a></td> <td><a href="#" class="display">显示详细</a> </td> <td><a href="#" class="hidden">隐藏详细</a></td> </script>
2.BackBone Model
模型用来存储页面上要显示的数据,它是这个JS lib的核心,此外它还包括了数据验证,转换等相关的逻辑。models中的数据可以创建、校验、销毁和保存到服务端以一种Restful的API的方式。当models中值被改变时自动触发一个"change"事件、所有用于展示models数据的views都会侦听到这个事件,然后进行重新渲染。Model必须继承自Backbone.Model。下面是一个model的定义包括验证逻辑:
window.Book = Backbone.Model.extend({ // 模型值校验 validate:function(attrs){ for(var key in attrs){ if(attrs[key] == ''){ return key + "不能为空"; } if(key == 'isbn' && isNaN(attrs.isbn)){ return "ISBN必须是数字"; } } } });
3. BackBone Collection
BackBone的Collection就是model的聚集,所以的Collection必须继承自Backbone.Collection.当然当你定义一个Collection时你也必须指定它的模型。通常我们通过Collection的fetch方法跟后端交互去获取数据。
window.BookList = Backbone.Collection.extend({ model : Book, // 持久化到本地数据库 localStorage: new Store("Books") });
4.BackBone Router
BackBone router 就相当于一个路由转发器,它扮演了一个MVC中的controller的角色。在单页应用中,我们通过JavaScript来控制界面的切换和展现,并通过AJAX从服务器获取数据。
可能产生的问题是,当用户希望返回到上一步操作时,他可能会习惯性地使用浏览器“返回”和“前进”按钮,而结果却是整个页面都被切换了,因为用户并不知道他正处于同一个页面中。
对于这个问题,我们常常通过Hash(锚点)的方式来记录用户的当前位置,并通过onhashchange事件来监听用户的“前进”和“返回”动作,但我们发现一些低版本的浏览器(例如IE6)并不支持onhashchange事件。你可以看到,当URL Hash发生变化时,会执行所绑定的方法。
// Router var AppRouter = Backbone.Router.extend({ routes:{ "":"list", "wines/new":"newWine", "wines/:id":"wineDetails" }, initialize:function () { $('#header').html(new HeaderView().render().el); }, list:function () { this.wineList = new WineCollection(); var self = this; this.wineList.fetch({ success:function () { self.wineListView = new WineListView({model:self.wineList}); $('#sidebar').html(self.wineListView.render().el); if (self.requestedId) self.wineDetails(self.requestedId); } }); }, wineDetails:function (id) { if (this.wineList) { this.wine = this.wineList.get(id); if (this.wineView) this.wineView.close(); this.wineView = new WineView({model:this.wine}); $('#content').html(this.wineView.render().el); } else { this.requestedId = id; this.list(); } }, newWine:function () { if (app.wineView) app.wineView.close(); app.wineView = new WineView({model:new Wine()}); $('#content').html(app.wineView.render().el); } });
BackBone使用前提:
BackBone比较适合单页面的应用跟复杂的大型应用,此外BackBone 还依赖其他的JSlib.比如:
Backbone依赖于Underscore框架的基础方法,此外在Backbone中,对DOM和事件的操作依赖于第三方库(如jQuery或Zepto)。
Sample 下载,图书管理程序