UNPKG

avalon2

Version:

an elegant efficient express mvvm framework

367 lines (339 loc) 11.5 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 } this.readyState = 1 //如果是非空元素,比如说xmp, ms-*, template var id = value.id || value.$id var hasCache = avalon.vmodels[id] var fromCache = false var component = new Component() if (hasCache) { comVm = hasCache this.comVm = comVm replaceRoot(this, comVm.$render) fromCache = true } else { console.log(value) var comVm = component.__getVm(value) fireComponentHook(comVm, vdom, 'Init') this.comVm = comVm var boss = avalon.scan(component.template, comVm) comVm.$render = boss replaceRoot(this, boss) 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 for (var i in childBoss) { delete childBoss[i] } } boss.directives.push.apply(boss.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(boss.vnodes, arraySlot) } else { insertObjectSlot(boss.vnodes, objectSlot) } } if (comment) { var dom = avalon.vdom(vdom, 'toDOM') comment.parentNode.replaceChild(dom, comment) comVm.$element = boss.root.dom = dom delete this.reInit } //处理DOM节点 dumpTree(vdom.dom) groupTree(vdom.dom, vdom.children) if (fromCache) { fireComponentHook(comVm, vdom, 'Enter') } else { fireComponentHook(comVm, vdom, 'Ready') } this.beforeDispose = function() { if (!this.cacheVm) { fireComponentHook(comVm, vdom, 'Dispose') comVm.$hashcode = false delete avalon.vmodels[comVm.$id] this.boss.dispose() } else { fireComponentHook(comVm, vdom, 'Leave') } } }, 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() } 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)) { comVm[i] = value[i] } } }) //要保证要先触发孩子的ViewChange 然后再到它自己的ViewChange fireComponentHook(comVm, vdom, 'ViewChange') delete avalon.viewChanging break } } }) function replaceRoot(instance, boss) { instance.boss = boss var root = boss.root var vdom = instance.node for (var i in root) { vdom[i] = root[i] } boss.root = vdom boss.vnodes[0] = vdom } function fireComponentHook(vm, vdom, name) { var list = vm.$events['on' + name] if (list) { list.forEach(function(el) { el.callback.call(vm, { type: name.toLowerCase(), target: vdom.dom, vmodel: 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] } } } 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) } } } function Avalon() {} Avalon.prototype = { constructor: Avalon, __getVm: function(props) { var hooks = [] var ctor = this.constructor var def = avalon.mix(true, {}, this, this.defaults, (ctor.defaults || {})) delete def.__getVm delete def.id delete def.is delete def.template delete def.soleSlot collectHooks(def, hooks) if (props) { collectHooks(props, hooks) for (var i in def) { var value = props[i] if (value != null) { if (value && typeof value === 'object') { def[i] = avalon.mix(true, Array.isArray(value) ? [] : {}, value) } else { def[i] = value } } } } else { props = {} } var is = ctor.name || '$' def.$id = props.id || props.$id || avalon.makeHashCode(is) var vm = avalon.define(def) hooks.forEach(function(el) { vm.$watch(el.type, el.cb) }) return vm } } avalon.Avalon = Avalon avalon.createComponent = (function() { function obj(o) { var __ = function() {} __.prototype = o return new __ } function extend(child, parent) { //这里要创建对象就是防止对parent产生污染 var proto = child.prototype = obj(parent.prototype); proto.constructor = child } return function(className, define, superClass) { className = avalon.camelize(className) var body = ['function ' + className + '(props){', '\t__parent.call(this)', '\tthis.template = ' + avalon.quote(define.template), (define.soleSlot ? '\tthis.soleSlot = ' + avalon.quote(define.soleSlot) : ''), '}', '__extends(' + className + ',__parent)', 'return ' + className ].join('\n') var factory = Function('__parent', '__extends', body) var parentClass = Avalon if (superClass) { parentClass = superClass } var c = factory(parentClass, extend) var m = superClass ? superClass.defaults : {} c.defaults = avalon.mix({}, m, define.defaults) return c } })() avalon.components = {} avalon.component = function(name, component) { /** * template: string * defaults: object * soleSlot: string */ if (component !== 'function') { //2.2.1 component = avalon.createComponent(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--; } } }