magix
Version:
view manager framewrok
1,872 lines (1,723 loc) • 148 kB
JavaScript
//#snippet;
//#uncheck = jsThis,jsLoop;
//#exclude = loader,allProcessor;
/*!3.8.16 Licensed MIT*/
/*
author:kooboy_li@163.com
loader:cmd
enables:style,viewInit,service,ceach,router,resource,configIni,nodeAttachVframe,viewMerge,tipRouter,updater,viewProtoMixins,base,defaultView,autoEndUpdate,linkage,updateTitleRouter,urlRewriteRouter,state,updaterDOM,viewInitAsync
optionals:updaterVDOM,updaterQuick,updaterAsync,updaterTouchAttr,serviceCombine,servicePush,tipLockUrlRouter,edgeRouter,forceEdgeRouter,cnum,vframeHost,layerVframe,collectView,share,keepHTML,naked,viewChildren,dispatcherRecast
*/
define('magix', ['$'], require => {
if (typeof DEBUG == 'undefined') window.DEBUG = true;
let $ = require('$');
let G_IsObject = $.isPlainObject;
let G_IsArray = $.isArray;
let G_COUNTER = 0;
let G_EMPTY = '';
let G_EMPTY_ARRAY = [];
let G_COMMA = ',';
let G_NULL = null;
let G_WINDOW = window;
let G_Undefined = void G_COUNTER;
let G_DOCUMENT = document;
let GA = G_DOCUMENT.documentElement.getAttribute;
let G_GetAttribute = (node, attr) => GA.call(node, attr);
let G_DOC = $(G_DOCUMENT);
let Timeout = G_WINDOW.setTimeout;
let G_CHANGED = 'changed';
let G_CHANGE = 'change';
let G_PAGE_UNLOAD = 'pageunload';
let G_VALUE = 'value';
let G_Tag_Key = 'mxs';
let G_Tag_Attr_Key = 'mxa';
let G_Tag_View_Key = 'mxv';
let G_HashKey = '#';
function G_NOOP() { }
let JSONStringify = JSON.stringify;
let G_DOCBODY; //initilize at vframe_root
/*
关于spliter
出于安全考虑,使用不可见字符\u0000,然而,window手机上ie11有这样的一个问题:'\u0000'+"abc",结果却是一个空字符串,好奇特。
*/
let G_SPLITER = '\x1e';
let Magix_StrObject = 'object';
let G_PROTOTYPE = 'prototype';
let G_PARAMS = 'params';
let G_PATH = 'path';
let G_MX_VIEW = 'mx-view';
// let Magix_PathRelativeReg = /\/\.(?:\/|$)|\/[^\/]+?\/\.{2}(?:\/|$)|\/\/+|\.{2}\//; // ./|/x/../|(b)///
// let Magix_PathTrimFileReg = /\/[^\/]*$/;
// let Magix_ProtocalReg = /^(?:https?:)?\/\//i;
let Magix_PathTrimParamsReg = /[#?].*$/;
let Magix_ParamsReg = /([^=&?\/#]+)=?([^&#?]*)/g;
let Magix_IsParam = /(?!^)=|&/;
let G_Id = prefix => (prefix || 'mx_') + G_COUNTER++;
let MxGlobalView = G_Id();
let Magix_Cfg = {
rootId: G_Id(),
defaultView: MxGlobalView,
error(e) {
throw e;
}
};
let G_GetById = id => typeof id == Magix_StrObject ? id : G_DOCUMENT.getElementById(id);
let G_IsPrimitive = args => !args || typeof args != Magix_StrObject;
let G_Set = (newData, oldData, keys, unchanged) => {
let changed = 0,
now, old, p;
for (p in newData) {
now = newData[p];
old = oldData[p];
if ((!G_IsPrimitive(now) || old !== now) && !G_Has(unchanged, p)) {
keys[p] = 1;
changed = 1;
}
oldData[p] = now;
}
return changed;
};
let G_NodeIn = (a, b, r) => {
a = G_GetById(a);
b = G_GetById(b);
if (a && b) {
r = a == b;
if (!r) {
try {
r = (b.compareDocumentPosition(a) & 16) == 16;
} catch (_magix) { }
}
}
return r;
};
let {
assign: G_Assign,
keys: G_Keys,
hasOwnProperty: Magix_HasProp
} = Object;
let Header = $('head');
let View_ApplyStyle = (key, css) => {
if (DEBUG && G_IsArray(key)) {
for (let i = 0; i < key.length; i += 2) {
View_ApplyStyle(key[i], key[i + 1]);
}
return;
}
if (css && !View_ApplyStyle[key]) {
View_ApplyStyle[key] = 1;
if (DEBUG) {
if (key.indexOf('$throw_') === 0) {
throw new Error(css);
}
Header.append(`<style id="${key}">${css}`);
} else {
Header.append(`<style>${css}`);
}
}
};
let IdIt = n => G_GetAttribute(n, 'id') || (n['$a'] = 1, n.id = G_Id());
let G_ToTry = (fns, args, context, r, e) => {
args = args || G_EMPTY_ARRAY;
if (!G_IsArray(fns)) fns = [fns];
if (!G_IsArray(args)) args = [args];
for (e of fns) {
try {
r = e && e.apply(context, args);
} catch (x) {
Magix_Cfg.error(x);
}
}
return r;
};
let G_Has = (owner, prop) => owner && Magix_HasProp.call(owner, prop); //false 0 G_NULL '' undefined
let G_TranslateData = (data, params) => {
let p, val;
if (G_IsPrimitive(params)) {
p = params + G_EMPTY;
if (p[0] == G_SPLITER && G_Has(data, p)) {
params = data[p];
}
} else {
for (p in params) {
val = params[p];
val = G_TranslateData(data, val);
params[p] = val;
}
}
return params;
};
let Magix_CacheSort = (a, b) => b.f - a.f || b.t - a.t;
/**
* Magix.Cache 类
* @name Cache
* @constructor
* @param {Integer} [max] 缓存最大值,默认20
* @param {Integer} [buffer] 缓冲区大小,默认5
* @param {Function} [remove] 当缓存的元素被删除时调用
* @example
* let c = new Magix.cache(5,2);//创建一个可缓存5个,且缓存区为2个的缓存对象
* c.set('key1',{});//缓存
* c.get('key1');//获取
* c.del('key1');//删除
* c.has('key1');//判断
* //注意:缓存通常配合其它方法使用,在Magix中,对路径的解析等使用了缓存。在使用缓存优化性能时,可以达到节省CPU和内存的双赢效果
*/
function G_Cache(max, buffer, remove, me) {
me = this;
me.c = [];
me.b = buffer || 5; //buffer先取整,如果为0则再默认5
me.x = me.b + (max || 20);
me.r = remove;
}
G_Assign(G_Cache[G_PROTOTYPE], {
/**
* @lends Cache#
*/
/**
* 获取缓存的值
* @param {String} key
* @return {Object} 初始设置的缓存对象
*/
get(key) {
let me = this;
let c = me.c;
let r = c[G_SPLITER + key];
if (r) {
r.f++;
r.t = G_COUNTER++;
//console.log(r.f);
r = r.v;
//console.log('hit cache:'+key);
}
return r;
},
/**
* 循环缓存
* @param {Function} cb 回调
* @param {Object} [ops] 回调时传递的额外参数
* @beta
* @module ceach|service
*/
each(cb, ops, me, c, i) {
me = this;
c = me.c;
for (i of c) {
cb(i.v, ops, me);
}
},
/**
* 设置缓存
* @param {String} key 缓存的key
* @param {Object} value 缓存的对象
*/
set(okey, value) {
let me = this;
let c = me.c;
let key = G_SPLITER + okey;
let r = c[key];
let t = me.b,
f;
if (!r) {
if (c.length >= me.x) {
c.sort(Magix_CacheSort);
while (t--) {
r = c.pop();
//为什么要判断r.f>0,考虑这样的情况:用户设置a,b,主动删除了a,重新设置a,数组中的a原来指向的对象残留在列表里,当排序删除时,如果不判断则会把新设置的删除,因为key都是a
//
if (r.f > 0) me.del(r.o); //如果没有引用,则删除
}
}
r = {
o: okey
};
c.push(r);
c[key] = r;
}
r.v = value;
r.f = 1;
r.t = G_COUNTER++;
},
/**
* 删除缓存
* @param {String} key 缓存key
*/
del(k) {
k = G_SPLITER + k;
let c = this.c;
let r = c[k],
m = this.r;
if (r) {
r.f = -1;
r.v = G_EMPTY;
delete c[k];
if (m) {
G_ToTry(m, r.o);
}
}
},
/**
* 检测缓存中是否有给定的key
* @param {String} key 缓存key
* @return {Boolean}
*/
has(k) {
return G_Has(this.c, G_SPLITER + k);
}
});
let G_DefaultView;
let G_Require = (name, fn) => {
if (name) {
let a = [], n;
if (MxGlobalView == name) {
if (!G_DefaultView) {
G_DefaultView = View.extend();
}
fn(G_DefaultView);
} else
if (G_WINDOW.seajs) {
seajs.use(name, (...g) => {
for (let m of g) {
a.push(m && m.__esModule && m.default || m);
}
if (fn) {
CallFunction(fn, a);
}
});
} else {
if (!G_IsArray(name)) name = [name];
for (n of name) {
n = require(n);
a.push(n && n.__esModule && n.default || n);
}
if (fn) fn(...a);
}
} else {
fn();
}
};
function T() { }
let G_Extend = (ctor, base, props, statics, cProto) => {
//bProto.constructor = base;
T[G_PROTOTYPE] = base[G_PROTOTYPE];
cProto = new T();
G_Assign(cProto, props);
G_Assign(ctor, statics);
cProto.constructor = ctor;
ctor[G_PROTOTYPE] = cProto;
return ctor;
};
let G_SelectorEngine = $.find || $.zepto;
let G_TargetMatchSelector = G_SelectorEngine.matchesSelector || G_SelectorEngine.matches;
let G_DOMGlobalProcessor = (e, d) => {
d = e.data;
e.eventTarget = d.e;
G_ToTry(d.f, e, d.v);
};
let G_DOMEventLibBind = (node, type, cb, remove, scope) => {
if (scope) {
type += `.${scope.i}`;
}
if (remove) {
$(node).off(type, cb);
} else {
$(node).on(type, scope, cb);
}
};
let Safeguard = data => data;
if (DEBUG && window.Proxy) {
let ProxiesPool = new Map();
Safeguard = (data, getter, setter, root) => {
if (G_IsPrimitive(data)) {
return data;
}
let build = (prefix, o) => {
let key = getter + '\x01' + setter;
let cached = ProxiesPool.get(o);
if (cached && cached.key == key) {
return cached.entity;
}
if (o['\x1e_sf_\x1e']) {
return o;
}
let entity = new Proxy(o, {
set(target, property, value) {
if (!setter && !prefix) {
throw new Error('avoid writeback,key: ' + prefix + property + ' value:' + value + ' more info: https://github.com/thx/magix/issues/38');
}
target[property] = value;
if (setter) {
setter(prefix + property, value);
}
return true;
},
get(target, property) {
if (property == '\x1e_sf_\x1e') {
return true;
}
let out = target[property];
if (!prefix && getter) {
getter(property);
}
if (!root && G_Has(target, property) &&
(G_IsArray(out) || G_IsObject(out))) {
return build(prefix + property + '.', out);
}
return out;
}
});
ProxiesPool.set(o, {
key,
entity
});
return entity;
};
return build('', data);
};
}
let Magix_PathToObjCache = new G_Cache();
let Magix_Booted = 0;
//let Magix_PathCache = new G_Cache();
let Magix_ParamsObjectTemp;
let Magix_ParamsFn = (match, name, value) => {
try {
value = decodeURIComponent(value);
} catch (_magix) {
}
Magix_ParamsObjectTemp[name] = value;
};
/**
* 路径
* @param {String} url 参考地址
* @param {String} part 相对参考地址的片断
* @return {String}
* @example
* http://www.a.com/a/b.html?a=b#!/home?e=f / => http://www.a.com/
* http://www.a.com/a/b.html?a=b#!/home?e=f ./ =>http://www.a.com/a/
* http://www.a.com/a/b.html?a=b#!/home?e=f ../../ => http://www.a.com/
* http://www.a.com/a/b.html?a=b#!/home?e=f ./../ => http://www.a.com/
* //g.cn/a.html
*/
/*let G_Path = function(url, part) {
let key = url + G_SPLITER + part;
let result = Magix_PathCache.get(key),
domain = G_EMPTY,
idx;
if (!Magix_PathCache.has(key)) { //有可能结果为空,url='' path='';
let m = url.match(Magix_ProtocalReg);
if (m) {
idx = url.indexOf(Magix_SLASH, m[0].length);
if (idx < 0) idx = url.length;
domain = url.slice(0, idx);
url = url.slice(idx);
}
url = url.replace(Magix_PathTrimParamsReg, G_EMPTY).replace(Magix_PathTrimFileReg, Magix_SLASH);
if (!part.indexOf(Magix_SLASH)) {
url = G_EMPTY;
}
result = url + part;
console.log('url', url, 'part', part, 'result', result);
while (Magix_PathRelativeReg.test(result)) {
result = result.replace(Magix_PathRelativeReg, Magix_SLASH);
}
Magix_PathCache.set(key, result = domain + result);
}
return result;
};*/
/**
* 把路径字符串转换成对象
* @param {String} path 路径字符串
* @return {Object} 解析后的对象
* @example
* let obj = Magix.parseUri('/xxx/?a=b&c=d');
* // obj = {path:'/xxx/',params:{a:'b',c:'d'}}
*/
let G_ParseUri = path => {
//把形如 /xxx/?a=b&c=d 转换成对象 {path:'/xxx/',params:{a:'b',c:'d'}}
//1. /xxx/a.b.c.html?a=b&c=d path /xxx/a.b.c.html
//2. /xxx/?a=b&c=d path /xxx/
//3. /xxx/#?a=b => path /xxx/
//4. /xxx/index.html# => path /xxx/index.html
//5. /xxx/index.html => path /xxx/index.html
//6. /xxx/# => path /xxx/
//7. a=b&c=d => path ''
//8. /s?src=b# => path /s params:{src:'b'}
//9. a=YT3O0sPH1No= => path '' params:{a:'YT3O0sPH1No='}
//10.a=YT3O0sPH1No===&b=c => path '' params:{a:'YT3O0sPH1No===',b:'c'}
//11. ab?a&b => path ab params:{a:'',b:''}
//12. a=b&c => path '' params:{a:'b',c:''}
//13. =abc => path '=abc'
//14. ab= => path '' params:{ab:''}
//15. a&b => path '' params:{a:'',b:''}
let r = Magix_PathToObjCache.get(path),
pathname;
if (!r) {
Magix_ParamsObjectTemp = {};
pathname = path.replace(Magix_PathTrimParamsReg, G_EMPTY);
if (path == pathname && Magix_IsParam.test(pathname)) pathname = G_EMPTY; //考虑 YT3O0sPH1No= base64后的pathname
path.replace(pathname, G_EMPTY).replace(Magix_ParamsReg, Magix_ParamsFn);
Magix_PathToObjCache.set(path, r = {
a: pathname,
b: Magix_ParamsObjectTemp
});
}
return {
path: r.a,
params: { ...r.b }
};
};
/**
* 转换成字符串路径
* @param {String} path 路径
* @param {Object} params 参数对象
* @param {Object} [keo] 保留空白值的对象
* @return {String} 字符串路径
* @example
* let str = Magix.toUri('/xxx/',{a:'b',c:'d'});
* // str == /xxx/?a=b&c=d
*
* let str = Magix.toUri('/xxx/',{a:'',c:2});
*
* // str == /xxx/?a=&c=2
*
* let str = Magix.toUri('/xxx/',{a:'',c:2},{c:1});
*
* // str == /xxx/?c=2
* let str = Magix.toUri('/xxx/',{a:'',c:2},{a:1,c:1});
*
* // str == /xxx/?a=&c=2
*/
let G_ToUri = (path, params, keo) => {
let arr = [], v, p, f;
for (p in params) {
v = params[p] + G_EMPTY;
if (v || G_Has(keo, p)) {
v = encodeURIComponent(v);
arr.push(f = p + '=' + v);
}
}
if (f) {
path += (path && (~path.indexOf('?') ? '&' : '?')) + arr.join('&');
}
return path;
};
let G_ToMap = (list, key) => {
let e, map = {},
l;
if (list) {
for (e of list) {
map[(key && e) ? e[key] : e] = key ? e : (map[e] | 0) + 1; //对于简单数组,采用累加的方式,以方便知道有多少个相同的元素
}
}
return map;
};
let G_ParseCache = new G_Cache();
let G_ParseExpr = (expr, data, result) => {
if (G_ParseCache.has(expr)) {
result = G_ParseCache.get(expr);
} else {
//jshint evil:true
result = G_ToTry(Function(`return ${expr}`));
if (expr.indexOf(G_SPLITER) > -1) {
G_TranslateData(data, result);
} else {
G_ParseCache.set(expr, result);
}
}
if (DEBUG) {
result = Safeguard(result);
}
return result;
};
let CallIndex = 0;
let CallList = [];
let CallBreakTime = 48;
let StartCall = () => {
let last = G_Now(),
next;
while (1) {
next = CallList[CallIndex - 1];
if (next) {
next.apply(CallList[CallIndex], CallList[CallIndex + 1]);
CallIndex += 3;
if (G_Now() - last > CallBreakTime &&
CallList.length > CallIndex) {
setTimeout(StartCall);
console.log(`[CF] take a break of ${CallList.length} at ${CallIndex}`);
break;
}
} else {
CallList.length = CallIndex = 0;
break;
}
}
};
let CallFunction = (fn, args, context) => {
CallList.push(fn, context, args);
if (!CallIndex) {
CallIndex = 1;
setTimeout(StartCall);
}
};
let Mark = (host, key) => {
let deletedKey = G_SPLITER + '$a';
let markObjectKey = G_SPLITER + '$b';
let sign;
if (!host[deletedKey]) {
let markHost = host[markObjectKey] || (host[markObjectKey] = {});
if (!markHost.hasOwnProperty(key)) {
markHost[key] = 0;
}
sign = ++markHost[key];
}
return () => {
let temp = host[markObjectKey];
return temp && sign === temp[key];
}
};
let Unmark = host => {
host[G_SPLITER + '$b'] = 0;
host[G_SPLITER + '$a'] = 1;
};
let EventDefaultOptions = {
bubbles: true,
cancelable: true
};
let DispatchEvent = (element, type, data) => {
let e = new Event(type, EventDefaultOptions);
G_Assign(e, data);
element.dispatchEvent(e);
};
/**
* Magix对象,提供常用方法
* @name Magix
* @namespace
*/
let Magix = {
/**
* @lends Magix
*/
mark: Mark,
unmark: Unmark,
dispatch: DispatchEvent,
task: CallFunction,
/**
* 设置或获取配置信息
* @param {Object} cfg 初始化配置参数对象
* @param {String} cfg.defaultView 默认加载的view
* @param {String} cfg.defaultPath 当无法从地址栏取到path时的默认值。比如使用hash保存路由信息,而初始进入时并没有hash,此时defaultPath会起作用
* @param {Object} cfg.routes path与view映射关系表
* @param {String} cfg.unmatchView 在routes里找不到匹配时使用的view,比如显示404
* @param {String} cfg.rootId 根view的id
* @param {Array} cfg.exts 需要加载的扩展
* @param {Function} cfg.error 发布版以try catch执行一些用户重写的核心流程,当出错时,允许开发者通过该配置项进行捕获。注意:您不应该在该方法内再次抛出任何错误!
* @example
* Magix.config({
* rootId:'J_app_main',
* defaultView:'app/views/layouts/default',//默认加载的view
* defaultPath:'/home',
* routes:{
* "/home":"app/views/layouts/default"
* }
* });
*
*
* let config = Magix.config();
*
* console.log(config.rootId);
*
* // 可以多次调用该方法,除内置的配置项外,您也可以缓存一些数据,如
* Magix.config({
* user:'彳刂'
* });
*
* console.log(Magix.config('user'));
*/
config(cfg, r) {
r = Magix_Cfg;
if (cfg) {
if (G_IsObject(cfg)) {
r = G_Assign(r, cfg);
} else {
r = r[cfg];
}
}
return r;
},
/**
* 应用初始化入口
* @function
* @param {Object} [cfg] 配置信息对象,更多信息请参考Magix.config方法
* @return {Object} 配置信息对象
* @example
* Magix.boot({
* rootId:'J_app_main'
* });
*
*/
boot(cfg) {
G_Assign(Magix_Cfg, cfg); //先放到配置信息中,供ini文件中使用
G_Require(Magix_Cfg.ini, I => {
G_Assign(Magix_Cfg, I, cfg);
G_Require(Magix_Cfg.exts, () => {
Router.on(G_CHANGED, Dispatcher_NotifyChange);
State.on(G_CHANGED, Dispatcher_NotifyChange);
Magix_Booted = 1;
Router_Bind();
});
});
},
/**
* 把列表转化成hash对象
* @param {Array} list 源数组
* @param {String} [key] 以数组中对象的哪个key的value做为hash的key
* @return {Object}
* @example
* let map = Magix.toMap([1,2,3,5,6]);
* //=> {1:1,2:1,3:1,4:1,5:1,6:1}
*
* let map = Magix.toMap([{id:20},{id:30},{id:40}],'id');
* //=>{20:{id:20},30:{id:30},40:{id:40}}
*
* console.log(map['30']);//=> {id:30}
* //转成对象后不需要每次都遍历数组查询
*/
toMap: G_ToMap,
/**
* 以try cache方式执行方法,忽略掉任何异常
* @function
* @param {Array} fns 函数数组
* @param {Array} [args] 参数数组
* @param {Object} [context] 在待执行的方法内部,this的指向
* @return {Object} 返回执行的最后一个方法的返回值
* @example
* let result = Magix.toTry(function(){
* return true
* });
*
* // result == true
*
* let result = Magix.toTry(function(){
* throw new Error('test');
* });
*
* // result == undefined
*
* let result = Magix.toTry([function(){
* throw new Error('test');
* },function(){
* return true;
* }]);
*
* // result == true
*
* //异常的方法执行时,可以通过Magix.config中的error来捕获,如
*
* Magix.config({
* error:function(e){
* console.log(e);//在这里可以进行错误上报
* }
* });
*
* let result = Magix.toTry(function(a1,a2){
* return a1 + a2;
* },[1,2]);
*
* // result == 3
* let o={
* title:'test'
* };
* let result = Magix.toTry(function(){
* return this.title;
* },null,o);
*
* // result == 'test'
*/
toTry: G_ToTry,
/**
* 转换成字符串路径
* @function
* @param {String} path 路径
* @param {Object} params 参数对象
* @param {Object} [keo] 保留空白值的对象
* @return {String} 字符串路径
* @example
* let str = Magix.toUrl('/xxx/',{a:'b',c:'d'});
* // str == /xxx/?a=b&c=d
*
* let str = Magix.toUrl('/xxx/',{a:'',c:2});
*
* // str==/xxx/?a=&c=2
*
* let str = Magix.toUrl('/xxx/',{a:'',c:2},{c:1});
*
* // str == /xxx/?c=2
* let str = Magix.toUrl('/xxx/',{a:'',c:2},{a:1,c:1});
*
* // str == /xxx/?a=&c=2
*/
toUrl: G_ToUri,
/**
* 把路径字符串转换成对象
* @function
* @param {String} path 路径字符串
* @return {Object} 解析后的对象
* @example
* let obj = Magix.parseUrl('/xxx/?a=b&c=d');
* // obj = {path:'/xxx/',params:{a:'b',c:'d'}}
*/
parseUrl: G_ParseUri,
/*
* 路径
* @function
* @param {String} url 参考地址
* @param {String} part 相对参考地址的片断
* @return {String}
* @example
* http://www.a.com/a/b.html?a=b#!/home?e=f / => http://www.a.com/
* http://www.a.com/a/b.html?a=b#!/home?e=f ./ =>http://www.a.com/a/
* http://www.a.com/a/b.html?a=b#!/home?e=f ../../ => http://www.a.com/
* http://www.a.com/a/b.html?a=b#!/home?e=f ./../ => http://www.a.com/
*/
//path: G_Path,
/**
* 把src对象的值混入到aim对象上
* @function
* @param {Object} aim 要mix的目标对象
* @param {Object} src mix的来源对象
* @example
* let o1={
* a:10
* };
* let o2={
* b:20,
* c:30
* };
*
* Magix.mix(o1,o2);//{a:10,b:20,c:30}
*
*
* @return {Object}
*/
mix: G_Assign,
/**
* 检测某个对象是否拥有某个属性
* @function
* @param {Object} owner 检测对象
* @param {String} prop 属性
* @example
* let obj={
* key1:undefined,
* key2:0
* }
*
* Magix.has(obj,'key1');//true
* Magix.has(obj,'key2');//true
* Magix.has(obj,'key3');//false
*
*
* @return {Boolean} 是否拥有prop属性
*/
has: G_Has,
/**
* 获取对象的keys
* @param {Object} object 获取key的对象
* @type {Array}
* @beta
* @module linkage|router
* @example
* let o = {
* a:1,
* b:2,
* test:3
* };
* let keys = Magix.keys(o);
*
* // keys == ['a','b','test']
* @return {Array}
*/
keys: G_Keys,
/**
* 判断一个节点是否在另外一个节点内,如果比较的2个节点是同一个节点,也返回true
* @function
* @param {String|HTMLElement} node节点或节点id
* @param {String|HTMLElement} container 容器
* @example
* let root = $('html');
* let body = $('body');
*
* let r = Magix.inside(body[0],root[0]);
*
* // r == true
*
* let r = Magix.inside(root[0],body[0]);
*
* // r == false
*
* let r = Magix.inside(root[0],root[0]);
*
* // r == true
*
* @return {Boolean}
*/
inside: G_NodeIn,
/**
* document.getElementById的简写
* @param {String} id
* @return {HTMLElement|Null}
* @example
* // html
* // <div id="root"></div>
*
* let node = Magix.node('root');
*
* // node => div[id='root']
*
* // node是document.getElementById的简写
*/
node: G_GetById,
/**
* 应用样式
* @beta
* @module style
* @param {String} prefix 样式的名称前缀
* @param {String} css 样式字符串
* @example
* // 该方法配合magix-combine工具使用
* // 更多信息可参考magix-combine工具:https://github.com/thx/magix-combine
* // 样式问题可查阅这里:https://github.com/thx/magix-combine/issues/6
*
*/
applyStyle: View_ApplyStyle,
/**
* 返回全局唯一ID
* @function
* @param {String} [prefix] 前缀
* @return {String}
* @example
*
* let id = Magix.guid('mx-');
* // id maybe mx-7
*/
guid: G_Id,
use: G_Require,
Cache: G_Cache,
nodeId: IdIt,
use: G_Require,
guard: Safeguard
};
/**
* 多播事件对象
* @name Event
* @namespace
*/
let MEvent = {
/**
* @lends MEvent
*/
/**
* 触发事件
* @param {String} name 事件名称
* @param {Object} data 事件对象
* @param {Boolean} [remove] 事件触发完成后是否移除这个事件的所有监听
* @param {Boolean} [lastToFirst] 是否从后向前触发事件的监听列表
*/
fire(name, data, remove, lastToFirst) {
let key = G_SPLITER + name,
me = this,
list = me[key],
end, len, idx, t;
if (!data) data = {};
data.type = name;
if (list) {
end = list.length;
len = end - 1;
while (end--) {
idx = lastToFirst ? end : len - end;
t = list[idx];
if (t.f) {
t.x = 1;
G_ToTry(t.f, data, me);
t.x = G_EMPTY;
} else if (!t.x) {
list.splice(idx, 1);
len--;
}
}
}
list = me[`on${name}`];
if (list) G_ToTry(list, data, me);
if (remove) me.off(name);
return me;
},
/**
* 绑定事件
* @param {String} name 事件名称
* @param {Function} fn 事件处理函数
* @example
* let T = Magix.mix({},Magix.Event);
* T.on('done',function(e){
* alert(1);
* });
* T.on('done',function(e){
* alert(2);
* T.off('done',arguments.callee);
* });
* T.fire('done',{data:'test'});
* T.fire('done',{data:'test2'});
*/
on(name, f) {
let me = this;
let key = G_SPLITER + name;
let list = me[key] || (me[key] = []);
list.push({
f
});
return me;
},
/**
* 解除事件绑定
* @param {String} name 事件名称
* @param {Function} [fn] 事件处理函数
*/
off(name, fn) {
let key = G_SPLITER + name,
me = this,
list = me[key],
t;
if (fn) {
if (list) {
for (t of list) {
if (t.f == fn) {
t.f = G_EMPTY;
break;
}
}
}
} else {
delete me[key];
delete me[`on${name}`];
}
return me;
}
};
Magix.Event = MEvent;
let State_AppData = {};
let State_AppDataKeyRef = {};
let State_ChangedKeys = {};
let State_DataIsChanged = 0;
let State_DataWhereSet = {};
let State_IsObserveChanged = (view, keys, r) => {
let oKeys = view['$os'], ok;
if (oKeys) {
for (ok of oKeys) {
r = G_Has(keys, ok);
if (r) break;
}
}
return r;
};
let SetupKeysRef = keys => {
keys = (keys + G_EMPTY).split(',');
for (let key of keys) {
if (G_Has(State_AppDataKeyRef, key)) {
State_AppDataKeyRef[key]++;
} else {
State_AppDataKeyRef[key] = 1;
}
}
return keys;
};
let TeardownKeysRef = keys => {
let key, v;
for (key of keys) {
if (G_Has(State_AppDataKeyRef, key)) {
v = --State_AppDataKeyRef[key];
if (!v) {
delete State_AppDataKeyRef[key];
delete State_AppData[key];
if (DEBUG) {
delete State_DataWhereSet[key];
}
}
}
}
};
if (DEBUG) {
setTimeout(() => {
Router.on('changed', () => {
setTimeout(() => {
let keys = [];
let cls = [];
for (let p in State_DataWhereSet) {
if (!State_AppDataKeyRef[p]) {
cls.push(p);
keys.push('key:"' + p + '" set by page:"' + State_DataWhereSet[p] + '"');
}
}
if (keys.length) {
console.warn('beware! Remember to clean ' + keys + ' in {Magix.State} Clean use view.mixins like mixins:[Magix.State.clean("' + cls + '")]');
}
}, 200);
});
}, 0);
}
if (DEBUG) {
let Started = 0;
let NotifyList = [];
let NotifyTimer = 0;
let Notify = () => {
let locker = {};
for (let n of NotifyList) {
if (!locker[n.msg]) {
console.warn(n.msg);
locker[n.msg] = 1;
}
}
NotifyList.length = 0;
Started = 0;
};
var ClearNotify = key => {
for (let i = NotifyList.length; i--;) {
let n = NotifyList[i];
if (n.key == key) {
NotifyList.splice(i, 1);
}
}
};
var DelayNotify = (key, msg) => {
clearTimeout(NotifyTimer);
Started = 0;
NotifyList.push({
key,
msg
});
if (!Started) {
Started = 1;
NotifyTimer = setTimeout(Notify, 500);
}
};
}
/**
* 可观察的内存数据对象
* @name State
* @namespace
* @borrows Event.on as on
* @borrows Event.fire as fire
* @borrows Event.off as off
* @beta
* @module router
*/
let State = {
/**
* @lends State
*/
/**
* 从Magix.State中获取数据
* @param {String} [key] 数据key
* @return {Object}
*/
get(key) {
let r = key ? State_AppData[key] : State_AppData;
if (DEBUG) {
if (key && Magix_Booted) {
let loc = Router.parse();
if (G_Has(State_DataWhereSet, key) && State_DataWhereSet[key] != loc.path) {
console.warn('beware! You get state:"{Magix.State}.' + key + '" where it set by page:' + State_DataWhereSet[key]);
}
}
r = Safeguard(r, dataKey => {
if (Magix_Booted) {
let loc = Router.parse();
if (G_Has(State_DataWhereSet, dataKey) && State_DataWhereSet[dataKey] != loc.path) {
console.warn('beware! You get state:"{Magix.State}.' + dataKey + '" where it set by page:' + State_DataWhereSet[dataKey]);
}
}
}, (path, value) => {
let sub = key ? key : path;
DelayNotify(sub, 'beware! You direct modify "{Magix.State}.' + sub + '" You should call Magix.State.set() and Magix.State.digest() to notify other views {Magix.State} changed');
});
}
return r;
},
/**
* 设置数据
* @param {Object} data 数据对象
*/
set(data, unchanged) {
State_DataIsChanged = G_Set(data, State_AppData, State_ChangedKeys, unchanged) || State_DataIsChanged;
if (DEBUG && Magix_Booted) {
let loc = Router.parse();
for (let p in data) {
State_DataWhereSet[p] = loc.path;
}
}
return this;
},
/**
* 检测数据变化,如果有变化则派发changed事件
* @param {Object} data 数据对象
*/
digest(data, unchanged) {
if (data) {
State.set(data, unchanged);
}
if (State_DataIsChanged) {
if (DEBUG) {
for (let p in State_ChangedKeys) {
ClearNotify(p);
}
}
State_DataIsChanged = 0;
//防止在change事件中再次digest造成的死循环
let keys = G_Assign({}, State_ChangedKeys);
State_ChangedKeys = {};
this.fire(G_CHANGED, {
keys
});
}
},
/**
* 获取当前数据与上一次数据有哪些变化
* @return {Object}
*/
diff() {
return State_ChangedKeys;
},
setup(keys) {
SetupKeysRef(keys);
},
teardown(keys) {
TeardownKeysRef(keys);
},
/**
* 清除数据,该方法需要与view绑定,写在view的mixins中,如mixins:[Magix.Sate.clean('user,permission')]
* @param {String} keys 数据key
*/
clean(keys) {
if (DEBUG) {
let called = false;
setTimeout(() => {
if (!called) {
throw new Error('Magix.State.clean only used in View.mixins like mixins:[Magix.State.clean("p1,p2,p3")]');
}
}, 1000);
return {
'\x1e': keys,
ctor() {
let me = this;
called = true;
keys = SetupKeysRef(keys);
me.on('destroy', () => {
TeardownKeysRef(keys);
});
}
};
}
return {
ctor() {
keys = SetupKeysRef(keys);
this.on('destroy', () => TeardownKeysRef(keys));
}
};
},
...MEvent
/**
* 当State中的数据有改变化后触发
* @name State.changed
* @event
* @param {Object} e 事件对象
* @param {Object} e.keys 包含哪些数据变化的key集合
*/
};
Magix.State = State;
//let G_IsFunction = $.isFunction;
let Router_VIEW = 'view';
let Router_HrefCache = new G_Cache();
let Router_ChgdCache = new G_Cache();
let Router_WinLoc = G_WINDOW.location;
let Router_LastChanged;
let Router_Silent = 0;
let Router_LLoc = {
query: {},
params: {},
href: G_EMPTY
};
let Router_TrimHashReg = /(?:^.*\/\/[^\/]+|#.*$)/gi;
let Router_TrimQueryReg = /^[^#]*#?!?/;
function GetParam(key, defaultValue) {
return this[G_PARAMS][key] || defaultValue !== G_Undefined && defaultValue || G_EMPTY;
}
let Router_Edge = 0;
let Router_Hashbang = G_HashKey + '!';
let Router_UpdateHash = (path, replace) => {
path = Router_Hashbang + path;
if (replace) {
Router_WinLoc.replace(path);
} else {
Router_WinLoc.hash = path;
}
};
let Router_Update = (path, params, loc, replace, silent, lQuery) => {
path = G_ToUri(path, params, lQuery);
if (path != loc.srcHash) {
Router_Silent = silent;
Router_UpdateHash(path, replace);
}
};
let Router_Bind = () => {
let lastHash = Router_Parse().srcHash;
let newHash, suspend;
G_DOMEventLibBind(G_WINDOW, 'hashchange', (e, loc, resolve) => {
if (suspend) {
return;
}
loc = Router_Parse();
newHash = loc.srcHash;
if (newHash != lastHash) {
resolve = () => {
e.p = 1;
lastHash = newHash;
suspend = G_EMPTY;
Router_UpdateHash(newHash);
Router_Diff();
};
e = {
reject() {
e.p = 1;
suspend = G_EMPTY;
Router_UpdateHash(lastHash);
},
resolve,
prevent() {
suspend = 1;
}
};
Router.fire(G_CHANGE, e);
if (!suspend && !e.p) {
resolve();
}
}
});
G_DOMEventLibBind(G_WINDOW, 'beforeunload', (e, te, msg) => {
e = e || G_WINDOW.event;
te = {};
Router.fire(G_PAGE_UNLOAD, te);
if ((msg = te.msg)) {
//chrome use e.returnValue and ie use return value
if (e) e.returnValue = msg;
return msg;
}
});
Router_Diff();
};
let Router_PNR_Routers, Router_PNR_UnmatchView, /*Router_PNR_IsFun,*/
Router_PNR_DefaultView, Router_PNR_DefaultPath;
let Router_PNR_Rewrite;
let DefaultTitle = G_DOCUMENT.title;
let Router_AttachViewAndPath = (loc, view) => {
if (!Router_PNR_Routers) {
Router_PNR_Routers = Magix_Cfg.routes || {};
Router_PNR_UnmatchView = Magix_Cfg.unmatchView;
Router_PNR_DefaultView = Magix_Cfg.defaultView;
Router_PNR_DefaultPath = Magix_Cfg.defaultPath || '/';
//Router_PNR_IsFun = G_IsFunction(Router_PNR_Routers);
//if (!Router_PNR_IsFun && !Router_PNR_Routers[Router_PNR_DefaultPath]) {
// Router_PNR_Routers[Router_PNR_DefaultPath] = Router_PNR_DefaultView;
//}
Router_PNR_Rewrite = Magix_Cfg.rewrite;
//if (!G_IsFunction(Router_PNR_Rewrite)) {
// Router_PNR_Rewrite = G_NULL;
//}
}
if (!loc[Router_VIEW]) {
let path = loc.hash[G_PATH] || (Router_Edge && loc.query[G_PATH]) || Router_PNR_DefaultPath;
if (Router_PNR_Rewrite) {
path = Router_PNR_Rewrite(path, loc[G_PARAMS], Router_PNR_Routers);
}
//if (Router_PNR_IsFun) {
// view = Router_PNR_Routers.call(Magix_Cfg, path, loc);
//} else {
view = Router_PNR_Routers[path] || Router_PNR_UnmatchView || Router_PNR_DefaultView;
//}
loc[G_PATH] = path;
loc[Router_VIEW] = view;
if (G_IsObject(view)) {
if (DEBUG) {
if (!view.view) {
console.error(path, ' config missing view!', view);
}
}
G_Assign(loc, view);
}
}
};
let Router_GetChged = (oldLocation, newLocation) => {
let oKey = oldLocation.href;
let nKey = newLocation.href;
let tKey = oKey + G_SPLITER + nKey;
let result = Router_ChgdCache.get(tKey);
if (!result) {
let hasChanged, rps;
result = {
params: rps = {},
//isParam: Router_IsParam,
//location: newLocation,
force: !oKey //是否强制触发的changed,对于首次加载会强制触发一次
};
let oldParams = oldLocation[G_PARAMS],
newParams = newLocation[G_PARAMS],
tArr = G_Keys(oldParams).concat(G_Keys(newParams)),
key;
let setDiff = key => {
let from = oldParams[key],
to = newParams[key];
if (from != to) {
rps[key] = {
from,
to
};
hasChanged = 1;
}
};
for (key of tArr) {
setDiff(key);
}
oldParams = oldLocation;
newParams = newLocation;
rps = result;
setDiff(G_PATH);
setDiff(Router_VIEW);
Router_ChgdCache.set(tKey, result = {
a: hasChanged,
b: result
});
}
return result;
};
let Router_Parse = href => {
href = href || Router_WinLoc.href;
let result = Router_HrefCache.get(href),
srcQuery, srcHash, query, hash, params;
if (!result) {
srcQuery = href.replace(Router_TrimHashReg, G_EMPTY);
srcHash = href.replace(Router_TrimQueryReg, G_EMPTY);
query = G_ParseUri(srcQuery);
hash = G_ParseUri(srcHash);
params = {
...query[G_PARAMS]
, ...hash[G_PARAMS]
};
result = {
get: GetParam,
href,
srcQuery,
srcHash,
query,
hash,
params
};
if (Magix_Booted) {
Router_AttachViewAndPath(result);
Router_HrefCache.set(href, result);
}
if (DEBUG) {
result.params = Safeguard(result.params);
result = Safeguard(result);
}
}
return result;
};
let Router_Diff = () => {
let location = Router_Parse();
let changed = Router_GetChged(Router_LLoc, Router_LLoc = location);
if (!Router_Silent && changed.a) {
Router_LastChanged = changed.b;
if (Router_LastChanged[G_PATH]) {
G_DOCUMENT.title = location.title || DefaultTitle;
}
Router.fire(G_CHANGED, Router_LastChanged );
}
Router_Silent = 0;
if (DEBUG) {
Router_LastChanged = Safeguard(Router_LastChanged);
}
return Router_LastChanged;
};
//let PathTrimFileParamsReg=/(\/)?[^\/]*[=#]$/;//).replace(,'$1').replace(,EMPTY);
//let PathTrimSearch=/\?.*$/;
/**
* 路由对象,操作URL
* @name Router
* @namespace
* @borrows Event.on as on
* @borrows Event.fire as fire
* @borrows Event.off as off
* @beta
* @module router
*/
let Router = {
/**
* @lends Router
*/
/**
* 解析href的query和hash,默认href为location.href
* @param {String} [href] href
* @return {Object} 解析的对象
*/
parse: Router_Parse,
/**
* 根据location.href路由并派发相应的事件,同时返回当前href与上一个href差异对象
* @example
* let diff = Magix.Router.diff();
* if(diff.params.page || diff.params.rows){
* console.log('page or rows changed');
* }
*/
diff: Router_Diff,
/**
* 导航到新的地址
* @param {Object|String} pn path或参数字符串或参数对象
* @param {String|Object} [params] 参数对象
* @param {Boolean} [replace] 是否替换当前历史记录
* @example
* let R = Magix.Router;
* R.to('/list?page=2&rows=20');//改变path和相关的参数,地址栏上的其它参数会进行丢弃,不会保留
* R.to('page=2&rows=20');//只修改参数,地址栏上的其它参数会保留
* R.to({//通过对象修改参数,地址栏上的其它参数会保留
* page:2,
* rows:20
* });
* R.to('/list',{//改变path和相关参数,丢弃地址栏上原有的其它参数
* page:2,
* rows:20
* });
*
* //凡是带path的修改地址栏,都会把原来地址栏中的参数丢弃
* 传递对象,内部对value会进行encodeURIComponent操作,传递字符串需要开发者自己处理。
* R.to({
* page:2,
* rows:20
* },null,true);//使用location.replace操作hash
* R.to({
* page:2,
* rows:20
* },null,null,true);//静默更新url但不派发事件
*/
to(pn, params, replace, silent) {
if (!params && G_IsObject(pn)) {
params = pn;
pn = G_EMPTY;
}
let temp = G_ParseUri(pn);
let tParams = temp[G_PARAMS];
let tPath = temp[G_PATH];
let lPath = Router_LLoc[G_PATH]; //历史路径
let lParams = Router_LLoc[G_PARAMS];
let lQuery = Router_LLoc.query[G_PARAMS];
G_Assign(tParams, params); //把路径中解析出来的参数与用户传递的参数进行合并
if (tPath) { //设置路径带参数的形式,如:/abc?q=b&c=e或不带参数 /abc
//tPath = G_Path(lPath, tPath);
if (!Router_Edge) { //pushState不用处理
for (lPath in lQuery) { //未出现在query中的参数设置为空
if (!G_Has(tParams, lPath)) tParams[lPath] = G_EMPTY;
}
}
} else if (lParams) { //只有参数,如:a=b&c=d
tPath = lPath; //使用历史路径
tParams = { ...lParams, ...tParams }; //复制原来的参数,合并新的参数
}
Router_Update(tPath, tParams, Router_LLoc, replace, silent, lQuery);
},
...MEvent
/**
* 当location.href有改变化后触发
* @name Router.changed
* @event
* @param {Object} e 事件对象
* @param {Object} e.path 如果path发生改变时,记录从(from)什么值变成(to)什么值的对象
* @param {Object} e.view 如果view发生改变时,记录从(from)什么值变成(to)什么值的对象
* @param {Object} e.params 如果参数发生改变时,记录从(from)什么值变成(to)什么值的对象
* @param {Boolean} e.force 标识是否是第一次强制触发的changed,对于首次加载完Magix,会强制触发一次changed
*/
};
Magix.Router = Router;
let Dispatcher_UpdateTag = 0;
/**
* 通知当前vframe,地址栏发生变化
* @param {Vframe} vframe vframe对象
* @private
*/
let Dispatcher_Update = (vframe, stateKeys, view, isChanged, cs, c, promise) => {
if (vframe && vframe['$a'] != Dispatcher_UpdateTag &&
(view = vframe['$v']) &&
view['$a'] > 1) { //存在view时才进行广播,对于加载中的可在加载完成后通过调用view.location拿到对应的G_WINDOW.location.href对象,对于销毁的也不需要广播
isChanged = stateKeys ? State_IsObserveChanged(view, stateKeys) : View_IsObserveChanged(view);
/**
* 事件对象
* @type {Object}
* @ignore
*/
/*let args = {
location: RefLoc,
changed: RefG_LocationChanged,*/
/**
* 阻止向所有的子view传递
* @ignore
*/
/* prevent: function() {
args.cs = EmptyArr;
},*/
/**
* 向特定的子view传递
* @param {Array} c 子view数组
* @ignore
*/
/*to: function(c) {
c = (c + EMPTY).split(COMMA);
args.cs = c;
}
};*/
if (isChanged) { //检测view所关注的相应的参数是否发生了变化
promise = view['$b']();
}
if (!promise || !promise.then) {
promise = Vframe_Promise;
}
promise.then(() => {
cs = vframe.children();
for (c of cs) {
Dispatcher_Update(Vframe_Vframes[c], stateKeys );
}
});
}
};
/**
* 向vframe通知地址栏发生变化
* @param {Object} e 事件对象
* @param {Object} e.location G_WINDOW.location.href解析出来的对象
* @private
*/
let Dispatcher_NotifyChange = (e, vf, view) => {
vf = Vframe_Root();
if ((view = e[Router_VIEW])) {
vf.mountView(view.to);
} else {
Dispatcher_UpdateTag = G_COUNTER++;
Dispatcher_Update(vf , e.keys );
}
};
let Vframe_RootVframe;
let Vframe_GlobalAlter;
let Vframe_Vframes = {};
let Vframe_Promise = { then: f => f() };
let Vframe_NotifyCreated = vframe => {
if (!vframe['$b'] && !vframe['$d'] && vframe['$cc'] == vframe['$rc']) { //childrenCount === readyCount
if (!vframe['$cr']) { //childrenCreated
vframe['$cr'] = 1; //childrenCreated
vframe['$ca'] = 0; //childrenAlter
vframe.fire('created'); //不在view上派发事件,如果view需要绑定,则绑定到owner上,view一般不用该事件,如果需要这样处理:this.owner.oncreated=function(){};this.ondestroy=function(){this.owner.off('created')}
}
let { id, pId } = vframe, p = Vframe_Vframes[pId];
if (p && !G_Has(p['$e'], id)) { //readyChildren
p['$e'][id] = 1; //readyChildren
p['$rc']++; //readyCount
Vframe_NotifyCreated(p);
}
}
};
let Vframe_NotifyAlter = (vframe, e) => {
if (!vframe['$ca'] && vframe['$cr']) { //childrenAlter childrenCreated 当前vframe触发过created才可以触发alter事件
vframe['$cr'] = 0; //childrenCreated
vframe['$ca'] = 1; //childreAleter
vframe.fire('alter', e);
let { id, pId } = vframe, p = Vframe_Vframes[pId];
//let vom = vframe.owner;
if (p && G_Has(p['$e'], id)) { //readyMap
p['$rc']--; //readyCount
delete p['$e'][id]; //readyMap
Vframe_NotifyAlter(p, e);
}
}
};
let Vframe_TranslateQuery = (pId, src, params, pVf) => {
pVf = Vframe_Vframes[pId];
pVf = pVf && pVf['$v'];
pVf = pVf ? pVf['$d']['$a'] : {};
if (src.indexOf(G_SPLITER) > 0) {
G_TranslateData(pVf, params);
}
return pVf;
};
/**
* 获取根vframe;
* @return {Vframe}
* @private
*/
let Vframe_Root = (rootId, e) => {
if (!Vframe_RootVframe) {
/*
尽可能的延迟配置,防止被依赖时,配置信息不正确
*/
G_DOCBODY = G_DOCUMENT.body;
rootId = Magix_Cfg.rootId;
e = G_GetById(rootId);
if (!e) {
G_DOCBODY.id = rootId;
}
Vframe_RootVframe = new Vframe(rootId);
}
return Vframe_RootVframe;
};
let Vframe_AddVframe = (id, vframe) => {
if (!G_Has(Vframe_Vframes, id)) {
Vframe_Vframes[id] = vframe;
Vframe.fire('add', {
vframe
});
id = G_GetById(id);
if (id) id.vframe = vframe;
}
};
let Vframe_RunInvokes = (vf, list, o) => {
list = vf['$f']; //invokeList
while (list.length) {
o = list.shift();
if (!o.r) { //remove
vf.invoke(o.n, o.a); //name,arguments
}
delete list[o.k]; //key
}
};
let Vframe_Cache = [];
let Vframe_RemoveVframe = (id, fcc, vframe) => {
vframe = Vframe_Vframes[id];
if (vframe) {
delete Vframe_Vframes[id];
Vframe.fire('remove', {
vframe,
fcc //fireChildrenCreated
});
if (DEBUG) {
let nodes = G_DOCUMENT.querySelectorAll('#' + id);
if (nodes.length > 1) {
Magix_Cfg.error(Error(`remove vframe error. dom id:"${id}" duplicate`));
}
}
id = G_GetById(id);
if (id) {
id['$b'] = 0;
id.vframe = 0;
id['$a'] = 0;
}
}
};
/**
* Vframe类
* @name Vframe
* @class
* @constructor
* @borrows Event.on as on
* @borrows Event.fire as fire
* @borrows Event.off as off
* @borrows Event.on as #on
* @borrows