UNPKG

magix

Version:

view manager framewrok

1,522 lines (1,520 loc) 167 kB
//#snippet; //#uncheck = jsThis,jsLoop; //#exclude = loader,allProcessor; /*!3.8.16 Licensed MIT*/ /* author:kooboy_li@163.com loader:kissy 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 */ KISSY.add('magix', function (S, SE, DOM, SNode) { if (typeof DEBUG == 'undefined') window.DEBUG = true; var $ = S.all; var G_IsObject = S.isObject; var G_IsArray = S.isArray; var G_COUNTER = 0; var G_EMPTY = ''; var G_EMPTY_ARRAY = []; var G_COMMA = ','; var G_NULL = null; var G_WINDOW = window; var G_Undefined = void G_COUNTER; var G_DOCUMENT = document; var GA = G_DOCUMENT.documentElement.getAttribute; var G_GetAttribute = function (node, attr) { return GA.call(node, attr); }; var G_DOC = $(G_DOCUMENT); var Timeout = G_WINDOW.setTimeout; var G_CHANGED = 'changed'; var G_CHANGE = 'change'; var G_PAGE_UNLOAD = 'pageunload'; var G_VALUE = 'value'; var G_Tag_Key = 'mxs'; var G_Tag_Attr_Key = 'mxa'; var G_Tag_View_Key = 'mxv'; var G_HashKey = '#'; function G_NOOP() { } var JSONStringify = JSON.stringify; var G_DOCBODY; //initilize at vframe_root /* 关于spliter 出于安全考虑,使用不可见字符\u0000,然而,window手机上ie11有这样的一个问题:'\u0000'+"abc",结果却是一个空字符串,好奇特。 */ var G_SPLITER = '\x1e'; var Magix_StrObject = 'object'; var G_PROTOTYPE = 'prototype'; var G_PARAMS = 'params'; var G_PATH = 'path'; var G_MX_VIEW = 'mx-view'; // let Magix_PathRelativeReg = /\/\.(?:\/|$)|\/[^\/]+?\/\.{2}(?:\/|$)|\/\/+|\.{2}\//; // ./|/x/../|(b)/// // let Magix_PathTrimFileReg = /\/[^\/]*$/; // let Magix_ProtocalReg = /^(?:https?:)?\/\//i; var Magix_PathTrimParamsReg = /[#?].*$/; var Magix_ParamsReg = /([^=&?\/#]+)=?([^&#?]*)/g; var Magix_IsParam = /(?!^)=|&/; var G_Id = function (prefix) { return (prefix || 'mx_') + G_COUNTER++; }; var MxGlobalView = G_Id(); var Magix_Cfg = { rootId: G_Id(), defaultView: MxGlobalView, error: function (e) { throw e; } }; var G_GetById = function (id) { return typeof id == Magix_StrObject ? id : G_DOCUMENT.getElementById(id); }; var G_IsPrimitive = function (args) { return !args || typeof args != Magix_StrObject; }; var G_Set = function (newData, oldData, keys, unchanged) { var 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; }; var G_NodeIn = function (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; }; function G_Assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (G_Has(s, p)) t[p] = s[p]; } return t; } var G_Keys = function (obj, keys, p) { keys = []; for (p in obj) { if (G_Has(obj, p)) { keys.push(p); } } return keys; }; var Magix_HasProp = Magix_Cfg.hasOwnProperty; var Header = $('head'); var View_ApplyStyle = function (key, css) { if (DEBUG && G_IsArray(key)) { for (var 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); } } }; var IdIt = function (n) { return G_GetAttribute(n, 'id') || (n['$a'] = 1, n.id = G_Id()); }; var G_ToTry = function (fns, args, context, r, e) { args = args || G_EMPTY_ARRAY; if (!G_IsArray(fns)) fns = [fns]; if (!G_IsArray(args)) args = [args]; for (var _i = 0, fns_1 = fns; _i < fns_1.length; _i++) { e = fns_1[_i]; try { r = e && e.apply(context, args); } catch (x) { Magix_Cfg.error(x); } } return r; }; var G_Has = function (owner, prop) { return owner && Magix_HasProp.call(owner, prop); }; //false 0 G_NULL '' undefined var G_TranslateData = function (data, params) { var 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; }; var Magix_CacheSort = function (a, b) { return 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: function (key) { var me = this; var c = me.c; var 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: function (cb, ops, me, c, i) { me = this; c = me.c; for (var _i = 0, c_1 = c; _i < c_1.length; _i++) { i = c_1[_i]; cb(i.v, ops, me); } }, /** * 设置缓存 * @param {String} key 缓存的key * @param {Object} value 缓存的对象 */ set: function (okey, value) { var me = this; var c = me.c; var key = G_SPLITER + okey; var r = c[key]; var 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: function (k) { k = G_SPLITER + k; var c = this.c; var 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: function (k) { return G_Has(this.c, G_SPLITER + k); } }); var G_Require = function (name, fn) { S.use(name && (name + G_EMPTY), function (S) { var args = []; for (var _i = 1; _i < arguments.length; _i++) { args[_i - 1] = arguments[_i]; } if (fn) { CallFunction(fn, args, S); } }); }; var G_Extend = S.extend; var G_TargetMatchSelector = DOM.test; function G_DOMGlobalProcessor(e, d) { d = this; e.eventTarget = d.e; G_ToTry(d.f, e, d.v); } var G_DOMEventLibBind = function (node, type, cb, remove, scope) { if (scope) { SE[(remove ? 'un' : G_EMPTY) + "delegate"](node, type, cb, scope); } else { SE[remove ? 'detach' : 'on'](node, type, cb, scope); } }; var Safeguard = function (data) { return data; }; if (DEBUG && window.Proxy) { var ProxiesPool_1 = new Map(); Safeguard = function (data, getter, setter, root) { if (G_IsPrimitive(data)) { return data; } var build = function (prefix, o) { var key = getter + '\x01' + setter; var cached = ProxiesPool_1.get(o); if (cached && cached.key == key) { return cached.entity; } if (o['\x1e_sf_\x1e']) { return o; } var entity = new Proxy(o, { set: function (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: function (target, property) { if (property == '\x1e_sf_\x1e') { return true; } var 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_1.set(o, { key: key, entity: entity }); return entity; }; return build('', data); }; } var Magix_PathToObjCache = new G_Cache(); var Magix_Booted = 0; //let Magix_PathCache = new G_Cache(); var Magix_ParamsObjectTemp; var Magix_ParamsFn = function (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'}} */ var G_ParseUri = function (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:''} var 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: G_Assign({}, 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 */ var G_ToUri = function (path, params, keo) { var 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; }; var G_ToMap = function (list, key) { var e, map = {}, l; if (list) { for (var _i = 0, list_1 = list; _i < list_1.length; _i++) { e = list_1[_i]; map[(key && e) ? e[key] : e] = key ? e : (map[e] | 0) + 1; //对于简单数组,采用累加的方式,以方便知道有多少个相同的元素 } } return map; }; var G_ParseCache = new G_Cache(); var G_ParseExpr = function (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; }; var CallIndex = 0; var CallList = []; var CallBreakTime = 48; var StartCall = function () { var 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; } } }; var CallFunction = function (fn, args, context) { CallList.push(fn, context, args); if (!CallIndex) { CallIndex = 1; setTimeout(StartCall); } }; var Mark = function (host, key) { var deletedKey = G_SPLITER + '$a'; var markObjectKey = G_SPLITER + '$b'; var sign; if (!host[deletedKey]) { var markHost = host[markObjectKey] || (host[markObjectKey] = {}); if (!markHost.hasOwnProperty(key)) { markHost[key] = 0; } sign = ++markHost[key]; } return function () { var temp = host[markObjectKey]; return temp && sign === temp[key]; }; }; var Unmark = function (host) { host[G_SPLITER + '$b'] = 0; host[G_SPLITER + '$a'] = 1; }; var EventDefaultOptions = { bubbles: true, cancelable: true }; var DispatchEvent = function (element, type, data) { var e = new Event(type, EventDefaultOptions); G_Assign(e, data); element.dispatchEvent(e); }; /** * Magix对象,提供常用方法 * @name Magix * @namespace */ var 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: function (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: function (cfg) { G_Assign(Magix_Cfg, cfg); //先放到配置信息中,供ini文件中使用 G_Require(Magix_Cfg.ini, function (I) { G_Assign(Magix_Cfg, I, cfg); G_Require(Magix_Cfg.exts, function () { 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 */ var MEvent = { /** * @lends MEvent */ /** * 触发事件 * @param {String} name 事件名称 * @param {Object} data 事件对象 * @param {Boolean} [remove] 事件触发完成后是否移除这个事件的所有监听 * @param {Boolean} [lastToFirst] 是否从后向前触发事件的监听列表 */ fire: function (name, data, remove, lastToFirst) { var 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: function (name, f) { var me = this; var key = G_SPLITER + name; var list = me[key] || (me[key] = []); list.push({ f: f }); return me; }, /** * 解除事件绑定 * @param {String} name 事件名称 * @param {Function} [fn] 事件处理函数 */ off: function (name, fn) { var key = G_SPLITER + name, me = this, list = me[key], t; if (fn) { if (list) { for (var _i = 0, list_2 = list; _i < list_2.length; _i++) { t = list_2[_i]; if (t.f == fn) { t.f = G_EMPTY; break; } } } } else { delete me[key]; delete me["on" + name]; } return me; } }; Magix.Event = MEvent; var State_AppData = {}; var State_AppDataKeyRef = {}; var State_ChangedKeys = {}; var State_DataIsChanged = 0; var State_DataWhereSet = {}; var State_IsObserveChanged = function (view, keys, r) { var oKeys = view['$os'], ok; if (oKeys) { for (var _i = 0, oKeys_1 = oKeys; _i < oKeys_1.length; _i++) { ok = oKeys_1[_i]; r = G_Has(keys, ok); if (r) break; } } return r; }; var SetupKeysRef = function (keys) { keys = (keys + G_EMPTY).split(','); for (var _i = 0, keys_1 = keys; _i < keys_1.length; _i++) { var key = keys_1[_i]; if (G_Has(State_AppDataKeyRef, key)) { State_AppDataKeyRef[key]++; } else { State_AppDataKeyRef[key] = 1; } } return keys; }; var TeardownKeysRef = function (keys) { var key, v; for (var _i = 0, keys_2 = keys; _i < keys_2.length; _i++) { key = keys_2[_i]; 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(function () { Router.on('changed', function () { setTimeout(function () { var keys = []; var cls = []; for (var 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) { var Started_1 = 0; var NotifyList_1 = []; var NotifyTimer_1 = 0; var Notify_1 = function () { var locker = {}; for (var _i = 0, NotifyList_2 = NotifyList_1; _i < NotifyList_2.length; _i++) { var n = NotifyList_2[_i]; if (!locker[n.msg]) { console.warn(n.msg); locker[n.msg] = 1; } } NotifyList_1.length = 0; Started_1 = 0; }; var ClearNotify = function (key) { for (var i = NotifyList_1.length; i--;) { var n = NotifyList_1[i]; if (n.key == key) { NotifyList_1.splice(i, 1); } } }; var DelayNotify = function (key, msg) { clearTimeout(NotifyTimer_1); Started_1 = 0; NotifyList_1.push({ key: key, msg: msg }); if (!Started_1) { Started_1 = 1; NotifyTimer_1 = setTimeout(Notify_1, 500); } }; } /** * 可观察的内存数据对象 * @name State * @namespace * @borrows Event.on as on * @borrows Event.fire as fire * @borrows Event.off as off * @beta * @module router */ var State = G_Assign({ /** * @lends State */ /** * 从Magix.State中获取数据 * @param {String} [key] 数据key * @return {Object} */ get: function (key) { var r = key ? State_AppData[key] : State_AppData; if (DEBUG) { if (key && Magix_Booted) { var 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, function (dataKey) { if (Magix_Booted) { var 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]); } } }, function (path, value) { var 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: function (data, unchanged) { State_DataIsChanged = G_Set(data, State_AppData, State_ChangedKeys, unchanged) || State_DataIsChanged; if (DEBUG && Magix_Booted) { var loc = Router.parse(); for (var p in data) { State_DataWhereSet[p] = loc.path; } } return this; }, /** * 检测数据变化,如果有变化则派发changed事件 * @param {Object} data 数据对象 */ digest: function (data, unchanged) { if (data) { State.set(data, unchanged); } if (State_DataIsChanged) { if (DEBUG) { for (var p in State_ChangedKeys) { ClearNotify(p); } } State_DataIsChanged = 0; //防止在change事件中再次digest造成的死循环 var keys = G_Assign({}, State_ChangedKeys); State_ChangedKeys = {}; this.fire(G_CHANGED, { keys: keys }); } }, /** * 获取当前数据与上一次数据有哪些变化 * @return {Object} */ diff: function () { return State_ChangedKeys; }, setup: function (keys) { SetupKeysRef(keys); }, teardown: function (keys) { TeardownKeysRef(keys); }, /** * 清除数据,该方法需要与view绑定,写在view的mixins中,如mixins:[Magix.Sate.clean('user,permission')] * @param {String} keys 数据key */ clean: function (keys) { if (DEBUG) { var called_1 = false; setTimeout(function () { if (!called_1) { throw new Error('Magix.State.clean only used in View.mixins like mixins:[Magix.State.clean("p1,p2,p3")]'); } }, 1000); return { '\x1e': keys, ctor: function () { var me = this; called_1 = true; keys = SetupKeysRef(keys); me.on('destroy', function () { TeardownKeysRef(keys); }); } }; } return { ctor: function () { keys = SetupKeysRef(keys); this.on('destroy', function () { return TeardownKeysRef(keys); }); } }; } }, MEvent /** * 当State中的数据有改变化后触发 * @name State.changed * @event * @param {Object} e 事件对象 * @param {Object} e.keys 包含哪些数据变化的key集合 */ ); Magix.State = State; //let G_IsFunction = S.isFunction; var Router_VIEW = 'view'; var Router_HrefCache = new G_Cache(); var Router_ChgdCache = new G_Cache(); var Router_WinLoc = G_WINDOW.location; var Router_LastChanged; var Router_Silent = 0; var Router_LLoc = { query: {}, params: {}, href: G_EMPTY }; var Router_TrimHashReg = /(?:^.*\/\/[^\/]+|#.*$)/gi; var Router_TrimQueryReg = /^[^#]*#?!?/; function GetParam(key, defaultValue) { return this[G_PARAMS][key] || defaultValue !== G_Undefined && defaultValue || G_EMPTY; } var Router_Edge = 0; var Router_Hashbang = G_HashKey + '!'; var Router_UpdateHash = function (path, replace) { path = Router_Hashbang + path; if (replace) { Router_WinLoc.replace(path); } else { Router_WinLoc.hash = path; } }; var Router_Update = function (path, params, loc, replace, silent, lQuery) { path = G_ToUri(path, params, lQuery); if (path != loc.srcHash) { Router_Silent = silent; Router_UpdateHash(path, replace); } }; var Router_Bind = function () { var lastHash = Router_Parse().srcHash; var newHash, suspend; G_DOMEventLibBind(G_WINDOW, 'hashchange', function (e, loc, resolve) { if (suspend) { return; } loc = Router_Parse(); newHash = loc.srcHash; if (newHash != lastHash) { resolve = function () { e.p = 1; lastHash = newHash; suspend = G_EMPTY; Router_UpdateHash(newHash); Router_Diff(); }; e = { reject: function () { e.p = 1; suspend = G_EMPTY; Router_UpdateHash(lastHash); }, resolve: resolve, prevent: function () { suspend = 1; } }; Router.fire(G_CHANGE, e); if (!suspend && !e.p) { resolve(); } } }); G_DOMEventLibBind(G_WINDOW, 'beforeunload', function (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(); }; var Router_PNR_Routers, Router_PNR_UnmatchView, /*Router_PNR_IsFun,*/ Router_PNR_DefaultView, Router_PNR_DefaultPath; var Router_PNR_Rewrite; var DefaultTitle = G_DOCUMENT.title; var Router_AttachViewAndPath = function (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]) { var 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); } } }; var Router_GetChged = function (oldLocation, newLocation) { var oKey = oldLocation.href; var nKey = newLocation.href; var tKey = oKey + G_SPLITER + nKey; var result = Router_ChgdCache.get(tKey); if (!result) { var hasChanged_1, rps_1; result = { params: rps_1 = {}, //isParam: Router_IsParam, //location: newLocation, force: !oKey //是否强制触发的changed,对于首次加载会强制触发一次 }; var oldParams_1 = oldLocation[G_PARAMS], newParams_1 = newLocation[G_PARAMS], tArr = G_Keys(oldParams_1).concat(G_Keys(newParams_1)), key = void 0; var setDiff = function (key) { var from = oldParams_1[key], to = newParams_1[key]; if (from != to) { rps_1[key] = { from: from, to: to }; hasChanged_1 = 1; } }; for (var _i = 0, tArr_1 = tArr; _i < tArr_1.length; _i++) { key = tArr_1[_i]; setDiff(key); } oldParams_1 = oldLocation; newParams_1 = newLocation; rps_1 = result; setDiff(G_PATH); setDiff(Router_VIEW); Router_ChgdCache.set(tKey, result = { a: hasChanged_1, b: result }); } return result; }; var Router_Parse = function (href) { href = href || Router_WinLoc.href; var 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 = G_Assign(G_Assign({}, query[G_PARAMS]), hash[G_PARAMS]); result = { get: GetParam, href: href, srcQuery: srcQuery, srcHash: srcHash, query: query, hash: hash, params: params }; if (Magix_Booted) { Router_AttachViewAndPath(result); Router_HrefCache.set(href, result); } if (DEBUG) { result.params = Safeguard(result.params); result = Safeguard(result); } } return result; }; var Router_Diff = function () { var location = Router_Parse(); var 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 */ var Router = G_Assign({ /** * @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: function (pn, params, replace, silent) { if (!params && G_IsObject(pn)) { params = pn; pn = G_EMPTY; } var temp = G_ParseUri(pn); var tParams = temp[G_PARAMS]; var tPath = temp[G_PATH]; var lPath = Router_LLoc[G_PATH]; //历史路径 var lParams = Router_LLoc[G_PARAMS]; var 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 = G_Assign(G_Assign({}, lParams), tParams); //复制原来的参