代码与效果
通过标签上”sd-“开头的指令绑定数据与操作。sd-each是众多指令中的一个,可以实现标签的循环输出。例如 sd-each = "todo:todos"
就是要将 todos 数组中的每一个对象都输出。
概述
有下面一段HTML片段,要将对应的对象的值正确的绑定到对应的标签当中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <div id="app" sd-controller="TodoList" sd-on="click:changeMessage | delegate .button"> <p sd-text="msg | capitalize"></p> <p sd-text="msg | uppercase"></p> <p sd-on="click:remove">bye</p> <p sd-text="total | money"></p> <p class="button">Change Message</p> <p sd-class="red:error" sd-show="error">Error</p> <ul sd-show="todos"> <li class="todo" sd-controller="Todo" sd-each="todo:todos" sd-class="done:todo.done" sd-on="click:changeMessage, click:todo.toggle" sd-text="todo.title" ></li> </ul> </div>
|
数据绑定的原理,我们上一节已经分析过了。总体来说分为以下几个步骤,首先是提取标签中的指令,分析指令,将指令中的参数提取出来,为这些指令和标签做绑定操作,通过绑定操作可以实现对象的属性与标签的值进行绑定,或者将事件处理函数与标签的事件进行绑定。
回到上面的片段,ul中的li有一个sd-each指令,是将todos数组的内容与标签相绑定。我们如何去实现呢?
功能设计
sd-each 节点也和普通的节点一样,可以绑定诸如 sd-text,sd-class等指令。它的不同之处在于它的数目等于绑定的数组的长度。
现在,整个框架的生命周期分为两个阶段。一个是绑定阶段,一个是更新数据阶段。框架首先将指令收集起来,然后再在数据赋值的时候更新DOM节点。
针对sd-each,首先在绑定阶段在li这个位置做一个标记,之后在更新阶段在标记位置插入绑定了数据的节点。
除去sd-each指令,这个节点与普通节点一样,我们循环将其他指令进行正确的绑定,输出正确的DOM节点,插入指定位置。
Seed对象在传入节点和数据之后可以生成绑定了数据的节点,因此,我们可以将sd-each的节点作为Seed对象的输入,生成一个绑定了对象的节点输出。
代码设计
通过文字解答有些复杂,直接看each指令的代码吧:
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
| each: { bind: function () { this.el.removeAttribute(config.prefix + '-each') this.prefixRE = new RegExp('^' + this.arg + '.') var ctn = this.container = this.el.parentNode this.marker = document.createComment('sd-each-' + this.arg + '-marker') ctn.insertBefore(this.marker, this.el) ctn.removeChild(this.el) this.childSeeds = [] }, update: function (collection) { if (this.childSeeds.length) { this.childSeeds.forEach(function (child) { child.destroy() }) this.childSeeds = [] } watchArray(collection, this.mutate.bind(this)) var self = this collection.forEach(function (item, i) { self.childSeeds.push(self.buildItem(item, i, collection)) }) console.log('collection creation done.') }, mutate: function (mutation) { console.log(mutation) }, buildItem: function (data, index, collection) { var Seed = require('seed/src/seed.js'), node = this.el.cloneNode(true) var spore = new Seed(node, data, { eachPrefixRE: this.prefixRE, parentSeed: this.seed }) this.container.insertBefore(spore.el, this.marker) collection[index] = spore.scope return spore } }
|
bind函数,是在绑定阶段运行的钩子函数。创建一个注释节点作为标记,在数据更新之后将节点插入标记节点之前。
还有几个细节,在bind函数中提取了sd-each指令的参数todo,todo是其他指定绑定值的一个前缀。创建了一个childSeeds节点,存放绑定后的Seed对象,在下一次绑定的时候要先将这些节点清除。
update函数,是在更新阶段调用的函数。collection参数是传入的数据数组,对collection中的每一个元素,调用buildItem函数去生成绑定数据的节点。
buildItem函数,将数据以及sd-each节点进行绑定,生成绑定后的对象spore,对象的el属性是要输出的DOM节点,插入标记节点之前,并且将spore对象存入childSeeds数组。
嵌套作用域
sd-each指令在上一节进行了详细的分析,主要思想就是将sd-each节点根据数据数组进行数据绑定。
如果节点绑定的sd-text不是todo.title而是父级作用域中的msg,需要怎么处理?
在给msg赋值的时候,sd-each的节点要随之数据进行更新。但是如果我们的sd-each的赋值操作在msg的赋值操作之后,那么我们就无法正确的解析sd-each节点,并且绑定上数据。
所以,sd-each指令的数据更新工作要比其他的指令要早,在其他指令的绑定阶段,我们的sd-each节点就要进行数据的更新。因为sd-each节点的数据更新阶段才会解析指令。
总结
sd-each指令的实现,要通过调用Seed构造函数生成节点,并且在更新阶段才调用这一构造函数,因此为了实现作用域的嵌套,我们要在其他数据的绑定阶段就将这一指令的数据进行更新。