avalon2
Version:
an elegant efficient express mvvm framework
1,468 lines (1,365 loc) • 44.6 kB
JavaScript
/*!
*
* 这是一个迷你版avalon,支持IE9+
*/
(function () {
var avalon = function () {
}
window.avalon = avalon
var vmodels = avalon.vmodels = {}
avalon.define = function (obj) {
return vmodels[obj.$id] = Observer(obj)
}
avalon.mix = function (a, b) {
for (var i in b) {
a[i] = b[i]
}
return a
}
avalon.noop = avalon
var rhashcode = /\d\.\d{4}/
avalon.makeHashCode = function (prefix) {
/* istanbul ignore next*/
prefix = prefix || 'avalon'
/* istanbul ignore next*/
return String(Math.random() + Math.random()).replace(rhashcode, prefix)
}
var hasConsole = typeof console === 'object'
avalon.config = function fn(obj) {
avalon.mix(fn, obj)
}
avalon.config({
debug: 1
})
avalon.log = function () {
if (hasConsole && avalon.config.debug) {
// http://stackoverflow.com/questions/8785624/how-to-safely-wrap-console-log
Function.apply.call(console.log, console, arguments)
}
}
var rword = /[^, ]+/g
avalon.oneObject = function (array, val) {
if (typeof array === 'string') {
array = array.match(rword) || []
}
var result = {},
value = val !== void 0 ? val : 1
for (var i = 0, n = array.length; i < n; i++) {
result[array[i]] = value
}
return result
}
avalon.quote = JSON.stringify
function startWith(long, short){
return long.indexOf(short) === 0
}
function isArray(a) {
return Array.isArray(a)
}
function isObservable(key, value) {
return (typeof value !== 'function') && key.charAt(0) !== '$'
}
function isObject(a) {
return a && typeof a === 'object'
}
function createFragment() {
return document.createDocumentFragment()
}
function createAnchor(nodeValue) {
return document.createComment(nodeValue)
}
function copy(target) {
var ret
if (isArray(target)) {
ret = target.slice(0)
} else if (isObject(target)) {
ret = avalon.mix({}, target)
}
return ret || target
}
function replaceNode(newNode, oldNode) {
oldNode.parentNode.replaceChild(newNode, oldNode)
return newNode
}
avalon.each = function (a, fn) {
if (isArray(a)) {
a.forEach(function (el, index) {
fn(index, el)
})
} else {
for (var i in a) {
fn(i, a[i])
}
}
}
var delayCompile = {}
avalon.directives = {}
avalon.directive = function (name, opts) {
avalon.directives[name] = opts
if (opts.delay) {
delayCompile[name] = 1
}
}
//============ Observer 模块 ==========
function createObserver(target, ret) {
if (isObject(target)) {
return target.$events ? target : new Observer(target, ret)
}
if (ret) {
return target
}
}
function Observer(data, vm) {
if (isArray(data)) {
vm = observeArray(data)
} else {
vm = observeObject(data)
}
if(vm)
vm.$events.__dep__ = new Depend()
return vm
}
function observeObject(object) {
var core = {} //events
var state = {}
var props = {}
for (var key in object) {
var val = object[key]
if (isObservable(key, val)) {
state[key] = createAccessor(key, val, core)
} else {
props[key] = val
}
}
addMoreProps(props, object, state, core)
var observe = {}
observe = createViewModel(observe, state, props)
for (var i in props) {
observe[i] = props[i]
}
core.observe = observe
return observe
}
function observeItemObject(before, after) {
var core = before.$events
var state = before.$accessor
var object = after.data
delete after.data
var props = after
for (var key in object) {
state[key] = createAccessor(key, object[key], core)
}
addMoreProps(props, object, state, core)
var observe = {}
observe = createViewModel(observe, state, props)
for (var i in props) {
observe[i] = props[i]
}
core.observe = observe
if (!core.__dep__) {
core.__dep__ = new Depend()
}
return observe
}
var modelAccessor = {
get: function () {
return toJson(this)
},
set: avalon.noop,
enumerable: false,
configurable: true
}
function toJson(val) {
if (isArray(val)) {
var array = []
for (var i = 0; i < val.length; i++) {
array[i] = toJson(val[i])
}
return array
} else if (isObject(val)) {
var obj = {}
for (i in val) {
if (val.hasOwnProperty(i)) {
var value = val[i]
obj[i] = value && value.nodeType ? value : toJson(value)
}
}
return obj
} else {
return val
}
}
function createViewModel(a, b, c) {
return Object.defineProperties(a, b)
}
function observeArray(array, rewrite, ret) {
function createAccessor(key, val, core) {
var value = val
var childOb = createObserver(val)
return {
get: function Getter() {
var ret = value
if (Depend.watcher) {
core.__dep__.collect()
if (childOb && childOb.$events) {
childOb.$events.__dep__.collect()
}
}
return ret
},
set: function Setter(newValue) {
var oldValue = value
if (newValue === oldValue) {
return
}
core.__dep__.beforeNotify()
value = newValue
childOb = createObserver(newValue)
core.__dep__.notify()
},
enumerable: true,
configurable: true
}
}
function addMoreProps(props, object, state, core) {
var hash = avalon.makeHashCode('$')
state.$model = modelAccessor
avalon.mix(props, {
$id: object.$id || hash,
$events: core,
$hashcode: hash,
$accessor: state
})
}
//============ 监控数组 ==========
var ap = Array.prototype
var __array__ = {}
var __method__ = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
function rewriteArrayMethods(array) {
/* istanbul ignore else */
for (var i in __array__) {
array[i] = __array__[i]
}
array.$events = {} // 以后自动加上
}
__method__.forEach(function (method) {
var original = ap[method]
__array__[method] = function () {
// 继续尝试劫持数组元素的属性
var args = []
var size = this.length
var core = this.$events
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i])
}
core.__dep__.beforeNotify()
var result = original.apply(this, args)
var inserts = []
switch (method) {
case 'push':
case 'unshift':
inserts = args
break
case 'splice':
inserts = args.slice(2)
break
}
if (inserts && inserts) {
inserts = observeArray(inserts, 1, 1)
}
core.__dep__.notify({
method: method,
args: args
})
return result
}
})
//============== Depend ============
var guid = 0
/**
* 依赖收集类 用于联结 VM 与 Watcher
*/
function Depend() {
this.watchers = []
this.guid = guid++
}
/**
* 当前收集依赖的订阅模块 watcher
* @type {Object}
*/
Depend.watcher = null
var dp = Depend.prototype
/**
* 添加依赖订阅
* @param {Object} watcher
*/
dp.addWatcher = function (watcher) {
this.watchers.push(watcher)
}
/**
* 移除依赖订阅
* @param {Object} watcher
*/
dp.removeWatcher = function (watcher) {
var index = this.watchers.indexOf(watcher)
if (index > -1) {
this.watchers.splice(index, 1)
}
}
/**
* 为 watcher 收集当前的依赖
*/
dp.collect = function () {
if (Depend.watcher) {
Depend.watcher.addDepend(this)
}
}
/**
* 依赖变更前调用方法,用于旧数据的缓存处理
*/
dp.beforeNotify = function () {
this.watchers.forEach(function (watcher) {
watcher.beforeUpdate()
})
}
/**
* 依赖变更,通知每一个订阅了该依赖的 watcher
* @param {Object} args [数组操作参数信息]
*/
dp.notify = function (args) {
var guid = this.guid
this.watchers.forEach(function (watcher) {
watcher.update(args, guid)
})
}
//============ Watcher模块 ============
/**
* 遍历对象/数组每一个可枚举属性
* @param {Object|Array} target [遍历值/对象或数组]
* @param {Boolean} root [是否是根对象/数组]
*/
var walkedObs = []
function walkThrough(target, root) {
var events = target && target.$events
var guid = events && events.__dep__.guid
if (guid) {
if (walkedObs.indexOf(guid) > -1) {
return
} else {
walkedObs.push(guid)
}
}
avalon.each(target, function (key, value) {
walkThrough(value, false)
})
if (root) {
walkedObs.length = 0
}
}
/**
* 用户watch回调及页面上的指令都会转换它的实例
* @param {type} vm
* @param {type} desc
* @param {type} callback
* @param {type} context
* @returns {Watcher}
*/
function Watcher(vm, desc, callback, context) {
this.vm = vm
avalon.mix(this, desc)
this.callback = callback
this.context = context || this
// 依赖 id 缓存
this.depIds = []
this.newDepIds = []
this.shallowIds = []
// 依赖实例缓存
this.depends = []
this.newDepends = []
var expr = desc.expr
var preSetFunc = typeof expr === 'function'
// 缓存取值函数
this.getter = preSetFunc ? expr : createGetter(expr)
// 缓存设值函数(双向数据绑定)
this.setter = this.type === 'duplex' ? createSetter(expr) : null
// 缓存表达式旧值
this.oldVal = null
// 表达式初始值 & 提取依赖
this.value = this.get()
}
var wp = Watcher.prototype
/**
* 获取取值域
* @return {Object}
*/
wp.getScope = function () {
return this.context.scope || this.vm
}
wp.getValue = function () {
var scope = this.getScope()
try {
return this.getter.call(scope, scope)
} catch (e) {
avalon.log(this.getter + 'exec error')
}
}
wp.setValue = function (value) {
var scope = this.getScope()
if (this.setter) {
this.setter.call(scope, scope, value)
}
}
wp.get = function () {
var value
this.beforeGet()
value = this.getValue()
// 深层依赖获取
if (this.deep) {
// 先缓存浅依赖的 ids
this.shallowIds = copy(this.newDepIds)
walkThrough(value, true)
}
this.afterGet()
return value
}
wp.beforeGet = function () {
Depend.watcher = this
}
wp.addDepend = function (depend) {
var guid = depend.guid
var newIds = this.newDepIds
if (newIds.indexOf(guid) < 0) {
newIds.push(guid)
this.newDepends.push(depend)
if (this.depIds.indexOf(guid) < 0) {
depend.addWatcher(this)
}
}
}
wp.removeDepends = function (filter) {
var self = this
this.depends.forEach(function (depend) {
if (filter) {
if (filter.call(self, depend)) {
depend.removeWatcher(self)
}
} else {
depend.removeWatcher(self)
}
})
}
wp.afterGet = function () {
Depend.watcher = null
// 清除无用的依赖
this.removeDepends(function (depend) {
return this.newDepIds.indexOf(depend.guid) < 0
})
// 重设依赖缓存
this.depIds = copy(this.newDepIds)
this.newDepIds.length = 0
this.depends = copy(this.newDepends)
this.newDepends.length = 0
}
wp.beforeUpdate = function () {
this.oldVal = copy(this.value)
}
wp.update = function (args, guid) {
var oldVal = this.oldVal
var newVal = this.value = this.get()
var callback = this.callback
if (callback && (oldVal !== newVal)) {
var fromDeep = this.deep && this.shallowIds.indexOf(guid) < 0
callback.call(this.context, newVal, oldVal, fromDeep, args)
}
}
wp.destroy = function () {
this.value = null
this.removeDepends()
if (this._destroy) {
this._destroy()
}
for (var i in this) {
delete this[i]
}
}
//========== 渲染模块 =========
function isDirective(directive) {
return /^(?:\:|ms-)\w+/.test(directive)
}
function delayCompileNodes(dirs) {
for (var i in delayCompile) {
if (('ms-' + i) in dirs) {
return true
}
}
}
var regMustache = /\{\{.+\}\}/
var rcolon = /^(:|ms)-/
function getRawBindings(node) {
if (node.nodeType === 1) {
var attrs = node.attributes
var props = {}, has = false
for (var i = 0, n = attrs.length; i < n; i++) {
var attr = attrs[i]
if (attr.specified) {
var name = attr.name
if (name.charAt(0) === ':') {
name = name.replace(rcolon, 'ms-')
}
if (startWith(name,'ms-')) {
props[name] = attr.value
has = true
}
}
}
if (attrs['is']) {
if (!props['ms-widget']) {
props['ms-widget'] = '{}'
}
has = true
}
return has ? props : false
} else if (node.nodeType === 3) {
if (regMustache.test(node.nodeValue)) {
return {
nodeValue: node.nodeValue
}
}
} else if (node.nodeType === 8) {
if (startWith(node.nodeValue,'ms-for:')) {
var nodes = []
var deep = 1
var begin = node
var expr = node.nodeValue.replace('ms-for:', '')
node.nodeValue = 'msfor:' + expr
while (node = node.nextSibling) {
nodes.push(node)
if (node.nodeType === 8) {
if (startWith(node.nodeValue,'ms-for:')) {
deep++
} else if (startWith(node.nodeValue,'ms-for-end:')) {
deep--
if (deep === 0) {
node.nodeValue = 'msfor-end:'
nodes.pop()
}
}
}
}
var f = createFragment()
nodes.forEach(function (n) {
f.appendChild(n)
})
this.queue.push([
f, this.vm, {'ms-for': expr}, begin
])
}
}
}
function emptyNode(a) {
var f = createFragment()
while (a.firstChild) {
f.appendChild(a.firstChild)
}
return f
}
avalon.scan = function (node, vm) {
return new Render(node, vm)
}
var eventMap = avalon.oneObject('animationend,blur,change,input,click,dblclick,focus,keydown,keypress,keyup,mousedown,mouseenter,mouseleave,mousemove,mouseout,mouseover,mouseup,scan,scroll,submit')
function Render(node, vm) {
this.node = node
this.vm = vm
this.queue = []
this.directives = []
this.init()
}
var cp = Render.prototype
cp.init = function () {
this.fragment = emptyNode(this.node)
this.getBindings(this.fragment, true)
}
cp.getRawBindings = getRawBindings
cp.getBindings = function (element, root) {
var childNodes = element.childNodes
var scope = this.vm
var dirs = this.getRawBindings(element)
if (dirs) {
this.queue.push([element, scope, dirs])
}
if (!/style|textarea|xmp|script|template/i.test(element.nodeName)
&& childNodes
&& childNodes.length
&& !delayCompileNodes(dirs || {})
) {
for (var i = 0; i < childNodes.length; i++) {
this.getBindings(childNodes[i], false)
}
}
if (root) {
this.compileBindings()
}
}
cp.compileBindings = function () {
this.queue.forEach(function (tuple) {
this.parseBindings(tuple)
}, this)
this.node.appendChild(this.fragment)
}
/**
* 将收集到的绑定属性进行深加工,最后转换为watcher
* @param {Array} tuple [node, scope, dirs]
*/
cp.parseBindings = function (tuple) {
var node = tuple[0]
var scope = tuple[1]
var dirs = tuple[2]
if ('nodeValue' in dirs) {
this.parseText(node, dirs, scope)
} else if (!('ms-skip' in dirs)) {
var uniq = {}, bindings = []
var directives = avalon.directives
for (var name in dirs) {
var value = dirs[name]
var rbinding = /^(\:|ms\-)\w+/
var match = name.match(rbinding)
var arr = name.replace(match[1], '').split('-')
if (eventMap[arr[0]]) {
arr.unshift('on')
}
if (arr[0] === 'on') {
arr[2] = parseFloat(arr[2]) || 0
}
arr.unshift('ms')
var type = arr[1]
if (directives[type]) {
var binding = {
type: type,
param: arr[2],
name: arr.join('-'),
expr: value,
priority: directives[type].priority || type.charCodeAt(0) * 100
}
if (type === 'on') {
binding.priority += arr[3]
}
if (!uniq[binding.name]) {
uniq[binding.name] = value
bindings.push(binding)
if (type === 'for') {
binding.begin = tuple[3]
bindings = [binding]
break
}
}
}
}
bindings.forEach(function (binding) {
this.parse(node, binding, scope)
}, this)
}
}
cp.parse = function (node, binding, scope) {
var dir = avalon.directives[binding.type]
if (dir) {
if (dir.parse) {
dir.parse(binding)
}
this.directives.push(new DirectiveWatcher(node, binding, scope))
}
}
cp.parseText = function (node, dir, scope) {
var rlineSp = /\n\r?/g
var text = dir.nodeValue.trim().replace(rlineSp, '')
var pieces = text.split(/\{\{(.+?)\}\}/g)
var tokens = []
pieces.forEach(function (piece) {
var segment = '{{' + piece + '}}'
if (text.indexOf(segment) > -1) {
tokens.push('(' + piece + ')')
text = text.replace(segment, '')
} else if (piece) {
tokens.push(avalon.quote(piece))
text = text.replace(piece, '')
}
})
var binding = {
expr: tokens.join('+'),
name: 'nodeValue',
type: 'nodeValue'
}
this.directives.push(new DirectiveWatcher(node, binding, scope))
}
cp.destroy = function () {
this.directives.forEach(function (directive) {
directive.destroy()
})
for (var i in this) {
delete this[i]
}
}
//========== Eval ========
var stringNum = 0
var stringPool = {
map: {}
}
var rfill = /\?\?\d+/g
function dig(a) {
var key = '??' + stringNum++
stringPool.map[key] = a
return key + ' '
}
function fill(a) {
var val = stringPool.map[a]
return val
}
function clearString(str) {
var array = readString(str)
for (var i = 0, n = array.length; i < n; i++) {
str = str.replace(array[i], dig)
}
return str
}
function readString(str) {
var end, s = 0
var ret = []
for (var i = 0, n = str.length; i < n; i++) {
var c = str.charAt(i)
if (!end) {
if (c === "'") {
end = "'"
s = i
} else if (c === '"') {
end = '"'
s = i
}
} else {
if (c === '\\') {
i += 1
continue
}
if (c === end) {
ret.push(str.slice(s, i + 1))
end = false
}
}
}
return ret
}
var keyMap = avalon.oneObject("break,case,catch,continue,debugger,default,delete,do,else,false," +
"finally,for,function,if,in,instanceof,new,null,return,switch,this," +
"throw,true,try,typeof,var,void,while,with," + /* 关键字*/
"abstract,boolean,byte,char,class,const,double,enum,export,extends," +
"final,float,goto,implements,import,int,interface,long,native," +
"package,private,protected,public,short,static,super,synchronized," +
"throws,transient,volatile")
var skipMap = avalon.mix({
Math: 1,
Date: 1,
$event: 1,
__vmodel__: 1
}, keyMap)
var rguide = /(^|[^\w\u00c0-\uFFFF_])(@|##)(?=[$\w])/g
var ruselessSp = /\s*(\.|\|)\s*/g
var rlocal = /[$a-z_][$\.\w\_]*/gi
var rregexp = /(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/g
function addScope(expr) {
var body = expr.trim().replace(rregexp, dig)//移除所有正则
body = clearString(body) //移除所有字符串
body = body.replace(ruselessSp, '$1').//移除.|两端空白
replace(rguide, '$1__vmodel__.').//转换@与##
replace(rlocal, function (a, b) {
var arr = a.split('.')
if (!skipMap[arr[0]]) {
return '__vmodel__.' + a
}
return a
}).replace(rfill, fill).replace(rfill, fill)
return body
}
function createGetter(expr) {
var body = addScope(expr)
try {
return new Function('__vmodel__', 'return ' + body + ';')
} catch (e) {
avalon.log('parse getter: ', expr, body, ' error')
return avalon.noop
}
}
/**
* 生成表达式设值函数
* @param {String} expr
*/
function createSetter(expr) {
var body = addScope(expr)
if (!startWith(body,'__vmodel__.')) {
body = ' __vmodel__.' + body
}
body = 'try{ ' + body + ' = __value__}catch(e){}'
try {
return new Function('__vmodel__', '__value__', body + ';')
} catch (e) {
avalon.log('parse setter: ', expr, ' error')
return avalon.noop
}
}
//=================== 各种指令的实现 ==============
/**
* 一个watcher装饰器
* @returns {watcher}
*/
function DirectiveWatcher(node, binding, scope) {
var type = binding.type
var directive = avalon.directives[type]
if (node.nodeType === 1) {
node.removeAttribute('ms-' + type)
node.removeAttribute(':' + type)
}
var callback = directive.update ? function (value) {
directive.update.call(this, node, value)
} : avalon.noop
var watcher = new Watcher(scope, binding, callback)
watcher.node = node
watcher._destory = directive.destory
if (directive.init)
directive.init(watcher)
delete watcher.value
watcher.update()
return watcher
}
avalon.directive('nodeValue', {
update: function (node, value) {
node.nodeValue = value
}
})
avalon.directive('attr', {
update: function (node, value) {
for (var i in value) {
node[i] = value[i]
}
}
})
avalon.directive('css', {
update: function (node, value) {
for (var i in value) {
node.style.setPropertyValue(i, value[i])
}
}
})
avalon.directive('on', {
init: function (watcher) {
var node = watcher.node
var body = addScope(watcher.expr)
var rhandleName = /^__vmodel__\.[$\w\.]+$/i
if (rhandleName.test(body)) {
body = body + '($event)'
}
body = body.replace(/__vmodel__\.([^(]+)\(([^)]*)\)/, function (a, b, c) {
return '__vmodel__.' + b + '.call(__vmodel__' + (/\S/.test(c) ? ',' + c : '') + ')'
})
var ret = [
'try{',
'\tvar __vmodel__ = this;',
'\t' + body,
'}catch(e){avalon.log(e)}']
var fn = new Function('$event', ret.join('\n'))
this.eventHandler = function (e) {
return fn.call(watcher.vm, e)
}
node.addEventListener(watcher.param, this.eventHandler)
},
destory: function () {
this.node.removeEventListener(this.param, this.eventHandler)
}
})
avalon.directive('if', {
delay: true,
priority: 5,
init: function (watcher) {
var node = watcher.node
node.removeAttribute('ms-if')
node.removeAttribute(':if')
watcher.node = node
var parent = node.parentNode
var c = watcher.placeholder = createAnchor('if')
replaceNode(c, node)
watcher.isShow = true
var f = createFragment()
f.appendChild(node)
watcher.fragment = f.cloneNode(true)
watcher.boss = avalon.scan(f, watcher.vm)
if (!!watcher.value) {
parent.replaceChild(f, c)
}
},
update: function (node, value) {
value = !!value
if (this.isShow === value)
return
this.isShow = value
if (value) {
var c = this.placeholder
var p = c.parentNode
var node = this.fragment.cloneNode(true)
this.boss = avalon.scan(node, this.vm)
this.node = node.firstChild
p.replaceChild(node, c)
} else {
var p = this.node.parentNode
var c = this.placeholder
p.replaceChild(c, this.node)
this.boss.destroy()
}
}
})
avalon.directive('html', {
update: function (node, value) {
this.boss && this.boss.destroy()
var div = document.createElement('div')
div.innerHTML = value
this.boss = avalon.scan(div, this.vm)
emptyNode(node)
node.appendChild(emptyNode(div))
},
delay: true
})
avalon.directive('duplex', {
priority: 999999,
init: function (watcher) {
var node = watcher.node
this.eventHandler = function () {
watcher.setValue(node.value)
}
if (/password|text|hidden/i.test(node.type)) {
node.addEventListener('input', this.eventHandler)
}
},
update: function (node, value) {
if (/password|text|hidden/i.test(node.type)) {
node.value = value
}
},
destory: function () {
this.node.removeEventListener('input', this.eventHandler)
}
})
avalon.directive('text', {
delay: true,
init: function (watcher) {
var node = watcher.node
emptyNode(node)
var child = document.createTextNode(watcher.value)
node.appendChild(child)
watcher.node = child
var type = 'nodeValue'
watcher.type = watcher.name = type
var directive = avalon.directives[type]
watcher.callback = function (value) {
directive.update.call(this, watcher.node, value)
}
}
})
var none = 'none'
function getDisplay(el) {
return window.getComputedStyle(el, null).display
}
function parseDisplay(elem, val) {
//用于取得此类标签的默认display值
var doc = elem.ownerDocument
var nodeName = elem.nodeName
var key = '_' + nodeName
if (!parseDisplay[key]) {
var temp = doc.body.appendChild(doc.createElement(nodeName))
val = getDisplay(temp)
doc.body.removeChild(temp)
if (val === none) {
val = 'block'
}
parseDisplay[key] = val
}
return parseDisplay[key]
}
avalon.directive('skip', {
delay: true
})
avalon.directive('visible', {
init: function (watcher) {
watcher.isShow = true
},
update: function (node, value) {
var isShow = !!value
if (this.isShow === isShow)
return
this.isShow = isShow
var display = node.style.display
if (isShow) {
if (display === none) {
value = this.displayValue
if (!value) {
node.style.display = ''
if (node.style.cssText === '') {
node.removeAttribute('style')
}
}
}
if (node.style.display === '' && getDisplay(node) === none &&
// fix firefox BUG,必须挂到页面上
node.ownerDocument.contains(node)) {
value = parseDisplay(node)
}
} else {
if (display !== none) {
value = none
this.displayValue = display
}
}
function cb() {
if (value !== void 0) {
node.style.display = value
}
}
cb()
}
})
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
avalon.directive('for', {
delay: true,
priority: 3,
parse: function (binding) {
var str = binding.origExpr = binding.expr, asName
str = str.replace(rforAs, function (a, b) {
/* istanbul ignore if */
if (!rident.test(b) || rinvalid.test(b)) {
avalon.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) {//确保avalon._each的回调有三个参数
kv.unshift('$key')
}
binding.expr = arr[1]
binding.keyName = kv[0]
binding.valName = kv[1]
binding.signature = avalon.makeHashCode('for')
if (asName) {
binding.asName = asName
}
},
init: function (watcher) {
var node = watcher.node
if (node.nodeType === 11) {
watcher.fragment = node
var begin = watcher.begin
delete watcher.begin
} else {
begin = createAnchor('msfor:' + watcher.origExpr)
var end = createAnchor('msfor-end:')
var p = node.parentNode
p.insertBefore(begin, node)
p.replaceChild(end, node)
var f = createFragment()
f.appendChild(node)
watcher.fragment = f
var cb = node.getAttribute('data-for-rendered')
if (cb) {
watcher.forCb = createGetter(cb)
}
}
watcher.node = begin
watcher.end = watcher.node.nextSibling
watcher.fragment.appendChild(createAnchor(watcher.signature))
watcher.cache = {}
watcher.update = function () {
var newVal = this.value = this.get()
var traceIds = createFragments(this, newVal)
var callback = this.callback
if (this.oldTrackIds !== traceIds) {
this.oldTrackIds = traceIds
callback.call(this.context, newVal)
}
}
},
update: function (node, value) {
if (!this.preFragments) {
mountList(this)
} else {
diffList(this)
updateList(this)
}
if (this.forCb) {
this.forCb()
}
}
})
function getTraceKey(item) {
var type = typeof item
return item && type === 'object' ? item.$hashcode : type + ':' + item
}
//创建一组fragment的虚拟DOM
function createFragments(watcher, obj) {
if (isObject(obj)) {
var array = isArray(obj)
var ids = []
var fragments = [], i = 0
avalon.each(obj, function (key, value) {
var k = array ? getTraceKey(value) : key
fragments.push(new Fragment(k, value, i++))
ids.push(k)
})
if (watcher.fragments) {
watcher.preFragments = watcher.fragments
watcher.fragments = fragments
} else {
watcher.fragments = fragments
}
return ids.join(';;')
} else {
return NaN
}
}
function mountList(watcher) {
var f = createFragment()
watcher.fragments.forEach(function (fragment, index) {
FragmentDecorator(fragment, watcher, index)
saveInCache(watcher.cache, fragment)
f.appendChild(fragment.dom)
})
watcher.end.parentNode.insertBefore(f, watcher.end)
}
function diffList(watcher) {
var cache = watcher.cache
var newCache = {}
var fuzzy = []
var list = watcher.preFragments
list.forEach(function (el) {
el._destory = true
})
watcher.fragments.forEach(function (c, index) {
var fragment = isInCache(cache, c.key)
//取出之前的文档碎片
if (fragment) {
delete fragment._destory
fragment.oldIndex = fragment.index
fragment.index = index // 相当于 c.index
fragment.vm[watcher.keyName] = index
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[watcher.valName] = val
fragment.vm[watcher.keyName] = index
delete fragment._destory
} else {
fragment = FragmentDecorator(c, watcher, c.index)
list.push(fragment)
}
saveInCache(newCache, fragment)
})
watcher.fragments = list
list.sort(function (a, b) {
return a.index - b.index
})
watcher.cache = newCache
}
function updateList(watcher) {
var before = watcher.node
var parent = before.parentNode
var list = watcher.fragments
for (var i = 0, item; item = list[i]; i++) {
if (item._destory) {
list.splice(i, 1)
i--
item.destory()
continue
}
if (item.ordexIndex !== item.index) {
if (item.dom && !item.dom.childNodes.length) {
item.move()
}
parent.insertBefore(item.dom, before.nextSibling)
}
before = item.split
}
}
function Fragment(key, val, index) {
this.name = '#document-fragment'
this.key = key
this.val = val
this.index = index
}
Fragment.prototype = {
destory: function () {
this.move()
this.boss.destroy()
for (var i in this) {
this[i] = null
}
},
move: function () {
var pre = this.split
var f = this.dom
var list = [pre]
var w = this.watcher
var a = 99999
do {
pre = pre.previousSibling
if (!pre || pre === w.node || pre.nodeValue === w.signature) {
break
}
list.unshift(pre)
} while (--a)
list.forEach(function (el) {
f.appendChild(el)
})
return f
}
}
/**
*
* @param {type} fragment
* @param {type} watcher
* @param {type} index
* @returns { key, val, index, oldIndex, watcher, dom, split, boss, vm}
*/
function FragmentDecorator(fragment, watcher, index) {
var dom = fragment.dom = watcher.fragment.cloneNode(true)
fragment.split = dom.lastChild
fragment.watcher = watcher
fragment.vm = observeItemObject(watcher.vm, {
data: new function () {
var data = {}
data[watcher.keyName] = fragment.index
data[watcher.valName] = fragment.val
if (watcher.asName) {
data[watcher.asName] = []
}
return data
}
})
fragment.index = index
fragment.boss = avalon.scan(dom, fragment.vm)
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)
}
}
avalon.directive('widget', {
delay: true,
priority: 4,
init: function (watcher) {
var node = watcher.node
var is = node.getAttribute('is')
var component = avalon.components[is]
if (component) {
var slots = {}, soleSlot
if (component.soleSlot) {
soleSlot = avalon.scan(emptyNode(node), watcher.vm)
} else {
avalon.each(node.childNodes, function (el) {
var name = el.getAttribute('slot')
if (name) {
slots[name] = avalon.scan(el, watcher.vm)
}
})
}
var opt = watcher.value
if (isObject(watcher.value)) {
var def = avalon.mix({}, component.defaults)
for (var i in def) {
def[i] = opt[i]
}
if (opt.id) {
def.$id = opt.id
}
var vm = avalon.define(def)
var div = document.createElement('div')
div.innerHTML = component.template
var boss = avalon.scan(div, vm)
var com = div.children[0]
var els = com.querySelectorAll('slot')
var push = Array.prototype.push
if (soleSlot) {
push.apply(boss.directives, soleSlot.directives)
replaceNode(soleSlot.node, els[0])
} else {
avalon.each(function (el) {
var name = el.getAttribute('name')
replaceNode(slots[name].node, el)
push.apply(boss.directives, slots[name].directives)
})
}
replaceNode(com, node)
watcher.node = com
watcher.comBoss = boss
}
}
},
update: function (node, value) {
},
destory: function () {
this.comBoss.destory()
}
})
avalon.components = {}
avalon.component = function (name, component) {
/**
* template: string
* defaults: object
* soleSlot: string
*/
avalon.components[name] = component
}
})()