backBone 入门指南

来源:互联网 发布:淘宝店铺过户新规 编辑:程序博客网 时间:2024/06/05 16:52

本文非Backbone入门级教程,很多细节可能会被无意识的忽略。Backbone框架比较简单,更多的是要理解其前端MVC思想及应用注意点,随着本文一个完整的Demo做完后,你就可以愉快的掌握使用了。本文更多的是给出一些注意点,强烈建议好好阅读官方API。Backbone基本依赖于Jquery,所以你最好对JQuery有基本的了解。本文中会利用Bootstrap演示部分重要功能,所以 你对css最好也有所了解。MVC开发离不开模板的概念,对常见的模板库比如underscore、handlebars最好都有所了解。

    • 简要介绍
    • 参考资料
    • 简要示意图
      • SPA通用顺序图
      • Backbone处理流程图
    • SPA中的view切换类型
      • router切换
      • 非router切换
    • Backbone MVC应用基本准则
    • 本文的页面布局设计
    • 编写标准Backbone架构的应用
      • 建立目录结构
      • 文件加载过程流程图
    • 各文件主要内容
      • indexhtml
      • mainjs
      • router
        • routersapp-routerjs
      • AppView
        • viewsappjs
        • templatesapphtml
      • dashboard mainview
        • viewslayoutdashboardjs
        • templateslayoutdashboardhtml
      • report mainview
    • 总结

简要介绍

Backbone是一个非常流行而又简单、好用、基于MVC架构的js前端框架,非常轻量,适合做轻量级SPA应用。 
同样适合做SPA的angularjs框架可以看我的另一篇文章:

  • javascript 框架之 AngularJS 1.x 实用指南。

参考资料

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](http://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

说明:

名称类型用途libdir存放基本的js文件[本例不涉及]stylesdir存放css文件[本例不涉及]modelsdir存放model文件[本例不涉及]templatesdir存放被view使用的模板文件viewsdir存放整个spa应用中的view,按功能分目录存放main.jsjs filespa应用启动文件index.htmlhtml filebrowser加载主文件

请注意一个细节: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,这也是掌握一个框架所必须经历的过程。

原创粉丝点击