477 lines
13 KiB
JavaScript
477 lines
13 KiB
JavaScript
Anot.directive('for', {
|
|
priority: 90,
|
|
init: function(binding) {
|
|
var type = binding.type
|
|
binding.cache = {} //用于存放代理VM
|
|
binding.enterCount = 0
|
|
|
|
var elem = binding.element
|
|
if (elem.nodeType === 1) {
|
|
var vars = binding.expr.split(' in ')
|
|
binding.expr = vars.pop()
|
|
if (vars.length) {
|
|
vars = vars.pop().split(/\s+/)
|
|
}
|
|
binding.vars = vars
|
|
elem.removeAttribute(binding.name)
|
|
effectBinding(elem, binding)
|
|
var rendered = getBindingCallback(elem, 'data-rendered', binding.vmodels)
|
|
|
|
var signature = generateID(type)
|
|
var start = DOC.createComment(signature + ':start')
|
|
var end = (binding.element = DOC.createComment(signature + ':end'))
|
|
binding.signature = signature
|
|
binding.start = start
|
|
binding.template = anotFragment.cloneNode(false)
|
|
|
|
var _parent = elem.parentNode
|
|
_parent.replaceChild(end, elem)
|
|
_parent.insertBefore(start, end)
|
|
binding.template.appendChild(elem)
|
|
|
|
binding.element = end
|
|
|
|
if (rendered) {
|
|
var removeFn = Anot.bind(_parent, 'datasetchanged', function() {
|
|
rendered.apply(_parent, _parent.args)
|
|
Anot.unbind(_parent, 'datasetchanged', removeFn)
|
|
_parent.msRendered = rendered
|
|
})
|
|
}
|
|
}
|
|
},
|
|
update: function(value, oldValue) {
|
|
var binding = this
|
|
var xtype = this.xtype
|
|
|
|
if (xtype === 'array') {
|
|
if (!this.vars.length) {
|
|
this.vars.push('$index', 'el')
|
|
} else if (this.vars.length === 1) {
|
|
this.vars.unshift('$index')
|
|
}
|
|
this.param = this.vars[1]
|
|
} else {
|
|
this.param = '__el__'
|
|
if (!this.vars.length) {
|
|
this.vars.push('$key', '$val')
|
|
} else if (this.vars.length === 1) {
|
|
this.vars.push('$val')
|
|
}
|
|
}
|
|
|
|
this.enterCount += 1
|
|
var init = !oldValue
|
|
if (init) {
|
|
binding.$outer = {}
|
|
var check0 = this.vars[0]
|
|
var check1 = this.vars[1]
|
|
if (xtype === 'array') {
|
|
check0 = '$first'
|
|
check1 = '$last'
|
|
}
|
|
for (var i = 0, v; (v = binding.vmodels[i++]); ) {
|
|
if (v.hasOwnProperty(check0) && v.hasOwnProperty(check1)) {
|
|
binding.$outer = v
|
|
break
|
|
}
|
|
}
|
|
}
|
|
var track = this.track
|
|
var action = 'move'
|
|
binding.$repeat = value
|
|
var fragments = []
|
|
var transation = init && anotFragment.cloneNode(false)
|
|
var proxies = []
|
|
var param = this.param
|
|
var retain = Anot.mix({}, this.cache)
|
|
var elem = this.element
|
|
var length = track.length
|
|
|
|
var _parent = elem.parentNode
|
|
|
|
//检查新元素数量
|
|
var newCount = 0
|
|
for (i = 0; i < length; i++) {
|
|
var keyOrId = track[i]
|
|
if (!retain[keyOrId]) newCount++
|
|
}
|
|
var oldCount = 0
|
|
for (i in retain) {
|
|
oldCount++
|
|
}
|
|
var clear = (!length || newCount === length) && oldCount > 10 //当全部是新元素,且移除元素较多(10)时使用clear
|
|
|
|
var kill = elem.previousSibling
|
|
var start = binding.start
|
|
|
|
if (clear) {
|
|
while (kill !== start) {
|
|
_parent.removeChild(kill)
|
|
kill = elem.previousSibling
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < length; i++) {
|
|
keyOrId = track[i] //array为随机数, object 为keyName
|
|
var proxy = retain[keyOrId]
|
|
if (!proxy) {
|
|
// log(this)
|
|
proxy = getProxyVM(this)
|
|
proxy.$up = this.vmodels[0]
|
|
if (xtype === 'array') {
|
|
action = 'add'
|
|
proxy.$id = keyOrId
|
|
var valueItem = value[i]
|
|
proxy[param] = valueItem //index
|
|
if (Object(valueItem) === valueItem) {
|
|
hideProperty(valueItem, '$ups', valueItem.$ups || {})
|
|
valueItem.$ups[param] = proxy
|
|
}
|
|
} else {
|
|
action = 'append'
|
|
proxy[check0] = keyOrId
|
|
proxy[check1] = value[keyOrId] //key
|
|
var tmp = {}
|
|
tmp[check0] = proxy[check0]
|
|
tmp[check1] = proxy[check1]
|
|
proxy[param] = tmp
|
|
}
|
|
this.cache[keyOrId] = proxy
|
|
var node = proxy.$anchor || (proxy.$anchor = elem.cloneNode(false))
|
|
node.nodeValue = this.signature
|
|
shimController(
|
|
binding,
|
|
transation,
|
|
proxy,
|
|
fragments,
|
|
init && !binding.effectDriver
|
|
)
|
|
decorateProxy(proxy, binding, xtype)
|
|
} else {
|
|
fragments.push({})
|
|
retain[keyOrId] = true
|
|
}
|
|
|
|
//重写proxy
|
|
if (this.enterCount === 1) {
|
|
//防止多次进入,导致位置不对
|
|
proxy.$active = false
|
|
proxy.$oldIndex = proxy.$index
|
|
proxy.$active = true
|
|
proxy.$index = i
|
|
}
|
|
|
|
if (xtype === 'array') {
|
|
proxy.$first = i === 0
|
|
proxy.$last = i === length - 1
|
|
proxy[this.vars[0]] = proxy.$index
|
|
} else {
|
|
proxy[check1] = toJson(value[keyOrId]) //这里是处理vm.object = newObject的情况
|
|
}
|
|
proxies.push(proxy)
|
|
}
|
|
this.proxies = proxies
|
|
if (init && !binding.effectDriver) {
|
|
_parent.insertBefore(transation, elem)
|
|
fragments.forEach(function(fragment) {
|
|
scanNodeArray(fragment.nodes || [], fragment.vmodels)
|
|
//if(fragment.vmodels.length > 2)
|
|
fragment.nodes = fragment.vmodels = null
|
|
}) // jshint ignore:line
|
|
} else {
|
|
var staggerIndex = (binding.staggerIndex = 0)
|
|
for (keyOrId in retain) {
|
|
if (retain[keyOrId] !== true) {
|
|
action = 'del'
|
|
!clear && removeItem(retain[keyOrId].$anchor, binding, true)
|
|
// 相当于delete binding.cache[key]
|
|
proxyRecycler(this.cache, keyOrId, param)
|
|
retain[keyOrId] = null
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < length; i++) {
|
|
proxy = proxies[i]
|
|
keyOrId = xtype === 'array' ? proxy.$id : proxy.$key
|
|
var pre = proxies[i - 1]
|
|
var preEl = pre ? pre.$anchor : binding.start
|
|
if (!retain[keyOrId]) {
|
|
//如果还没有插入到DOM树,进行插入动画
|
|
;(function(fragment, preElement) {
|
|
var nodes = fragment.nodes
|
|
var vmodels = fragment.vmodels
|
|
if (nodes) {
|
|
staggerIndex = mayStaggerAnimate(
|
|
binding.effectEnterStagger,
|
|
function() {
|
|
_parent.insertBefore(fragment.content, preElement.nextSibling)
|
|
scanNodeArray(nodes, vmodels)
|
|
!init && animateRepeat(nodes, 1, binding)
|
|
},
|
|
staggerIndex
|
|
)
|
|
}
|
|
fragment.nodes = fragment.vmodels = null
|
|
})(fragments[i], preEl) // jshint ignore:line
|
|
} else if (proxy.$index !== proxy.$oldIndex) {
|
|
//进行移动动画
|
|
;(function(proxy2, preElement) {
|
|
staggerIndex = mayStaggerAnimate(
|
|
binding.effectEnterStagger,
|
|
function() {
|
|
var curNode = removeItem(proxy2.$anchor)
|
|
var inserted = Anot.slice(curNode.childNodes)
|
|
_parent.insertBefore(curNode, preElement.nextSibling)
|
|
animateRepeat(inserted, 1, binding)
|
|
},
|
|
staggerIndex
|
|
)
|
|
})(proxy, preEl) // jshint ignore:line
|
|
}
|
|
}
|
|
}
|
|
if (!value.$track) {
|
|
//如果是非监控对象,那么就将其$events清空,阻止其持续监听
|
|
for (keyOrId in this.cache) {
|
|
proxyRecycler(this.cache, keyOrId, param)
|
|
}
|
|
}
|
|
|
|
// :for --> duplex
|
|
;(function(args) {
|
|
_parent.args = args
|
|
if (_parent.msRendered) {
|
|
//第一次事件触发,以后直接调用
|
|
_parent.msRendered.apply(_parent, args)
|
|
}
|
|
})(kernel.newWatch ? arguments : [action])
|
|
var id = setTimeout(function() {
|
|
clearTimeout(id)
|
|
//触发上层的select回调及自己的rendered回调
|
|
Anot.fireDom(_parent, 'datasetchanged', {
|
|
bubble: _parent.msHasEvent
|
|
})
|
|
})
|
|
this.enterCount -= 1
|
|
}
|
|
})
|
|
|
|
function animateRepeat(nodes, isEnter, binding) {
|
|
for (var i = 0, node; (node = nodes[i++]); ) {
|
|
if (node.className === binding.effectClass) {
|
|
Anot.effect.apply(node, isEnter, noop, noop, binding)
|
|
}
|
|
}
|
|
}
|
|
|
|
function mayStaggerAnimate(staggerTime, callback, index) {
|
|
if (staggerTime) {
|
|
setTimeout(callback, ++index * staggerTime)
|
|
} else {
|
|
callback()
|
|
}
|
|
return index
|
|
}
|
|
|
|
function removeItem(node, binding, flagRemove) {
|
|
var fragment = anotFragment.cloneNode(false)
|
|
var last = node
|
|
var breakText = last.nodeValue
|
|
var staggerIndex = binding && Math.max(+binding.staggerIndex, 0)
|
|
var nodes = Anot.slice(last.parentNode.childNodes)
|
|
var index = nodes.indexOf(last)
|
|
while (true) {
|
|
var pre = nodes[--index] //node.previousSibling
|
|
if (!pre || String(pre.nodeValue).indexOf(breakText) === 0) {
|
|
break
|
|
}
|
|
if (!flagRemove && binding && pre.className === binding.effectClass) {
|
|
node = pre
|
|
;(function(cur) {
|
|
binding.staggerIndex = mayStaggerAnimate(
|
|
binding.effectLeaveStagger,
|
|
function() {
|
|
Anot.effect.apply(
|
|
cur,
|
|
0,
|
|
noop,
|
|
function() {
|
|
fragment.appendChild(cur)
|
|
},
|
|
binding
|
|
)
|
|
},
|
|
staggerIndex
|
|
)
|
|
})(pre) // jshint ignore:line
|
|
} else {
|
|
fragment.insertBefore(pre, fragment.firstChild)
|
|
}
|
|
}
|
|
fragment.appendChild(last)
|
|
return fragment
|
|
}
|
|
|
|
function shimController(data, transation, proxy, fragments, init) {
|
|
var content = data.template.cloneNode(true)
|
|
var nodes = Anot.slice(content.childNodes)
|
|
content.appendChild(proxy.$anchor)
|
|
init && transation.appendChild(content)
|
|
var itemName = data.param || 'el'
|
|
var valueItem = proxy[itemName],
|
|
nv
|
|
|
|
nv = [proxy].concat(data.vmodels)
|
|
|
|
var fragment = {
|
|
nodes: nodes,
|
|
vmodels: nv,
|
|
content: content
|
|
}
|
|
fragments.push(fragment)
|
|
}
|
|
// {} --> {xx: 0, yy: 1, zz: 2} add
|
|
// {xx: 0, yy: 1, zz: 2} --> {xx: 0, yy: 1, zz: 2, uu: 3}
|
|
// [xx: 0, yy: 1, zz: 2} --> {xx: 0, zz: 1, yy: 2}
|
|
|
|
function getProxyVM(binding) {
|
|
var agent = binding.xtype === 'object' ? withProxyAgent : eachProxyAgent
|
|
var proxy = agent(binding)
|
|
var node = proxy.$anchor || (proxy.$anchor = binding.element.cloneNode(false))
|
|
node.nodeValue = binding.signature
|
|
proxy.$outer = binding.$outer
|
|
return proxy
|
|
}
|
|
|
|
function decorateProxy(proxy, binding, type) {
|
|
if (type === 'array') {
|
|
proxy.$remove = function() {
|
|
binding.$repeat.removeAt(proxy.$index)
|
|
}
|
|
var param = binding.param
|
|
proxy.$watch(param, function(val) {
|
|
var index = proxy.$index
|
|
binding.$repeat[index] = val
|
|
})
|
|
} else {
|
|
var __k__ = binding.vars[0]
|
|
var __v__ = binding.vars[1]
|
|
proxy.$up.$watch(binding.expr + '.' + proxy[__k__], function(val) {
|
|
proxy[binding.param][__v__] = val
|
|
proxy[__v__] = val
|
|
})
|
|
}
|
|
}
|
|
|
|
var eachProxyPool = []
|
|
|
|
function eachProxyAgent(data, proxy) {
|
|
var itemName = data.param || 'el'
|
|
for (var i = 0, n = eachProxyPool.length; i < n; i++) {
|
|
var candidate = eachProxyPool[i]
|
|
if (candidate && candidate.hasOwnProperty(itemName)) {
|
|
eachProxyPool.splice(i, 1)
|
|
proxy = candidate
|
|
break
|
|
}
|
|
}
|
|
if (!proxy) {
|
|
proxy = eachProxyFactory(data)
|
|
}
|
|
return proxy
|
|
}
|
|
|
|
function eachProxyFactory(data) {
|
|
var itemName = data.param || 'el'
|
|
var __k__ = data.vars[0]
|
|
var source = {
|
|
$outer: {},
|
|
$index: 0,
|
|
$oldIndex: 0,
|
|
$anchor: null,
|
|
//-----
|
|
$first: false,
|
|
$last: false,
|
|
$remove: Anot.noop
|
|
}
|
|
source[__k__] = 0
|
|
source[itemName] = NaN
|
|
var force = {
|
|
$last: 1,
|
|
$first: 1,
|
|
$index: 1
|
|
}
|
|
force[__k__] = 1
|
|
force[itemName] = 1
|
|
var proxy = modelFactory(
|
|
{ state: source },
|
|
{
|
|
force: force
|
|
}
|
|
)
|
|
proxy.$id = generateID('proxy-each')
|
|
return proxy
|
|
}
|
|
|
|
var withProxyPool = []
|
|
|
|
function withProxyAgent(data) {
|
|
return withProxyPool.pop() || withProxyFactory(data)
|
|
}
|
|
|
|
function withProxyFactory(data) {
|
|
var itemName = data.param || '__el__'
|
|
var __k__ = data.vars[0]
|
|
var __v__ = data.vars[1]
|
|
var source = {
|
|
$index: 0,
|
|
$oldIndex: 0,
|
|
$outer: {},
|
|
$anchor: null
|
|
}
|
|
source[__k__] = ''
|
|
source[__v__] = NaN
|
|
source[itemName] = NaN
|
|
var force = {
|
|
__el__: 1,
|
|
$index: 1
|
|
}
|
|
force[__k__] = 1
|
|
force[__v__] = 1
|
|
var proxy = modelFactory(
|
|
{ state: source },
|
|
{
|
|
force: force
|
|
}
|
|
)
|
|
proxy.$id = generateID('proxy-with')
|
|
return proxy
|
|
}
|
|
|
|
function proxyRecycler(cache, key, param) {
|
|
var proxy = cache[key]
|
|
if (proxy) {
|
|
var proxyPool =
|
|
proxy.$id.indexOf('proxy-each') === 0 ? eachProxyPool : withProxyPool
|
|
proxy.$outer = {}
|
|
|
|
for (var i in proxy.$events) {
|
|
var a = proxy.$events[i]
|
|
if (Array.isArray(a)) {
|
|
a.length = 0
|
|
if (i === param) {
|
|
proxy[param] = NaN
|
|
} else if (i === '$val') {
|
|
proxy.$val = NaN
|
|
}
|
|
}
|
|
}
|
|
|
|
if (proxyPool.unshift(proxy) > kernel.maxRepeatSize) {
|
|
proxyPool.pop()
|
|
}
|
|
delete cache[key]
|
|
}
|
|
}
|