UNPKG

migi

Version:

A JavaScript MVVM on JSX

298 lines (288 loc) 8.56 kB
import Event from './Event'; import Element from './Element'; import VirtualDom from './VirtualDom'; import util from './util'; import Obj from './Obj'; import Cb from './Cb'; import Model from './Model'; import array from './array'; import fixEvent from './fixEvent'; const STOP = ['click', 'dblclick', 'focus', 'blur', 'change', 'contextmenu', 'mousedown', 'mousemove', 'mouseover', 'mouseup', 'mouseout', 'mousewheel', 'resize', 'scroll', 'select', 'submit', 'DOMActivate', 'DOMFocusIn', 'DOMFocusOut', 'keydown', 'keypress', 'keyup', 'drag', 'dragstart', 'dragover', 'dragenter', 'dragleave', 'dragend', 'drop', 'formchange', 'forminput', 'input', 'cut', 'paste', 'reset', 'touchstart', 'touchmove', 'touchend', 'touchcancel', 'MSGestureEnd', 'MSPointerDown', 'pointerdown', 'MSPointerMove', 'pointermove', 'MSPointerUp', 'pointerup', 'MSPointerCancel', 'pointercancel']; class Component extends Element { constructor(uid, props = [], children = []) { super(uid, null, props, children); var self = this; var proto = Object.getPrototypeOf(self); ['on', 'once', 'emit', 'off', 'clean', 'inTo', 'appendTo', 'prependTo', 'before', 'after', 'replace', 'top', 'parent', 'children', 'dom', 'toString', 'preString', 'findChild', 'findChildren', 'find', 'findAll', 'stopPropagation', 'element', 'style', 'model', 'virtualDom', 'ref'].forEach(function(key) { if(proto.hasOwnProperty(key)) { throw new Error(`cannot overwrite method of '${key}'`); } }); if(self.constructor.__migiName) { self.__name = self.constructor.__migiName; } self.__virtualDom = null; // 根节点vd引用 self.__ref = {}; // 以ref为attr的vd快速访问引用 // self.__stop = null; // 停止冒泡的fn引用 // self.__model = null; // 数据模型引用 // self.__stopPropagation = false; // 默认允许冒泡 // self.__canData = false; // 防止添加至DOM前触发无谓的数据更新 self.__bindHash = {}; // 缩略语法中是否设置过默认值 self.__ob = []; // 被array们的__ob__引用 self.__bindProperty = {}; // @property语法,出现在组件属性上时联动父层@bind值更新 self.__props.forEach(function(item, index) { var k = item[0]; var v = item[1]; self.__init(k, v, index); }); } __init(k, v, index) { var self = this; if(/^on[a-zA-Z]/.test(k)) { var name = k.slice(2).toLowerCase(); self.once(Event.DOM, function() { self.virtualDom.__addEvt(name, v); }); } else if(/^on-[a-zA-Z\d_]/.test(k)) { var name = k.slice(3); self.on(name, function(...data) { if(v instanceof Cb) { if(util.isFunction(v.cb)) { v.cb.call(v.context, ...data); } } else if(util.isFunction(v)) { v(...data); } }); } else if(k == 'model') { self.model = v; } else if(v instanceof Obj) { self.__props[index] = v.v; self.props[k] = v.v; self.__bindProperty[v.k] = [k, v]; } } // 需要被子类覆盖 // @abstract render() { return new VirtualDom(this.__uid, 'div', this.__props, this.__children); } // @override toString() { this.__virtualDom = this.render(); if(!this.__virtualDom) { throw new Error('render must return a VirtualDom: ' + this.__name); } this.__virtualDom.__parent = this; if(this.__style) { this.__virtualDom.style = this.__style; } return this.__virtualDom.toString(); } // @override preString() { this.toString(); } findChild(name) { return this.findChildren(name, true)[0]; } findChildren(name, first) { var res = []; for(var i = 0, len = this.children.length; i < len; i++) { var child = this.children[i]; if(child instanceof Element) { if(child instanceof Component) { if(child.__name == name || util.isFunction(name) && child instanceof name) { res.push(child); if(first) { break; } } } else { if(child.__name == name || util.isFunction(name) && child instanceof name) { res.push(child); if(first) { break; } } res = res.concat(child.findAll(name)); if(first && res.length) { break; } } } } return res; } find(selector) { return this.__virtualDom ? this.__virtualDom.find(selector) : null; } findAll(selector) { return this.__virtualDom ? this.__virtualDom.findAll(selector) : []; } // @overwrite __onDom() { super.__onDom(); var self = this; self.virtualDom.emit(Event.DOM); var elem = self.element; if(self.__name) { elem.setAttribute('migi-name', self.__name); } // 无覆盖render时渲染标签的children;有时渲染render的children // 指定不允许冒泡,默认是全部冒泡 if(self.props.stopPropagation === true || self.props.stopPropagation === 'true') { // stop } else if(self.props.stopPropagation === false || self.props.stopPropagation === 'false') { return; } else if(!self.stopPropagation) { return; } // 将所有组件DOM事件停止冒泡,形成shadow特性,但不能阻止捕获 function stopPropagation(e) { e = e || window.event; fixEvent(e); if(e.target != elem && e.srcElement != elem) { e.cancelBubble = true; e.stopPropagation && e.stopPropagation(); } } self.__stop = stopPropagation; // 仅考虑用户事件,媒体等忽略 STOP.forEach(function(name) { elem.addEventListener(name, stopPropagation); }); } __data(k, opt) { var self = this; // set触发数据变更时,若已DOM则打开开关 if(self.dom) { self.__canData = true; } self.__onData(k, opt); self.emit(Event.DATA, k, self[k]); } // @overwrite __onData(k, opt) { // 未DOM或开关时不触发更新 if(!this.__dom || !this.__canData) { return; } if(this.virtualDom) { this.virtualDom.__onData(k, opt); } for(var i = 0, len = this.children.length; i < len; i++) { var child = this.children[i]; if(child instanceof VirtualDom) { child.__onData(k, opt); } } } __notifyBindProperty(k) { if(this.__bindProperty.hasOwnProperty(k)) { var arr = this.__bindProperty[k]; var bindProperty = arr[0]; let obj = arr[1]; if(obj.update(obj.v)) { this[bindProperty] = obj.v; } } } __destroy() { var self = this; if(self.__stop) { var elem = self.element; STOP.forEach(function(name) { elem.removeEventListener(name, self.__stop); }); } if(self.model) { self.model.__del(self); } // 侦听array里面的引用需删除 self.__ob.forEach(function(arr) { var i = arr.__ob__.indexOf(self); if(i > -1) { arr.__ob__.splice(i, 1); arr.__cb__.splice(i, 1); } }); var vd = self.virtualDom.__destroy(); self.emit(Event.DESTROY); self.__hash = {}; self.__bindProperty = null; return vd; } __initBind(name) { return !this.__bindHash.hasOwnProperty(name); } __getBind(name) { return this.__bindHash[name]; } __setBind(name, v) { this.__bindHash[name] = v; this.__array(name, v); } __array(name, v) { var self = this; // 检查array类型,替换并侦听array的原型方法 if(Array.isArray(v)) { v.__proto__ = array; v.__ob__ = v.__ob__ || []; v.__cb__ = v.__cb__ || []; if(v.__ob__.indexOf(self) == -1) { self.__ob.push(v); v.__ob__.push(self); v.__cb__.push(function(opt) { self.__data(name, opt); }); } } } get stopPropagation() { return this.__stopPropagation; } set stopPropagation(v) { this.__stopPropagation = v; } get element() { return this.virtualDom ? this.virtualDom.element : null; } get style() { return this.__style; } set style(v) { this.__style = v; } get model() { return this.__model; } set model(v) { if(!(v instanceof Model)) { throw new Error('can not set model to a non Model: ' + v); } this.__model = v; v.__add(this); } get virtualDom() { return this.__virtualDom; } get ref() { return this.__ref; } } export default Component;