UNPKG

avalon2

Version:

an elegant efficient express mvvm framework

344 lines (316 loc) 10.9 kB
import { avalon, isObject, platform } from '../seed/core' import { cssDiff } from '../directives/css' import { dumpTree, groupTree, getRange } from '../renders/share' var legalTags = { wbr: 1, xmp: 1, template: 1 } var events = 'onInit,onReady,onViewChange,onDispose,onEnter,onLeave' var componentEvents = avalon.oneObject(events) function toObject(value) { var value = platform.toJson(value) if (Array.isArray(value)) { var v = {} value.forEach(function(el) { el && avalon.shadowCopy(v, el) }) return v } return value } var componentQueue = [] avalon.directive('widget', { delay: true, priority: 4, deep: true, init: function() { //cached属性必须定义在组件容器里面,不是template中 var vdom = this.node this.cacheVm = !!vdom.props.cached if (vdom.dom && vdom.nodeName === '#comment') { var comment = vdom.dom } var oldValue = this.getValue() var value = toObject(oldValue) //外部VM与内部VM // ===创建组件的VM==BEGIN=== var is = vdom.props.is || value.is this.is = is var component = avalon.components[is] //外部传入的总大于内部 if (!('fragment' in this)) { if (!vdom.isVoidTag) { //提取组件容器内部的东西作为模板 var text = vdom.children[0] if (text && text.nodeValue) { this.fragment = text.nodeValue } else { this.fragment = avalon.vdom(vdom.children, 'toHTML') } } else { this.fragment = false } } //如果组件还没有注册,那么将原元素变成一个占位用的注释节点 if (!component) { this.readyState = 0 vdom.nodeName = '#comment' vdom.nodeValue = 'unresolved component placeholder' delete vdom.dom avalon.Array.ensure(componentQueue, this) return } //如果是非空元素,比如说xmp, ms-*, template var id = value.id || value.$id var hasCache = avalon.vmodels[id] var fromCache = false // this.readyState = 1 if (hasCache) { comVm = hasCache this.comVm = comVm replaceRoot(this, comVm.$render) fromCache = true } else { if(typeof component === 'function'){ component = new component(value) } var comVm = createComponentVm(component, value, is) this.readyState = 1 fireComponentHook(comVm, vdom, 'Init') this.comVm = comVm // ===创建组件的VM==END=== var innerRender = avalon.scan(component.template, comVm) comVm.$render = innerRender replaceRoot(this, innerRender) var nodesWithSlot = [] var directives = [] if (this.fragment || component.soleSlot) { var curVM = this.fragment ? this.vm : comVm var curText = this.fragment || '{{##' + component.soleSlot + '}}' var childBoss = avalon.scan('<div>' + curText + '</div>', curVM, function() { nodesWithSlot = this.root.children }) directives = childBoss.directives this.childBoss = childBoss for (var i in childBoss) { delete childBoss[i] } } Array.prototype.push.apply(innerRender.directives, directives) var arraySlot = [], objectSlot = {} //从用户写的元素内部 收集要移动到 新创建的组件内部的元素 if (component.soleSlot) { arraySlot = nodesWithSlot } else { nodesWithSlot.forEach(function(el, i) { //要求带slot属性 if (el.slot) { var nodes = getRange(nodesWithSlot, el) nodes.push(nodes.end) nodes.unshift(el) objectSlot[el.slot] = nodes } else if (el.props) { var name = el.props.slot if (name) { delete el.props.slot if (Array.isArray(objectSlot[name])) { objectSlot[name].push(el) } else { objectSlot[name] = [el] } } } }) } //将原来元素的所有孩子,全部移动新的元素的第一个slot的位置上 if (component.soleSlot) { insertArraySlot(innerRender.vnodes, arraySlot) } else { insertObjectSlot(innerRender.vnodes, objectSlot) } } if (comment) { var dom = avalon.vdom(vdom, 'toDOM') comment.parentNode.replaceChild(dom, comment) comVm.$element = innerRender.root.dom = dom delete this.reInit } //处理DOM节点 dumpTree(vdom.dom) comVm.$element = vdom.dom groupTree(vdom.dom, vdom.children) if (fromCache) { fireComponentHook(comVm, vdom, 'Enter') } else { fireComponentHook(comVm, vdom, 'Ready') } }, diff: function(newVal, oldVal) { if (cssDiff.call(this, newVal, oldVal)) { return true } }, update: function(vdom, value) { //this.oldValue = value //★★防止递归 switch (this.readyState) { case 0: if (this.reInit) { this.init() this.readyState++ } break case 1: this.readyState++ break default: this.readyState++ var comVm = this.comVm avalon.viewChanging = true avalon.transaction(function() { for (var i in value) { if (comVm.hasOwnProperty(i)) { if (Array.isArray(value[i])) { comVm[i] = value[i].concat() } else { comVm[i] = value[i] } } } }) //要保证要先触发孩子的ViewChange 然后再到它自己的ViewChange fireComponentHook(comVm, vdom, 'ViewChange') delete avalon.viewChanging break } this.value = avalon.mix(true, {}, value) }, beforeDispose: function() { var comVm = this.comVm if (!this.cacheVm) { fireComponentHook(comVm, this.node, 'Dispose') comVm.$hashcode = false delete avalon.vmodels[comVm.$id] this.innerRender && this.innerRender.dispose() } else { fireComponentHook(comVm, this.node, 'Leave') } }, }) function replaceRoot(instance, innerRender) { instance.innerRender = innerRender var root = innerRender.root var vdom = instance.node var slot = vdom.props.slot for (var i in root) { vdom[i] = root[i] } if (vdom.props && slot) { vdom.props.slot = slot } innerRender.root = vdom innerRender.vnodes[0] = vdom } function fireComponentHook(vm, vdom, name) { var list = vm.$events['on' + name] if (list) { list.forEach(function(el) { setTimeout(function(){ el.callback.call(vm, { type: name.toLowerCase(), target: vdom.dom, vmodel: vm }) },0) }) } } export function createComponentVm(component, value, is) { var hooks = [] var defaults = component.defaults collectHooks(defaults, hooks) collectHooks(value, hooks) var obj = {} for (var i in defaults) { var val = value[i] if (val == null) { obj[i] = defaults[i] } else { obj[i] = val } } obj.$id = value.id || value.$id || avalon.makeHashCode(is) delete obj.id var def = avalon.mix(true, {}, obj) var vm = avalon.define(def) hooks.forEach(function(el) { vm.$watch(el.type, el.cb) }) return vm } function collectHooks(a, list) { for (var i in a) { if (componentEvents[i]) { if (typeof a[i] === 'function' && i.indexOf('on') === 0) { list.unshift({ type: i, cb: a[i] }) } //delete a[i] 这里不能删除,会导致再次切换时没有onReady } } } function resetParentChildren(nodes, arr) { var dir = arr && arr[0] && arr[0].forDir if (dir) { dir.parentChildren = nodes } } function insertArraySlot(nodes, arr) { for (var i = 0, el; el = nodes[i]; i++) { if (el.nodeName === 'slot') { resetParentChildren(nodes, arr) nodes.splice.apply(nodes, [i, 1].concat(arr)) break } else if (el.children) { insertArraySlot(el.children, arr) } } } function insertObjectSlot(nodes, obj) { for (var i = 0, el; el = nodes[i]; i++) { if (el.nodeName === 'slot') { var name = el.props.name resetParentChildren(nodes, obj[name]) nodes.splice.apply(nodes, [i, 1].concat(obj[name])) continue } else if (el.children) { insertObjectSlot(el.children, obj) } } } avalon.components = {} avalon.component = function(name, component) { component.extend = componentExtend return addToQueue(name, component) } function addToQueue(name, component){ avalon.components[name] = component for (var el, i = 0; el = componentQueue[i]; i++) { if (el.is === name) { componentQueue.splice(i, 1) el.reInit = true delete el.value el.update() i--; } } return component } function componentExtend(child){ var name = child.displayName delete child.displayName var obj = {defaults: avalon.mix(true, {}, this.defaults, child.defaults)} if( child.soleSlot){ obj.soleSlot = child.soleSlot } obj.template = child.template || this.template return avalon.component(name, obj) }