zoukankan      html  css  js  c++  java
  • backbone.js 教程(1) View & Model & Collection

    Backbone.js Overview

    • 它由Jeremy Ashkenas开发,最初发行于2010-10-13
    • 它是一个轻量的JavaScript类库,只依赖于underscore.js,非强制依赖于jquery,大小只有7.6Kb,作为一个框架级的js文件,它已经非常小了
    • 它提供了Model-View-Presenter(MVP)的框架模式,可以帮助前端开发搭建一个层次清晰的Web应用框架
    • 它提供了models和collections来封装数据结构,提供了views来操作DOM,提供了自定义事件将数据结构和DOM操作绑定在一起

    Environment Setup

    要完整使用BackboneJS,需要引入以下js

    • Underscore.js(>= 1.8.3)或者lodash.js
    • Jquery.js(>= 1.11.0)
    • Json2.js(如果需要支持IE)

    在没有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>
    
    

    Bakbone.js View

    负责操作DOM,采用 OO 的思想来操作一个视图。

    Backbone.js View API

    • extend 每一个自定义的View都必须由Backbone.View类extend而来,override父类中的属性
    • initialize() View的构造函数,每当new一个实例时,就会调用该方法
    • render() 通常将view的渲染逻辑写在此方法中,并在initialize中调用它
    • template() View渲染时的模板,可以使用underscore的模板,也可以使用其他任意JS模板引擎
    • el View对应的DOM对象,可以使用id选择器,类选择器来定义
    • $el View对应的jQuery对象,方便使用jQuery的方法来操作DOM
    • tagName View对应的DOM节点的标签名称,默认是“div”
    • id View对应的DOM节点的id属性
    • className View对应的DOM节点的class属性
    • events 给View绑定事件
    • remove() 移除一个view,其el将从DOM中移出,绑定的事件也将停止监听

    Create a View

    只需要扩展视图构造函数 Backbone.View, 传入Dom相关的属性。

    示例 假如,需要在DOM中动态添加一个id=“root”的div。

    不使用backbone.js,我们通常这样实现。

    // 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);
    
    
    • tagName 指定这个element 是一个div
    • id 指定这个div的id属性值
    • 当 调用 new AppView() 时,执行initialize() 函数
    • render() 函数用于渲染这个element

    Get Existed Element

    假如,在html中已经定义了div#root这个element,想修改它的内容。

    使用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();
    

    Bind events

    格式:

    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>
    
    
    • 给input绑定一个 keypress 事件
    • 给每一个 .btn-remove 绑定一个click事件
    // 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;
    
    

    How to change view

    在引入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 进行分离。

    Bakbone.js Model & Collection

    Model的作用

    • 封装数据结构
    • 处理业务逻辑
    • 从server 加载、保存数据
    • 当data发生变化时触发事件,比如重新渲染视图

    Collection的作用

    Collection是Model的有序集合,和Model一样用于数据加载、保存,监听数据变化,还可以使用 Underscore.js 提供的方法来操作Collection。

    主要适用于list、table等视图的渲染。在本例中,就需要定义一个Collection来渲染列表,并监听Collection的变化。

    定义Model和Collection

    // Goal Model
    var Goal = Backbone.Model.extend({
      defaults: {
        title: ''
      }
    });
    
    // Goal Collection
    var GoalCollection = Backbone.Collection.extend({
      model: Goal,
    });
    
    

    使用template

    下面这段代码,有一些地方是相同的,为了避免重复代码,可以使用模板来渲染。

    <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> 
    

    在html中定义模板

    把重复的部分抽出来,定义模板时使用<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代码段
    • <%- %>表示插值并进行转义

    View中定义解析函数template()

    在js中定义GoalView,用于生成每一个Goal对应的<li>节点。

    BackboneJS中的template实际上调用的是underscore.js的template方法,该方法可以将 JavaScript 模板编译为可以用于页面呈现的函数,它返回的是一个函数

    _.template(templateString, [settings])

    render时调用template

    然后在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);
    

    bind Collection to View

    在AppView中,修改addGoal的添加模式,将原来的直接操作DOM,修改为通过data的变化来触发DOM的渲染。

    • 在AppView中实例化一个GoalCollection,命名为goalList
    • Keypress事件触发时,修改goalList,这里调用了Backbone.Collection中的push()方法
    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的变化进行监听。

    Model 和 Collection的事件监听

    View 监听 Model或Collection的变化

    在AppView中,通过listenTo()方法,监听Collection的变化,当Collection发生变化时,触发内部的某个方法。

    object.listenTo(other, event, callback)

    listenTo 用于一个对象,监听另一个对象的变化

    停止监听使用stopListening

    object.stopListening([other], [event], [callback])

    监听add事件
    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 });

    效果相同

    监听destroy事件

    在上一步中,已经给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对应的节点,同时还能停止节点上绑定的事件监听。

    Model或Collection 自我监听变化

    在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。

    使用 bind() 或 bindAll() 修改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);
    },
    
  • 相关阅读:
    Python基础之基本数据类型
    Python基础之变量
    mysql数据库
    进程与线程
    并发编程
    网络编程
    内置函数(魔法方法)
    组合,封装,访问限制机制,property装饰器
    面向对象之继承
    Web开发中最致命的8个小错误
  • 原文地址:https://www.cnblogs.com/liulei-cherry/p/10095352.html
Copyright © 2011-2022 走看看