351 lines
8.3 KiB
JavaScript
351 lines
8.3 KiB
JavaScript
import {
|
|
Anot,
|
|
createFragment,
|
|
platform,
|
|
isObject,
|
|
ap,
|
|
makeHashCode
|
|
} from '../seed/core'
|
|
|
|
import { VFragment } from '../vdom/VFragment'
|
|
import { $$skipArray } from '../vmodel/reserved'
|
|
|
|
import { addScope, makeHandle } from '../parser/index'
|
|
import { updateView } from './duplex/share'
|
|
|
|
var rforAs = /\s+as\s+([$\w]+)/
|
|
var rident = /^[$a-zA-Z_][$a-zA-Z0-9_]*$/
|
|
var rinvalid = /^(null|undefined|NaN|window|this|\$index|\$id)$/
|
|
var rargs = /[$\w_]+/g
|
|
Anot.directive('for', {
|
|
delay: true,
|
|
priority: 3,
|
|
beforeInit: function() {
|
|
var str = this.expr,
|
|
asName
|
|
str = str.replace(rforAs, function(a, b) {
|
|
/* istanbul ignore if */
|
|
if (!rident.test(b) || rinvalid.test(b)) {
|
|
console.error(
|
|
'alias ' +
|
|
b +
|
|
' is invalid --- must be a valid JS identifier which is not a reserved name.'
|
|
)
|
|
} else {
|
|
asName = b
|
|
}
|
|
return ''
|
|
})
|
|
|
|
var arr = str.split(' in ')
|
|
var kv = arr[0].match(rargs)
|
|
if (kv.length === 1) {
|
|
//确保Anot._each的回调有三个参数
|
|
kv.unshift('$key')
|
|
}
|
|
this.expr = arr[1]
|
|
this.keyName = kv[0]
|
|
this.valName = kv[1]
|
|
this.signature = makeHashCode('for')
|
|
if (asName) {
|
|
this.asName = asName
|
|
}
|
|
|
|
delete this.param
|
|
},
|
|
init: function() {
|
|
var cb = this.userCb
|
|
if (typeof cb === 'string' && cb) {
|
|
var arr = addScope(cb, 'for')
|
|
var body = makeHandle(arr[0])
|
|
this.userCb = new Function(
|
|
'$event',
|
|
'var __vmodel__ = this\nreturn ' + body
|
|
)
|
|
}
|
|
this.node.forDir = this //暴露给component/index.js中的resetParentChildren方法使用
|
|
this.fragment = [
|
|
'<div>',
|
|
this.fragment,
|
|
'<!--',
|
|
this.signature,
|
|
'--></div>'
|
|
].join('')
|
|
this.cache = {}
|
|
},
|
|
diff: function(newVal, oldVal) {
|
|
/* istanbul ignore if */
|
|
if (this.updating) {
|
|
return
|
|
}
|
|
this.updating = true
|
|
var traceIds = createFragments(this, newVal)
|
|
|
|
if (this.oldTrackIds === void 0) return true
|
|
|
|
if (this.oldTrackIds !== traceIds) {
|
|
this.oldTrackIds = traceIds
|
|
return true
|
|
}
|
|
},
|
|
update: function() {
|
|
if (!this.preFragments) {
|
|
this.fragments = this.fragments || []
|
|
mountList(this)
|
|
} else {
|
|
diffList(this)
|
|
updateList(this)
|
|
}
|
|
|
|
if (this.userCb) {
|
|
var me = this
|
|
setTimeout(function() {
|
|
me.userCb.call(me.vm, {
|
|
type: 'rendered',
|
|
target: me.begin.dom,
|
|
signature: me.signature
|
|
})
|
|
}, 0)
|
|
}
|
|
delete this.updating
|
|
},
|
|
beforeDispose: function() {
|
|
this.fragments.forEach(function(el) {
|
|
el.dispose()
|
|
})
|
|
}
|
|
})
|
|
|
|
function getTraceKey(item) {
|
|
var type = typeof item
|
|
return item && type === 'object' ? item.$hashcode : type + ':' + item
|
|
}
|
|
|
|
//创建一组fragment的虚拟DOM
|
|
function createFragments(instance, obj) {
|
|
if (isObject(obj)) {
|
|
var array = Array.isArray(obj)
|
|
var ids = []
|
|
var fragments = [],
|
|
i = 0
|
|
|
|
instance.isArray = array
|
|
if (instance.fragments) {
|
|
instance.preFragments = instance.fragments
|
|
Anot.each(obj, function(key, value) {
|
|
var k = array ? getTraceKey(value) : key
|
|
|
|
fragments.push({
|
|
key: k,
|
|
val: value,
|
|
index: i++
|
|
})
|
|
ids.push(k)
|
|
})
|
|
instance.fragments = fragments
|
|
} else {
|
|
Anot.each(obj, function(key, value) {
|
|
if (!(key in $$skipArray)) {
|
|
var k = array ? getTraceKey(value) : key
|
|
fragments.push(new VFragment([], k, value, i++))
|
|
ids.push(k)
|
|
}
|
|
})
|
|
instance.fragments = fragments
|
|
}
|
|
return ids.join(';;')
|
|
} else {
|
|
return NaN
|
|
}
|
|
}
|
|
|
|
function mountList(instance) {
|
|
var args = instance.fragments.map(function(fragment, index) {
|
|
FragmentDecorator(fragment, instance, index)
|
|
saveInCache(instance.cache, fragment)
|
|
return fragment
|
|
})
|
|
var list = instance.parentChildren
|
|
var i = list.indexOf(instance.begin)
|
|
list.splice.apply(list, [i + 1, 0].concat(args))
|
|
}
|
|
|
|
function diffList(instance) {
|
|
var cache = instance.cache
|
|
var newCache = {}
|
|
var fuzzy = []
|
|
var list = instance.preFragments
|
|
|
|
list.forEach(function(el) {
|
|
el._dispose = true
|
|
})
|
|
|
|
instance.fragments.forEach(function(c, index) {
|
|
var fragment = isInCache(cache, c.key)
|
|
//取出之前的文档碎片
|
|
if (fragment) {
|
|
delete fragment._dispose
|
|
fragment.oldIndex = fragment.index
|
|
fragment.index = index // 相当于 c.index
|
|
|
|
resetVM(fragment.vm, instance.keyName)
|
|
fragment.vm[instance.valName] = c.val
|
|
fragment.vm[instance.keyName] = instance.isArray ? index : fragment.key
|
|
saveInCache(newCache, fragment)
|
|
} else {
|
|
//如果找不到就进行模糊搜索
|
|
fuzzy.push(c)
|
|
}
|
|
})
|
|
fuzzy.forEach(function(c) {
|
|
var fragment = fuzzyMatchCache(cache, c.key)
|
|
if (fragment) {
|
|
//重复利用
|
|
fragment.oldIndex = fragment.index
|
|
fragment.key = c.key
|
|
var val = (fragment.val = c.val)
|
|
var index = (fragment.index = c.index)
|
|
|
|
fragment.vm[instance.valName] = val
|
|
fragment.vm[instance.keyName] = instance.isArray ? index : fragment.key
|
|
delete fragment._dispose
|
|
} else {
|
|
c = new VFragment([], c.key, c.val, c.index)
|
|
fragment = FragmentDecorator(c, instance, c.index)
|
|
list.push(fragment)
|
|
}
|
|
saveInCache(newCache, fragment)
|
|
})
|
|
|
|
instance.fragments = list
|
|
list.sort(function(a, b) {
|
|
return a.index - b.index
|
|
})
|
|
instance.cache = newCache
|
|
}
|
|
|
|
function updateItemVm(vm, top) {
|
|
for (var i in top) {
|
|
if (top.hasOwnProperty(i)) {
|
|
vm[i] = top[i]
|
|
}
|
|
}
|
|
}
|
|
|
|
function resetVM(vm, a, b) {
|
|
vm.$accessors[a].value = NaN
|
|
}
|
|
|
|
function updateList(instance) {
|
|
var before = instance.begin.dom
|
|
var parent = before.parentNode
|
|
var list = instance.fragments
|
|
var end = instance.end.dom
|
|
|
|
for (var i = 0, item; (item = list[i]); i++) {
|
|
if (item._dispose) {
|
|
list.splice(i, 1)
|
|
i--
|
|
item.dispose()
|
|
continue
|
|
}
|
|
if (item.oldIndex !== item.index) {
|
|
var f = item.toFragment()
|
|
var isEnd = before.nextSibling === null
|
|
parent.insertBefore(f, before.nextSibling)
|
|
if (isEnd && !parent.contains(end)) {
|
|
parent.insertBefore(end, before.nextSibling)
|
|
}
|
|
}
|
|
before = item.split
|
|
}
|
|
var ch = instance.parentChildren
|
|
var startIndex = ch.indexOf(instance.begin)
|
|
var endIndex = ch.indexOf(instance.end)
|
|
|
|
list.splice.apply(ch, [startIndex + 1, endIndex - startIndex].concat(list))
|
|
if (parent.nodeName === 'SELECT' && parent._ms_duplex_) {
|
|
updateView['select'].call(parent._ms_duplex_)
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {type} fragment
|
|
* @param {type} this
|
|
* @param {type} index
|
|
* @returns { key, val, index, oldIndex, this, dom, split, vm}
|
|
*/
|
|
function FragmentDecorator(fragment, instance, index) {
|
|
var data = {}
|
|
data[instance.keyName] = instance.isArray ? index : fragment.key
|
|
data[instance.valName] = fragment.val
|
|
if (instance.asName) {
|
|
data[instance.asName] = instance.value
|
|
}
|
|
var vm = (fragment.vm = platform.itemFactory(instance.vm, {
|
|
data: data
|
|
}))
|
|
if (instance.isArray) {
|
|
vm.$watch(instance.valName, function(a) {
|
|
if (instance.value && instance.value.set) {
|
|
instance.value.set(vm[instance.keyName], a)
|
|
}
|
|
})
|
|
} else {
|
|
vm.$watch(instance.valName, function(a) {
|
|
instance.value[fragment.key] = a
|
|
})
|
|
}
|
|
|
|
fragment.index = index
|
|
fragment.innerRender = Anot.scan(instance.fragment, vm, function() {
|
|
var oldRoot = this.root
|
|
ap.push.apply(fragment.children, oldRoot.children)
|
|
this.root = fragment
|
|
})
|
|
return fragment
|
|
}
|
|
// 新位置: 旧位置
|
|
function isInCache(cache, id) {
|
|
var c = cache[id]
|
|
if (c) {
|
|
var arr = c.arr
|
|
/* istanbul ignore if*/
|
|
if (arr) {
|
|
var r = arr.pop()
|
|
if (!arr.length) {
|
|
c.arr = 0
|
|
}
|
|
return r
|
|
}
|
|
delete cache[id]
|
|
return c
|
|
}
|
|
}
|
|
//[1,1,1] number1 number1_ number1__
|
|
function saveInCache(cache, component) {
|
|
var trackId = component.key
|
|
if (!cache[trackId]) {
|
|
cache[trackId] = component
|
|
} else {
|
|
var c = cache[trackId]
|
|
var arr = c.arr || (c.arr = [])
|
|
arr.push(component)
|
|
}
|
|
}
|
|
|
|
var rfuzzy = /^(string|number|boolean)/
|
|
var rkfuzzy = /^_*(string|number|boolean)/
|
|
|
|
function fuzzyMatchCache(cache) {
|
|
var key
|
|
for (var id in cache) {
|
|
var key = id
|
|
break
|
|
}
|
|
if (key) {
|
|
return isInCache(cache, key)
|
|
}
|
|
}
|