本文非Backbone入门级教程,很多细节可能会被无意识的忽略。Backbone框架比较简单,更多的是要理解其前端MVC思想及应用注意点,随着本文一个完整的Demo做完后,你就可以愉快的掌握使用了。本文更多的是给出一些注意点,强烈建议好好阅读官方API。Backbone基本依赖于Jquery,所以你最好对JQuery有基本的了解。本文中会利用Bootstrap演示部分重要功能,所以 你对css最好也有所了解。MVC开发离不开模板的概念,对常见的模板库比如underscore、handlebars最好都有所了解。
简要介绍
Backbone是一个非常流行而又简单、好用、基于MVC架构的js前端框架,非常轻量,适合做轻量级SPA应用。
同样适合做SPA的angularjs框架可以看我的另一篇文章:
参考资料
Backbone 官方 API
RequireJS
jQuery
Bootstrap3
HandlebarsJs
简要示意图
Blog中绘图始终是个麻烦事,这里就用序列图 和流程图 分别表示一下
SPA通用顺序图
下面的示意图不是很严谨,读者有mvc的概念的话应该很好理解
browserbrowserrouterroutercontrollercontrollerviewviewmodelmodel访问地址用户触发调用指定controller获取视图获取数据并渲染返回视图局部刷新页面
Backbone处理流程图
下面从流程的角度描绘一下Backbone的处理过程:
开始加载App初始化routerrouter监听url变化url跳转站外结束触发view切换yesno
细心的你应该能够发现,没有了controller。是的,使用backbone不需要单独建controller,这也是其比较简单的一面。
SPA中的view切换类型
router切换
- router监听浏览器地址栏url的变化,并根据变化触发view切换;
- 由于浏览器地址栏url发生了变化,可以使用浏览器收藏夹功能收藏该地址,方便下次快速进入;
非router切换
- 使用针对Dom元素的监听事件(比如jquery.on),捕获需要局部刷新的操作,通过model、ajax、localStorage等方式获取数据信息并刷新指定区域;
- 浏览器地址栏的url没有发生变化,无法使用收藏夹功能来提供快速进入的能力,可以认为这个变化是瞬态的;
router的设计非常重要,一般概要设计时就应该初具雏形。
Backbone MVC应用基本准则
这里的准则会写的比较抽象,但是写完一个backbone最基本的应用后,自然而然就会习惯,但最好先看下官方文档:Backbone 官方Api。
- 对router进行合理规划
- 根据router将view的切换与router进行绑定
- router切换一般用于最顶层App级别,比如左边sidebar、顶部navbar等等
- 非router切换一般为局部Dom事件触发的切换,例如使用on事件处理按钮点击事件;
- model的修改都要通过set方法,以便触发change事件
- view必须做到数据无关,数据都存放在model中
- 基于事件驱动的响应方式
- Dom事件只改变相关model的属性 (改变model属性值)
- Dom只在model发生改变而改变 (监听model的属性变化)
- 尽量使用listenTo监听事件,因为其会自动解绑(on、bind都是需要手动解绑的)
- 编码要保持链式调用
- 多用事件、少用回调
- 每个view都有scope,永远不要操作当前view之外的Dom
其实这些准则在其他MVC框架中也基本同样适用,只是实现细节上需要调整。
本文的页面布局设计
在Bootstrap官网中有一个布局概念的范例,本文基于其结构进行基于Backbone的SPA化,如下图: ![bootstrap overview](https://img-blog.csdn.net/20151225092145718) 熟悉Bootstrap的话就知道左侧为功能导航,右侧为功能的具体内容 因此,本范例将整个App分为2种类型的View:
请注意一个细节:左侧导航栏有Active的效果,Bootstrap 的NavItem只要设定 active css属性就能满足要求。在何处进行该属性的设定是我们要考虑的。个人建议:元素属于哪个View的范围,就应该由哪个View处理,也就是Scope的概念,上述的MVC基本原则中有提及,这里算是其一个应用实例。
在本文中左侧导航的切换只会触发右侧MainView的切换,上面两张示意图展现了两个不同的MainView,Overview和Report
编写标准Backbone架构的应用
步骤大致分为:
- 建立目录结构
- 建立main.js
- 建立router.js
- 建立view.js
- 建立与view相关联的template
- 建立与view关联的model
建立目录结构
建立目录结构是第一步,其中涉及到的基于npm和bower部分这里会忽略,具体详情可以参考我写的另一篇:javascript 前端 基于 npm和bower的SPA项目标准结构 。这里只给出基本的目录结构:
- App
- lib
- styles
- models
- routers
- templates
- layout
- dashboard.html
- report.html
- app.html
- views
- main.js
- index.html
说明:
名称 | 类型 | 用途 |
---|
lib | dir | 存放基本的js文件[本例不涉及] |
styles | dir | 存放css文件[本例不涉及] |
models | dir | 存放model文件[本例不涉及] |
templates | dir | 存放被view使用的模板文件 |
views | dir | 存放整个spa应用中的view,按功能分目录存放 |
main.js | js file | spa应用启动文件 |
index.html | html file | browser加载主文件 |
请注意一个细节:views和templates中的内容是完全一一对应的,views存放js,而templates中存放与view对应的html模板。
本文中不涉及的models其结构与views也有很大关系。
文件加载过程流程图
开始browser加载index.html加载main.js加载app-router.js初始化AppViewrouter开始监听url变化导航功能改变切换MainView结束yesno
各文件主要内容
index.html
根据requirejs的规范,在index.html最重要的是添加js启动项,加载下一步要编写的main.js,代码非常简单,下面给出范例:
<!doctype html>
<html lang="en">
<head>
</head>
<body id="appView">
</body>
<script data-main="main" src="../bower_components/requirejs/require.js"></script>
</html>
main.js
main.js最重要的作用是初始化router,下面的代码非常简单:
'use strict';
require.config({
baseUrl: "/newecenterMain/",
paths: {
jquery: "javascripts/jquery-1.7.2.min",
underscore: "javascripts/backbone/underscore-min",
backbone: "javascripts/backbone/backbone-min",
front:"views/js/front",
require([
'backbone',
'jquery',
'routers/app-router',
'domReady'
], function (backbone,$,AppRouter) {
var router = new AppRouter('#appView');
backbone.history.start();
});
router
routers/app-router.js
router是在backbone中是非常重要的环节,其要完成的工作主要有:
define([
'jquery',
'backbone',
'views/app'
],function($,Backbone,AppView){
"use strict";
var AppRouter=Backbone.Router.extend({
initialize:function(el){
this.el=el;
this.$el=$(el);
console.log("AppRouter initialized!");
var router=this;
this.cleanAppView();
var appView=new AppView();
this.setAppView(appView);
},
routes: {
'*filter': 'setFilter',
"" : "getIndex",
"overview":"getOverview",
"report":"getReport",
"group":"getGroup",
"process":"getProcess",
"*error" : "fourOfour"
},
getIndex: function(){
this.getOverview();
},
getOverview:function(){
this.setMainview(new DashboardView());
},
getReport:function(){
this.setMainview(new ReportView());
},
getGroup:function(){
this.setMainview(new GroupsView());
},
getProcess:function(){
this.setMainview(new ProcessesView());
},
setFilter: function (param) {
console.log("route.setFilter invoked,param="+param);
},
cleanAppView:function () {
if (this.appView) {
this.appView.remove();
this.appView = null;
}
},
setAppView:function(newView){
this.cleanAppView();
this.appView=newView.render().$el.appendTo($(this.el));
},
cleanMainview:function(){
if(this.mainView){
this.mainView.remove();
this.mainView=null;
}
},
setMainview:function(newView){
this.cleanMainview();
this.mainView=newView.render().$el.appendTo(this.$el.find("#main"));
}
});
return AppRouter;
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
AppView
views/app.js
app.js是整个SPA应用的View,即AppView,其负责整个App的整体显示与控制,本文中AppView主要完成的工作有:
- 初始化与View关联的template(每个view都必须完成的工作)
- 根据路由的切换改变NavBar中的active属性(监听route事件)
- 顶部工具栏的查找、设置等等功能(暂时忽略,在events中添加处理事件即可)
define([
'jquery',
'underscore',
'backbone',
'text!templates/app.html',
'domReady!'
], function ($, _, Backbone,appTemplate) {
'use strict';
var AppView = Backbone.View.extend({
el:'body',
template: _.template(appTemplate),
events: {
},
initialize: function (options) {
this.router=options.router;
this.routes=options.routes;
this.viewState=new Backbone.Model();
this.listenTo(this.router,"route",this.onRouteChange);
},
render: function () {
this.$el.empty();
this.$el.html(this.template());
return this;
},
onRouteChange:function(routename){
this.$el.find("#sidebar >ul.nav-sidebar li").removeClass("active");
var $nav_a=null
var selectorTemplate="#sidebar > ul.nav-sidebar li > a[href='#{0}']";
var $el=this.$el;
$.each(this.routes,function(k,v,obj){
if (!$nav_a){
if(v===routename)
$nav_a=$el.find(String.format(selectorTemplate,k));
}
});
if($nav_a){
$nav_a.parents("li").addClass("active");
}
else{
console.error(String.format("app.onRouteChange invoked,but $nav_a not found,routename={0}",routename));
}
}
});
return AppView;
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
templates/app.html
app.html作为与AppView关联的template,其包含的主体内容为本SAP应用的布局信息,需要特别注意的一点是标注为#main的div。
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Process Explorer</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="#">Dashboard</a></li>
<li><a href="#">Settings</a></li>
<li><a href="#">Profile</a></li>
<li><a href="#">Help</a></li>
</ul>
<form class="navbar-form navbar-right">
<input type="text" class="form-control" placeholder="Search...">
</form>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar" id="sidebar">
<ul class="nav nav-sidebar">
<li class="active"><a href="#overview">Overview <span class="sr-only">(current)</span></a></li>
<li><a href="#report">Reports</a></li>
<li><a href="#group">Groups</a></li>
<li><a href="#process">Processes</a></li>
</ul>
<ul class="nav nav-sidebar">
<li><a href="#Nav_Item">Nav item</a></li>
<li><a href="#Nav_Item">Nav item again</a></li>
<li><a href="#OneMoreWay">One more nav</a></li>
<li><a href="#AnotherNavItem">Another nav item</a></li>
<li><a href="#MoreNavigation">More navigation</a></li>
</ul>
<ul class="nav nav-sidebar">
<li><a href="">Nav item again</a></li>
<li><a href="">One more nav</a></li>
<li><a href="">Another nav item</a></li>
</ul>
</div>
<div id="main" class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
</div>
</div>
</div>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
dashboard mainview
views/layout/dashboard.js
dashboard.js对应的是overview视图,其完成的主要工作是:
- 加载相关联的template(templates/layout/dashboard.html)
- 渲染并显示template的内容
- 处理view中的事件
define([
'jquery',
'underscore',
'backbone',
'handlebars',
'text!templates/layout/dashboard.html'
],function($,_,Backbone,Handlebars,ViewTemplate){
var DashboardView=Backbone.View.extend({
template:Handlebars.compile(ViewTemplate),
events: {
'click a': 'onClick'
},
initialize:function(){
},
render:function(){
this.$el.html(this.template(
{id:1,data="test data"}
));
return this;
},
onClick:function(event){
console.log($(event.currentTarget).text());
}
});
return DashboardView;
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
templates/layout/dashboard.html
dashboard.html和app.html,还有尚未提到的report.html都是一样的性质:模板文件。更确切的说,templates目录中的所有html都是作为模板文件存在的,每个模板文件都一定有视图与其关联。
当今有很多成熟的模板库可以被直接使用,主要区别在于提供的语法、处理能力强弱差异,选择一个符合要求的即可。对于backbone来说,其强依赖的underscore功能较弱,推荐使用handlebarsjs。
<h1 class="page-header">Dashboard</h1>
<div class="row placeholders">
</div>
<h2 class="sub-header">Section title</h2>
</div>
report mainview
report相关的视图和模板由于与dashboard几乎完全相同,未少占篇幅,这里忽略,有兴趣可自行实践下。
总结
本文给出了一个基于Backbone框架的标准应用模式,可根据需求的不同进行局部调整,最终为的是达到规范开发、屏蔽细节、专注业务的目的。有些Backbone标准用法都没在文中体现,尤其是view嵌套、行列型model和view、model和collection之间的关系、模板库的使用等等,但作为前端MVC入门和框架使用的培训提纲感觉应该够了,细节部分建议好好阅读Backbone官方API,这也是掌握一个框架所必须经历的过程。