backBone 入门指南

本文非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:
  • AppView:整个页面都属于AppView,一般情况下一个App只有一个AppView
  • MainView:App的主要功能显示区域,上图中的Dashboard部分,导航栏中的任意功能都有其对应的MainView,根据导航功能的选择进行切换

    AppView和MainView的划分比较适合本文范例的架构,具体应用具体分析,作为研发人员应该是不断创新,而不是被条条框框限制。

    切换到Report 功能的效果图: 
    bootstrap report

请注意一个细节:左侧导航栏有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 
      • app-router.js
    • templates 
      • layout 
        • dashboard.html
        • report.html
      • app.html
    • views 
      • layout 
        • dashboard.js
        • report.js
      • app.js
    • 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信息  -->
</head>

<body id="appView">  <!-- 这里标注body为#appView,确定整个App处理的区域,可以根据实际需要改变 -->
</body>
    <!-- 这里就是用requirejs加载同一目录下的main.js -->
    <script data-main="main" src="../bower_components/requirejs/require.js"></script>

</html>
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

main.js

main.js最重要的作用是初始化router,下面的代码非常简单:

'use strict';
//requirejs的配置部分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) {
    //参数 AppRouter指向了下一步要实现的app-router.js
    //AppRouter建立后就完成了对页面url的监视
    var router = new AppRouter('#appView');
    backbone.history.start();   
    //如果需要启用 HTML5 特性 pushState 的配置调用,修改上面的backbone.history.start();并需要后端支持rewrite,这里不做进一步说明

});
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

router

routers/app-router.js

router是在backbone中是非常重要的环节,其要完成的工作主要有:

  • AppView初始化
  • Route切换处理
define([
    'jquery',
    'backbone',
    'views/app'
],function($,Backbone,AppView){
    "use strict";
    var AppRouter=Backbone.Router.extend({
        initialize:function(el){
            this.el=el;//表明本应用对应的已有DOM元素,比如body、#appView等等
            this.$el=$(el);//转为jquery对象
            console.log("AppRouter initialized!");
            var  router=this;
            this.cleanAppView();
            var appView=new AppView();//主要工作1:初始化AppView
            this.setAppView(appView);
        },
        routes: { //主要工作2:Route切换处理
            '*filter': 'setFilter',//*filter会拦截所有的请求,需要进行过滤操作是可用
            "" : "getIndex",//默认页面,一般为MainView之一,本例中为显示Overview信息
            "overview":"getOverview",//在MainView中显示预览信息
            "report":"getReport",//在MainView中显示报表信息
            "group":"getGroup",//在MainView中显示分组信息
            "process":"getProcess",//在MainView中显示流程信息

            "*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) {
            // Set the current filter to be used
            //Common.TodoFilter = param || '';
            console.log("route.setFilter invoked,param="+param);
            // Trigger a collection filter event, causing hiding/unhiding
            // of the Todo view items
            //Todos.trigger('filter');
        },

        //--------------以下为内部函数--------------
        cleanAppView:function () {/*清除当前页面的appView*/
            if (this.appView) {
                this.appView.remove();
                this.appView = null;
            }
        },
        setAppView:function(newView){/*切换App视图函数*/
            this.cleanAppView();
            this.appView=newView.render().$el.appendTo($(this.el));
        },
        cleanMainview:function(){//清除当前的MainView
            if(this.mainView){
                this.mainView.remove();
                this.mainView=null;
            }
        },
        setMainview:function(newView){//设置当前的MainView
            this.cleanMainview();
            this.mainView=newView.render().$el.appendTo(this.$el.find("#main"));//重要点:在AppView的模板中给MainView预留的id
        }

    });
    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',//重点:与本视图相关的template被注入
    'domReady!'
], function ($, _, Backbone,appTemplate) {
    'use strict';

    var AppView = Backbone.View.extend({

        el:'body',//重点:这里指定view对应Dom中的位置,AppView一般为body,也可以外部传入
        template: _.template(appTemplate),//主要工作1:初始化与view关联的template
        events: {
            //顶部工具栏的查找、设置等等功能
            //这里定义与本视图相关的事件处理,此处忽略  
        },

        initialize: function (options) {
            this.router=options.router;//将AppRouter传递进来,可以用于路由listenTo;
            this.routes=options.routes;//AppRouter传递来的路由列表
            this.viewState=new Backbone.Model();
            this.listenTo(this.router,"route",this.onRouteChange);
            //this.listenTo(this.viewState,"change:navitem",this.changeNavItem)
        },

        render: function () {
            this.$el.empty();
            this.$el.html(this.template());//

            return this;//重点:每个view的render方法都推荐使用return this以保持链式调用。
        },
        onRouteChange:function(routename){
            this.$el.find("#sidebar >ul.nav-sidebar li").removeClass("active");//主要工作2:清除选中的navitem状态
            //主要工作2:根据route信息查找当前选中的navitem
            var $nav_a=null///this.$el.find(String.format("li>a[href='#{0}']",routename));
            var selectorTemplate="#sidebar > ul.nav-sidebar li > a[href='#{0}']";
            //console.log(String.format("routename={0}",routename));
            var $el=this.$el;
            $.each(this.routes,function(k,v,obj){
                //console.log(String.format("key={0},value={1}",k,v));
                if (!$nav_a){
                    if(v===routename)
                        $nav_a=$el.find(String.format(selectorTemplate,k));
                }
            });


            if($nav_a){//主要工作2:激活当前选中的navitem
                $nav_a.parents("li").addClass("active");
                //console.log($nav_a.html());
            }
            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">
        <!-- 重点:#main是给MainView预留的区域 -->
        </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'//处理所有a的单击事件
        },
        initialize:function(){

        },
        render:function(){
            this.$el.html(this.template(
                //给模板传递数据,模板+数据+模板处理=最后的html
                {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>
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

report mainview

report相关的视图和模板由于与dashboard几乎完全相同,未少占篇幅,这里忽略,有兴趣可自行实践下。

总结

本文给出了一个基于Backbone框架的标准应用模式,可根据需求的不同进行局部调整,最终为的是达到规范开发、屏蔽细节、专注业务的目的。有些Backbone标准用法都没在文中体现,尤其是view嵌套、行列型model和view、model和collection之间的关系、模板库的使用等等,但作为前端MVC入门和框架使用的培训提纲感觉应该够了,细节部分建议好好阅读Backbone官方API,这也是掌握一个框架所必须经历的过程。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值