canjs的数据绑定
发布在canjs乱炖2014年11月12日view:3245
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

本文所写的绑定是基于模板Mustache的。同时也算是一个关于canjs的数据绑定的基础知识,后续会给出更加详细的分析。

1、canjs中的数据绑定

在上一篇关于canjs的文章中提到了组件开发,其中对于tag: “grid”的组件中的定义方法有各种令人生僻的用法:

can.Component.extend({
  tag: "grid",
  scope: {
    items: [],
    waiting: true
  },
  template: "<table><tbody><content></content></tbody></table>",
  events: {
    init: function () {
      this.A();
    },
    "{scope} deferreddata": "A",
    A: function () {
      var deferred = this.scope.attr('deferreddata'),
        scope = this.scope;
      if (can.isDeferred(deferred)) {
        this.scope.attr("waiting", true);
        this.element.find('tbody').css('opacity', 0.5);
        deferred.then(function (items) {
          scope.attr('items').replace(items);
        });
      } else {
        scope.attr('items').attr(deferred, true);
      }
    },
    "{items} change": function () {
      this.scope.attr("waiting", false);
      this.element.find('tbody').css('opacity', 1);
    }
  }
});

那现在我们翻开canjs的bindings.js的源码,可以看到如下针对输入框做的双向绑定代码:

// ### Value 
// A can.Control that manages the two-way bindings on most inputs.  When can-value is found as an attribute 
// on an input, the callback above instantiates this Value control on the input element.
var Value = can.Control.extend({
    init: function () {
        // Handle selects by calling `set` after this thread so the rest of the element can finish rendering.
        if (this.element[0].nodeName.toUpperCase() === "SELECT") {
            setTimeout(can.proxy(this.set, this), 1);
        } else {
            this.set();
        }

    },
    // If the live bound data changes, call set to reflect the change in the dom.
    "{value} change": "set",
    set: function () {
        // This may happen in some edgecases, esp. with selects that are not in DOM after the timeout has fired
        if (!this.element) {
            return;
        }
        var val = this.options.value();
        // Set the element's value to match the attribute that was passed in
        this.element[0].value = (val == null ? '' : val);
    },
    // If the input value changes, this will set the live bound data to reflect the change.
    "change": function () {
        // This may happen in some edgecases, esp. with selects that are not in DOM after the timeout has fired
        if (!this.element) {
            return;
        }
        // Set the value of the attribute passed in to reflect what the user typed
        this.options.value(this.element[0].value);
    }
}),

从注释中得知:{value} change触发的是数据绑定,如果数据发生变化,会反应到页面上。change触发的是页面反向绑定,如果页面元素发生变化,会反应到数据上。对于Control来说,里面内置一个init方法用于初始化。

另外该文件中还有针对radiobox/checkbox、select等的数据绑定,原理上和这也是一样的。

2、什么是数据绑定

数据绑定就是提供这样一种能力:它能够通过HTML标签的形式去展现底层数据的当前状态,数据的状态发生变化后能直观的反应到HTML上去。它一般有如下特性:

  1. 计算属性
  2. 模板
  3. DOM事件或者自定义事件的一个处理框架
  4. 能够观察数据的变化
  5. 单向绑定和双向绑定

3、数据绑定常见解决方案

  1. 模板解析。在模板解析的同时,对需要绑定值生成一些方法hooks。
  2. 模板引擎调用相关逻辑,生成可观察对象。
  3. 触发事件时候去调用方法hooks,从而引起数据或者界面发生变化。
  4. 允许我们自定义一些helper方法来辅助模板生成界面。

4、从一个例子开始

下面我们看一个双向绑定的例子:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>two way bind demo</title>
</head>
<body>
<div id='out'></div>
<script src="../../../lib/steal/steal.js"></script>
<script>
steal("can/view/bindings",function(){

var HyperLoop = can.Map.extend({
  travelTime: function(){debugger;
    return this.attr('dist') / 962 /* km/h */
  }
})

loop = new HyperLoop({
    dist: 3429.19 /* Chi to SF */
})

var template = can.view.mustache(
  "<p>Distance:\
      <input can-value='dist'/>\
  </p>\
  <p>Time:\
     {{travelTime}} hrs\
  </p>")

$("#out").html( template(loop) );
})
</script>
</body>

该例子是在已知速度的情况下,根据行驶的距离算出行驶的时间。输入是距离,输出是时间。在输入距离后,首先触发{value} change事件使得dist数据发生变化,在dist数据发生变化后,触发change,使得界面上(模板中)的{{travelTime}}发生变化。

下面我们对数据绑定做更加详细的分析。

4.1、can.map对象

上文提到,can.map是一个可观察对象,对于里面的属性取值,如果变化了,会触发如下事件:

function (ev, attr, how, newVal, oldVal) {    
                // when a change happens, create the named event.
                can.batch.trigger(this, {
                    type: attr,
                    batchNum: ev.batchNum,
                    target: ev.target
                }, [newVal, oldVal]);

这个事件是在调用loop = new HyperLoop({dist: 3429.19 /* Chi to SF */})做初始化can.map对象的时候就绑定上去了。对于loop,它__bindEvents属性中包含了两个对象,分别为change和dist,它们各自下面挂载这绑定的事件名称和关联的事件。

__bindEvents    {...}   Object
        change  [[object Object]]   Object, (Array)
            [0] {...}   Object
                handler function () {return fn.apply(context, arguments);}  Object, (Function)
                name    "change"    String

        dist    [[object Object]]   Object, (Array)
            [0] {...}   Object          
                handler function(ev){if (compute.bound && (ev.batchNum === undefined || ev.batchNum !== batchNum) ) {// Keep the old value
                                    var oldValue = readInfo.value;                                      
                                    // Get the new value
                                    readInfo = getValueAndBind(func, co Object, (Function)
                name    "dist"  String

4.2、{{travelTime}}绑定

这里面另一个可被观察的对象就是模板中的travelTime,这里就是传说中的动态绑定。

在解析模板过程中,{{travelTime}}会被解析为___v1ew.push(can.view.txt(1,'p',0,this,can.Mustache.txt({scope:scope,options:options},null,{get:"travelTime"})));,解析完成后的执行阶段,canjs会为travelTime计算出它是否对其他属性有依赖,如果有则会绑定change事件,用以当其他地方发生变化时候,同步使其也产生变化,这里它依赖于dist。所以当dist发生变化时候,travelTime可以实现动态变化。

4.3、can-value=’dist’绑定

再看下另一个特殊的东东:can-value=’dist’。

在解析模板过程中,can-value=’dist’会被解析为___v1ew.push("<p>Distance: <input can-value='dist'",can.view.pending({attrs: ['can-value'], scope: scope,options: options}),"/>"),解析完成后的执行阶段,canjs会为can-value标识的做特殊回调处理。它位于bindings.js中的can.view.attr("can-value", function (el, data) {})方法。该方法中的最后一行代码new Value(el, {value: value});就是为了实现双向数据绑定,因为Value这个类就是基于control的监控类,里面提供的就是上面我们说的var Value = can.Control.extend({...});,其中有"{value} change": "set","change": function (){}

当然control的用法不仅仅如此,还可以作为路由的匹配导航,后面介绍路由时候会有涉猎。

5、总结

从这里我们发现可以使用两种方式实现输出,一个是{{travelTime}},另一个就是can-value=’dist’:

{{}}用于文本输出,因为它只是计算了它所要依赖的对象,也是被动的去触发变动,这个也可以理解成为单向绑定。

can-value而后则可以用于输入框等,因为它依赖了control对象,使用control对属性的变化作出调整,这个是真正的双向绑定。

而其中监控其变化的则是can.Map对象。

参考:Live binding with CanJS:http://www.slideshare.net/StanCarrico/milwaukee-js-live-binding-with-canjs?qid=339a2935-c7a6-4a28-a7a3-64f4d28470c6&v=qf1&b=&from_search=2

评论
发表评论
暂无评论
WRITTEN BY
jnotnull
专注于前端开发
TA的新浪微博
PUBLISHED IN
canjs乱炖

关于canjs的基本功能和原理研究

我的收藏