UNPKG

easy-magix

Version:

view manager framewrok

1,831 lines (1,706 loc) 115 kB
//#exclude(define,before); /*!3.7.0 Licensed MIT*/ /* author:kooboy_li@163.com loader:amd enables:style,viewInit,service,ceach,resource,configIni,nodeAttachVframe,viewMerge,tipRouter,updater,updaterSetState,viewProtoMixins,base,defaultView,autoEndUpdate,linkage,state,updateTitleRouter,urlRewriteRouter optionals:updaterIncrement,serviceCombine,router,tipLockUrlRouter,edgeRouter,forceEdgeRouter,cnum,collectView,layerVframe,share,mxViewAttr,keepHTML,eventShortCtrl,eventEnterLeave */ /* author:xinglie.lkf@taobao.com */ define('magix', ['$'], function($) { if (typeof DEBUG == 'undefined') DEBUG = true; var G_IsObject = $.isPlainObject; var G_IsArray = $.isArray; var G_NOOP = function() {}; var G_COUNTER = 0; var G_EMPTY = ''; var G_EMPTY_ARRAY = []; var G_Slice = G_EMPTY_ARRAY.slice; var G_COMMA = ','; var G_NULL = null; var G_WINDOW = window; var G_DOCUMENT = document; var G_DOC = $(G_DOCUMENT); var G_HashKey = '#'; 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 Magix_PathRelativeReg = /\/\.(?:\/|$)|\/[^\/]+?\/\.{2}(?:\/|$)|\/\/+|\.{2}\//; // ./|/x/../|(b)/// // var Magix_PathTrimFileReg = /\/[^\/]*$/; // var 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 Magix_HasProp = Magix_Cfg.hasOwnProperty; 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) { var changed = 0, now, old, p; for (p in newData) { now = newData[p]; old = oldData[p]; if (!G_IsPrimitive(now) || old != now) { 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.contains ? b.contains(a) : b.compareDocumentPosition(a) & 16; } catch (e) { } } } return r; }; var G_Mix = Object.assign || function (aim, src, p) { for (p in src) { aim[p] = src[p]; } return aim; }; var View_ApplyStyle = function (key, css) { if (css && !View_ApplyStyle[key]) { View_ApplyStyle[key] = 1; $('head').append('<style>' + css + '</style>'); } }; var G_ToTry = function (fns, args, context, i, r, e) { args = args || G_EMPTY_ARRAY; if (!G_IsArray(fns)) fns = [fns]; if (!G_IsArray(args)) args = [args]; for (i = 0; i < fns.length; i++) { e = fns[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 GSet_Params = function (updater, oldParams, newParams) { var p, val; for (p in oldParams) { val = oldParams[p]; newParams[p] = (val + G_EMPTY).charAt(0) == G_SPLITER ? updater.get(val) : val; } }; 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 * var c = new Magix.cache(5,2);//创建一个可缓存5个,且缓存区为2个的缓存对象 * c.set('key1',{});//缓存 * c.get('key1');//获取 * c.del('key1');//删除 * c.has('key1');//判断 * //注意:缓存通常配合其它方法使用,在Magix中,对路径的解析等使用了缓存。在使用缓存优化性能时,可以达到节省CPU和内存的双赢效果 */ var G_Cache = function (max, buffer, remove, me) { me = this; me.c = []; me.b = buffer | 0 || 5; //buffer先取整,如果为0则再默认5 me.x = me.b + (max || 20); me.r = remove; }; G_Mix(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 (i = c.length; i--;) { cb(c[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_DefaultView; var G_Require = function(name, fn) { if (name) { if (MxGlobalView == name) { if (!G_DefaultView) { G_DefaultView = View.extend( ); } fn(G_DefaultView); } else if (G_IsArray(name)) { require(name, fn); } else { try { fn(require(name)); //获取过的直接返回 } catch (e) { require([name], fn); } } } else if (fn) { fn(); } }; var G_Define = function(mId, value) { define(mId, function() { return value; }); }; var T = function () { }; var G_Extend = function (ctor, base, props, statics, cProto) { //bProto.constructor = base; T[G_PROTOTYPE] = base[G_PROTOTYPE]; cProto = new T(); G_Mix(cProto, props); G_Mix(ctor, statics); cProto.constructor = ctor; ctor[G_PROTOTYPE] = cProto; return ctor; }; var G_HTML = function(node, html, vId) { G_DOC.triggerHandler({ type: 'htmlchange', vId: vId }); $(node).html(html); G_DOC.triggerHandler({ type: 'htmlchanged', vId: vId }); }; var G_SelectorEngine = $.find || $.zepto; var G_TargetMatchSelector = G_SelectorEngine.matchesSelector || G_SelectorEngine.matches; var G_DOMGlobalProcessor = function(e, d) { d = e.data; e.eventTarget = d.e; G_ToTry(d.f, e, d.v); }; var G_DOMEventLibBind = function(node, type, cb, remove, scope) { if (scope) { type += '.' + scope.i; } if (remove) { $(node).off(type, cb); } else { $(node).on(type, scope, cb); } }; if (DEBUG) { if (window.Proxy) { var Safeguard = function(data, allows, getter, setter) { var build = function(prefix, o) { if (o['\x1e_sf_\x1e']) { return o; } return new Proxy(o, { set: function(target, property, value) { if (!setter && !G_Has(allows, property) && (!prefix || !G_Has(allows, prefix.slice(0, -1)))) { setTimeout(function() { throw new Error('avoid writeback,key: ' + prefix + property + ' value:' + value + ' more info: https://github.com/thx/magix/issues/38'); }, 0); } 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 (G_Has(target, property) && (G_IsArray(out) || G_IsObject(out))) { return build(prefix + property + '.', out); } return out; } }); }; if (G_IsPrimitive(data)) { return data; } return build('', data); }; } else { var Safeguard = function(data) { return data; }; } } var Magix_PathToObjCache = new G_Cache(); //var Magix_PathCache = new G_Cache(); var Magix_ParamsObjectTemp; var Magix_ParamsFn = function(match, name, value) { try { value = decodeURIComponent(value); } catch (e) { } 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 */ /*var G_Path = function(url, part) { var key = url + G_SPLITER + part; var result = Magix_PathCache.get(key), domain = G_EMPTY, idx; if (!Magix_PathCache.has(key)) { //有可能结果为空,url='' path=''; var 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 * var 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_Mix({}, r.b) }; }; /** * 转换成字符串路径 * @param {String} path 路径 * @param {Object} params 参数对象 * @param {Object} [keo] 保留空白值的对象 * @return {String} 字符串路径 * @example * var str = Magix.toUri('/xxx/',{a:'b',c:'d'}); * // str == /xxx/?a=b&c=d * * var str = Magix.toUri('/xxx/',{a:'',c:2}); * * // str == /xxx/?a=&c=2 * * var str = Magix.toUri('/xxx/',{a:'',c:2},{c:1}); * * // str == /xxx/?c=2 * var 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 = []; var v, p, f; for (p in params) { v = params[p] + G_EMPTY; if (!keo || 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 i, e, map = {}, l; if (list && (l = list.length)) { for (i = 0; i < l; i++) { e = list[i]; map[(key && e) ? e[key] : e] = key ? e : (map[e] | 0) + 1; //对于简单数组,采用累加的方式,以方便知道有多少个相同的元素 } } return map; }; var G_Keys = Object.keys || function(obj, keys, p) { keys = []; for (p in obj) { if (G_Has(obj, p)) { keys.push(p); } } return keys; }; /** * Magix对象,提供常用方法 * @name Magix * @namespace */ var Magix = { /** * @lends Magix */ /** * 设置或获取配置信息 * @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" * } * }); * * * var 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_Mix(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_Mix(Magix_Cfg, cfg); G_Require(Magix_Cfg.exts, function() { Vframe_Root().mountView(Magix_Cfg.defaultView); State.on('changed', Vframe_NotifyChange); }); }, /** * 把列表转化成hash对象 * @param {Array} list 源数组 * @param {String} [key] 以数组中对象的哪个key的value做为hash的key * @return {Object} * @example * var map = Magix.toMap([1,2,3,5,6]); * //=> {1:1,2:1,3:1,4:1,5:1,6:1} * * var 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 * var result = Magix.toTry(function(){ * return true * }); * * // result == true * * var result = Magix.toTry(function(){ * throw new Error('test'); * }); * * // result == undefined * * var result = Magix.toTry([function(){ * throw new Error('test'); * },function(){ * return true; * }]); * * // result == true * * //异常的方法执行时,可以通过Magix.config中的error来捕获,如 * * Magix.config({ * error:function(e){ * console.log(e);//在这里可以进行错误上报 * } * }); * * var result = Magix.toTry(function(a1,a2){ * return a1 + a2; * },[1,2]); * * // result == 3 * var o={ * title:'test' * }; * var 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 * var str = Magix.toUrl('/xxx/',{a:'b',c:'d'}); * // str == /xxx/?a=b&c=d * * var str = Magix.toUrl('/xxx/',{a:'',c:2}); * * // str==/xxx/?a=&c=2 * * var str = Magix.toUrl('/xxx/',{a:'',c:2},{c:1}); * * // str == /xxx/?c=2 * var 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 * var 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 * var o1={ * a:10 * }; * var o2={ * b:20, * c:30 * }; * * Magix.mix(o1,o2);//{a:10,b:20,c:30} * * * @return {Object} */ mix: G_Mix, /** * 检测某个对象是否拥有某个属性 * @function * @param {Object} owner 检测对象 * @param {String} prop 属性 * @example * var 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 * var o = { * a:1, * b:2, * test:3 * }; * var 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 * var root = $('html'); * var body = $('body'); * * var r = Magix.inside(body[0],root[0]); * * // r == true * * var r = Magix.inside(root[0],body[0]); * * // r == false * * var 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> * * var 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 * * var id = Magix.guid('mx-'); * // id maybe mx-7 */ guid: G_Id, Cache: G_Cache, /** * 获取模块,调用如requirejs或seajs的require的实现 * @function */ use: G_Require, /** * 定义一个模块 * @param {string} moduleId 模块id * @param {any} value 值 */ define: G_Define }; /** * 多播事件对象 * @name Event * @namespace */ var Event_ON = 'on'; var Event = { /** * @lends Event */ /** * 触发事件 * @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 = {}; if (!data.type) 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[Event_ON + name]; if (list) G_ToTry(list, data, me); if (remove) me.off(name); }, /** * 绑定事件 * @param {String} name 事件名称 * @param {Function} fn 事件处理函数 * @example * var 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, fn) { var me = this; var key = G_SPLITER + name; var list = me[key] || (me[key] = []); list.push({ f: fn }); }, /** * 解除事件绑定 * @param {String} name 事件名称 * @param {Function} [fn] 事件处理函数 */ off: function (name, fn) { var key = G_SPLITER + name, me = this, list = me[key], i, t; if (fn) { if (list) { i = list.length; while (i--) { t = list[i]; if (t.f == fn) { t.f = G_EMPTY; break; } } } } else { delete me[key]; delete me[Event_ON + name]; } } }; Magix.Event = Event; var State_AppData = {}; var State_AppDataKeyRef = {}; var State_ChangedKeys = {}; var State_DataIsChanged = 0; var State_IsObserveChanged = function(view, keys, r) { var oKeys = view.$os; if (oKeys) { for (var i = oKeys.length; i--;) { var ok = oKeys[i]; r = G_Has(keys, ok); if (r) break; } } return r; }; var SetupKeysRef = function(keys) { keys = (keys + G_EMPTY).split(','); for (var i = 0, key; i < keys.length; i++) { key = keys[i]; if (G_Has(State_AppDataKeyRef, key)) { State_AppDataKeyRef[key]++; } else { State_AppDataKeyRef[key] = 1; } } return keys; }; var TeardownKeysRef = function(keys) { for (var i = 0, key, v; i < keys.length; i++) { key = keys[i]; if (G_Has(State_AppDataKeyRef, key)) { v = --State_AppDataKeyRef[key]; if (!v) { delete State_AppDataKeyRef[key]; delete State_AppData[key]; } } } }; /** * 可观察的内存数据对象 * @name State * @namespace * @borrows Event.on as on * @borrows Event.fire as fire * @borrows Event.off as off * @beta * @module router */ var State = G_Mix({ /** * @lends State */ /** * 从Magix.State中获取数据 * @param {String} [key] 数据key * @return {Object} */ get: function(key) { var r = key ? State_AppData[key] : State_AppData; if (DEBUG) { r = Safeguard(r, null, function(dataKey) { }, function(path, value) { var sub = key ? key : path; console.warn('beware! You direct set "{Magix.State}.' + sub + '" a new value You should call Magix.State.set() and Magix.State.digest() to notify other views {Magix.State} changed'); if (G_IsPrimitive(value) && !/\./.test(sub)) { console.warn('beware! Never set a primitive value ' + JSON.stringify(value) + ' to "{Magix.State}.' + sub + '" This may will not trigger "changed" event'); } }); } return r; }, /** * 设置数据 * @param {Object} data 数据对象 */ set: function(data) { State_DataIsChanged = G_Set(data, State_AppData, State_ChangedKeys) || State_DataIsChanged; return this; }, /** * 检测数据变化,如果有变化则派发changed事件 * @param {Object} data 数据对象 */ digest: function(data) { if (data) { State.set(data); } if (State_DataIsChanged) { this.fire('changed', { keys: State_ChangedKeys }); State_DataIsChanged = 0; State_ChangedKeys = {}; } }, /** * 获取当前数据与上一次数据有哪些变化 * @return {Object} */ diff: function() { return State_ChangedKeys; }, /** * 清除数据,该方法需要与view绑定,写在view的mixins中,如mixins:[Magix.Sate.clean('user,permission')] * @param {String} keys 数据key */ clean: function(keys) { if (DEBUG) { var called = false; setTimeout(function() { if (!called) { throw new Error('Magix.State.clean only used in View.mixins like mixins:[Magix.State.clean("p1,p2,p3")]'); } }, 1000); } if (DEBUG) { return { '\x1e': keys, ctor: function() { var me = this; called = true; keys = SetupKeysRef(keys); me.on('destroy', function() { TeardownKeysRef(keys); }); } }; } return { ctor: function() { var me = this; keys = SetupKeysRef(keys); me.on('destroy', function() { TeardownKeysRef(keys); }); } }; } /** * 当State中的数据有改变化后触发 * @name State.changed * @event * @param {Object} e 事件对象 * @param {Object} e.keys 包含哪些数据变化的key集合 */ }, Event); Magix.State = State; var Vframe_RootVframe; var Vframe_GlobalAlter; var Vframe_NotifyCreated = function(vframe, mId, p) { if (!vframe.$d && !vframe.$h && 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')} } mId = vframe.id; p = Vframe_Vframes[vframe.pId]; if (p && !G_Has(p.$r, mId)) { //readyChildren p.$r[mId] = 1; //readyChildren p.$rc++; //readyCount Vframe_NotifyCreated(p); } } }; var Vframe_NotifyAlter = function(vframe, e, mId, p) { if (!vframe.$ca && vframe.$cr) { //childrenAlter childrenCreated 当前vframe触发过created才可以触发alter事件 vframe.$cr = 0; //childrenCreated vframe.$ca = 1; //childreAleter vframe.fire('alter', e); mId = vframe.id; //var vom = vframe.owner; p = Vframe_Vframes[vframe.pId]; if (p && G_Has(p.$r, mId)) { //readyMap p.$rc--; //readyCount delete p.$r[mId]; //readyMap Vframe_NotifyAlter(p, e); } } }; /** * 获取根vframe; * @return {Vframe} * @private */ var Vframe_Root = function(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; }; var Vframe_Vframes = {}; var Vframe_AddVframe = function(id, vf) { if (!G_Has(Vframe_Vframes, id)) { Vframe_Vframes[id] = vf; Vframe.fire('add', { vframe: vf }); id = G_GetById(id); if (id) id.vframe = vf; } }; var Vframe_RunInvokes = function(vf, list, o) { list = vf.$il; //invokeList while (list.length) { o = list.shift(); if (!o.r) { //remove vf.invoke(o.n, o.a); //name,arguments } delete list[o.k]; //key } }; var Vframe_RemoveVframe = function(id, fcc, vf) { vf = Vframe_Vframes[id]; if (vf) { delete Vframe_Vframes[id]; Vframe.fire('remove', { vframe: vf, fcc: fcc //fireChildrenCreated }); id = G_GetById(id); if (id) id.vframe = G_NULL; } }; var Vframe_UpdateTag; /** * 通知当前vframe,地址栏发生变化 * @param {Vframe} vframe vframe对象 * @private */ var Vframe_Update = function(vframe, stateKeys, view) { if (vframe && vframe.$g != Vframe_UpdateTag && (view = vframe.$v) && view.$s > 0) { //存在view时才进行广播,对于加载中的可在加载完成后通过调用view.location拿到对应的G_WINDOW.location.href对象,对于销毁的也不需要广播 var isChanged = State_IsObserveChanged(view, stateKeys); /** * 事件对象 * @type {Object} * @ignore */ /*var 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所关注的相应的参数是否发生了变化 view.render(); } var cs = vframe.children(), j = cs.length, i = 0; //console.log(me.id,cs); while (i < j) { Vframe_Update(Vframe_Vframes[cs[i++]] , stateKeys ); } } }; /** * 向vframe通知地址栏发生变化 * @param {Object} e 事件对象 * @param {Object} e.location G_WINDOW.location.href解析出来的对象 * @private */ var Vframe_NotifyChange = function(e) { var vf = Vframe_Root(), view; if ((view = e.view)) { vf.mountView(view.to); } else { Vframe_UpdateTag = G_COUNTER++; Vframe_Update(vf , e.keys ); } }; /** * 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 Event.fire as #fire * @borrows Event.off as #off * @param {String} id vframe id * @property {String} id vframe id * @property {String} path 当前view的路径名,包括参数 * @property {String} pId 父vframe的id,如果是根节点则为undefined */ var Vframe = function(id, pId, me) { me = this; me.id = id; if (DEBUG) { setTimeout(function() { if (me.id && me.pId) { var parent = Vframe_Vframes[pId]; if (id != Magix_Cfg.rootId && (!pId || !parent || !parent.$c[id])) { console.error('beware! Avoid use new Magix.Vframe() outside'); } } }, 50); } //me.vId=id+'_v'; me.$c = {}; //childrenMap me.$cc = 0; //childrenCount me.$rc = 0; //readyCount me.$s = 1; //signature me.$r = {}; //readyMap me.$il = []; //invokeList me.pId = pId; Vframe_AddVframe(id, me); }; G_Mix(Vframe, G_Mix({ /** * @lends Vframe */ /** * 获取所有的vframe对象 * @return {Object} */ all: function() { return Vframe_Vframes; }, /** * 根据vframe的id获取vframe对象 * @param {String} id vframe的id * @return {Vframe|undefined} vframe对象 */ get: function(id) { return Vframe_Vframes[id]; } /** * 注册vframe对象时触发 * @name Vframe.add * @event * @param {Object} e * @param {Vframe} e.vframe */ /** * 删除vframe对象时触发 * @name Vframe.remove * @event * @param {Object} e * @param {Vframe} e.vframe * @param {Boolean} e.fcc 是否派发过created事件 */ }, Event)); G_Mix(G_Mix(Vframe[G_PROTOTYPE], Event), { /** * @lends Vframe# */ /** * 加载对应的view * @param {String} viewPath 形如:app/views/home?type=1&page=2 这样的view路径 * @param {Object|Null} [viewInitParams] 调用view的init方法时传递的参数 */ mountView: function(viewPath, viewInitParams /*,keepPreHTML*/ ) { var me = this; var id = me.id; var node = G_GetById(id), po, sign, view, params , ctors , pId, parent, p, val ; if (!me.$a && node) { //alter me.$a = 1; me.$t = node.innerHTML; //.replace(ScriptsReg, ''); template } me.unmountView( /*keepPreHTML*/ ); me.$d = 0; //destroyed 详见unmountView if (node && viewPath) { me.path = viewPath; po = G_ParseUri(viewPath); view = po.path; sign = ++me.$s; params = po[G_PARAMS]; pId = me.pId; parent = Vframe_Vframes[pId]; parent = parent && parent.$v; parent = parent && parent.$u; if (parent && viewPath.indexOf(G_SPLITER) > 0) { GSet_Params(parent, params, params); } G_Mix(params, viewInitParams); G_Require(view, function(TView) { if (sign == me.$s) { //有可能在view载入后,vframe已经卸载了 if (!TView) { return Magix_Cfg.error(Error('id:' + id + ' cannot load:' + view)); } ctors = View_Prepare(TView); view = new TView({ owner: me, id: id }, params , ctors ); me.$v = view; me.$g = Vframe_UpdateTag; View_DelegateEvents(view); view.init(params); view.render(); if (!view.$t) { //无模板 me.$a = 0; //不会修改节点,因此销毁时不还原 if (!view.$p) { view.endUpdate(); } } } }); } }, /** * 销毁对应的view */ unmountView: function( /*keepPreHTML*/ ) { var me = this; var view = me.$v, node, reset, vfId = me.id; me.$il = []; //invokeList 销毁当前view时,连同调用列表一起销毁 if (view) { if (!Vframe_GlobalAlter) { reset = 1; Vframe_GlobalAlter = { id: vfId }; } me.$d = 1; //用于标记当前vframe处于view销毁状态,在当前vframe上再调用unmountZone时不派发created事件 me.unmountZone(0, 1); Vframe_NotifyAlter(me, Vframe_GlobalAlter); me.$v = 0; //unmountView时,尽可能早的删除vframe上的view对象,防止view销毁时,再调用该 vfrmae的类似unmountZone方法引起的多次created if (view.$s > 0) { view.$s = 0; view.fire('destroy', 0, 1, 1); View_DestroyAllResources(view, 1); View_DelegateEvents(view, 1); } view.$s--; view.owner = G_NULL; node = G_GetById(vfId); if (node && me.$a /*&&!keepPreHTML*/ ) { //如果view本身是没有模板的,也需要把节点恢复到之前的状态上:只有保留模板且view有模板的情况下,这条if才不执行,否则均需要恢复节点的html,即view安装前什么样,销毁后把节点恢复到安装前的情况 G_HTML(node, me.$t, vfId); } /*if (me.$vPrimed) { //viewMounted与viewUnmounted成对出现 me.$vPrimed = 0; me.fire('viewUnmounted'); }*/ if (reset) Vframe_GlobalAlter = 0; } me.$s++; //增加signature,阻止相应的回调,见mountView }, /** * 加载vframe * @param {String} id 节点id * @param {String} viewPath view路径 * @param {Object} [viewInitParams] 传递给view init方法的参数 * @return {Vframe} vframe对象 * @example * // html * // &lt;div id="magix_vf_defer"&gt;&lt;/div&gt; * * * //js * view.owner.mountVframe('magix_vf_defer','app/views/list',{page:2}) * //注意:动态向某个节点渲染view时,该节点无须是vframe标签 */ mountVframe: function(id, viewPath, viewInitParams /*, keepPreHTML*/ ) { var me = this, vf; Vframe_NotifyAlter(me, { id: id }); //如果在就绪的vframe上渲染新的vframe,则通知有变化 //var vom = me.owner; vf = Vframe_Vframes[id]; if (!vf) { if (!G_Has(me.$c, id)) { //childrenMap,当前子vframe不包含这个id me.$cl = G_EMPTY; //childrenList 清空缓存的子列表 me.$cc++; //childrenCount ,增加子节点 } me.$c[id] = id; //map vf = new Vframe(id, me.id); } vf.mountView(viewPath, viewInitParams /*,keepPreHTML*/ ); return vf; }, /** * 加载某个区域下的view * @param {HTMLElement|String} zoneId 节点对象或id * @param {Object} [viewInitParams] 传递给view init方法的参数 * @example * // html * // &lt;div id="zone"&gt; * // &lt;div mx-view="path/to/v1"&gt;&lt;/div&gt; * // &lt;/div&gt; * * view.onwer.mountZone('zone');//即可完成zone节点下的view渲染 */ mountZone: function(zoneId, viewInitParams /*,keepPreHTML*/ ) { var me = this; var i, vf, id, vfs = []; zoneId = zoneId || me.id; var vframes = $(G_HashKey + zoneId + ' [mx-view]'); /* body(#mx-root) div(mx-vframe=true,mx-view='xx') div(mx-vframe=true,mx-view=yy) 这种结构,自动构建父子关系, 根结点渲染,获取到子列表[div(mx-view=xx)] 子列表渲染,获取子子列表的子列表 加入到忽略标识里 会导致过多的dom查询 现在使用的这种,无法处理这样的情况,考虑到项目中几乎没出现过这种情况,先采用高效的写法 上述情况一般出现在展现型页面,dom结构已经存在,只是附加上js行为 不过就展现来讲,一般是不会出现嵌套的情况,出现的话,把里面有层级的vframe都挂到body上也未尝不可,比如brix2.0 */ me.$h = 1; //hold fire creted //me.unmountZone(zoneId, 1); 不去清理,详情见:https://github.com/thx/magix/issues/27 for (i = 0; i < vframes.length; i++) { vf = vframes[i]; id = vf.id || (vf.id = G_Id()); if (!vf.$m) { //防止嵌套的情况下深层的view被反复实例化 vf.$m = 1; vfs.push([id, vf.getAttribute('mx-view')]); } } while (vfs.length) { vf = vfs.shift(); id = vf[0]; if (vfs[id]) { Magix_Cfg.error(Error('vf.id duplicate:' + id + ' at ' + me.path)); } else { me.mountVframe(vfs[id] = id, vf[1], viewInitParams); } } me.$h = 0; Vframe_NotifyCreated(me); }, /** * 销毁vframe * @param {String} [id] 节点id */ unmountVframe: function(id /*,keepPreHTML*/ , inner) { //inner 标识是否是由内部调用,外部不应该传递该参数 var me = this, vf, fcc, pId; id = id ? me.$c[id] : me.id; //var vom = me.owner; vf = Vframe_Vframes[id]; if (vf) { fcc = vf.$cr; //childrenCreated pId = vf.pId; vf.unmountView( /*keepPreHTML*/ ); Vframe_RemoveVframe(id, fcc); vf.id = vf.pId = G_EMPTY; //清除引用,防止被移除的view内部通过setTimeout之类的异步操作有关的界面,影响真正渲染的view vf = Vframe_Vframes[pId]; if (vf && G_Has(vf.$c, id)) { //childrenMap delete vf.$c[id]; //childrenMap vf.$cl = G_EMPTY; vf.$cc--; //cildrenCount if (!inner) Vframe_NotifyCreated(vf); //移除后通知完成事件 } } }, /** * 销毁某个区域下面的所有子vframes * @param {HTMLElement|String} [zoneId]节点对象或id */ unmountZone: function(zoneId, inner) { var me = this; var p; var cm = me.$c; for (p in cm) { if (!zoneId || (p != zoneId && G_NodeIn(p, zoneId))) { me.unmountVframe(p /*,keepPreHTML,*/ , 1); } } if (!inner) Vframe_NotifyCreated(me); } , /** * 获取父vframe * @param {Integer} [level] 向上查找层级,默认1,取当前vframe的父级 * @return {Vframe|undefined} * @beta * @module linkage */ parent: function(level, vf) { vf = this; level = (level >>> 0) || 1; while (vf && level--) { vf = Vframe_Vframes[vf.pId]; } return vf; }, /** * 获取当前vframe的所有子vframe的id。返回数组中,vframe在数组中的位置并不固定 * @return {Array[String]} * @beta * @module linkage * @example * var children = view.owner.children(); * console.log(children); */ children: function(me) { me = this; return me.$cl || (me.$cl = G_Keys(me.$c)); //排序,获取对象的key在不同的浏览器返回的顺序不一样,我们这里排序一次,强制一样。同时id不存在重复,所以排序后浏览器之间的表现肯定一致。 }, /** * 调用view的方法 * @param {String} name 方法名 * @param {Array} [args] 参数 * @return {Object} * @beta * @module linkage * @example * // html * // &lt;div&gt; mx-view="path/to/v1" id="test"&gt;&lt;/div&gt; * var vf = Magix.Vframe.get('test'); * vf.invoke('methodName',['args1','agrs2']); */ invoke: function(name, args) { var result; var vf = this, view, fn, o, list = vf.$il, key; if ((view = vf.$v) && view.$p) { //view rendered result = (fn = view[name]) && G_ToTry(fn, args, view); } else { o = list[key = G_SPLITER + name]; if (o) { o.r = args === o.a; //参数一样,则忽略上次的 } o = { n: name, a: args, k: key }; list.push(o); list[key] = o; } return result; } /** * 子孙view修改时触发 * @name Vframe#alter * @event * @param {Object} e */ /** * 子孙view创建完成时触发 * @name Vframe#created * @event * @param {Object} e */ }); Magix.Vframe = Vframe; /** * Vframe 中的2条线 * 一: * 渲染 * 每个Vframe有$cc(childrenCount)属性和$c(childrenItems)属性 * * 二: * 修改与创建完成 * 每个Vframe有rC(readyCount)属性和$r(readyMap)属性 * * fca firstChildrenAlter fcc firstChildrenCreated */ $.fn.invokeView = function(name, args) { var l = this.length; if (l) { var e = this[0]; var vf = e.vframe; if (args === undefined) { return vf && vf.invoke(name); } else { for (var i = 0; i < l; i++) { e = this[i]; vf = e.vframe; if (vf) { vf.invoke(name, args); } } } } }; /* dom event处理思路 性能和低资源占用高于一切,在不特别影响编程体验的情况下,向性能和资源妥协 1.所有事件代理到body上 2.优先使用原生冒泡事件,使用mouseover+view.inside代替mouseenter 'over<mouseover>':function(e){ if(!Magix.inside(e.relatedTarget,e.current)){ //enter } } 3.事件支持嵌套,向上冒泡 */ var Body_MagixPrefix = 'mx-'; var Body_EvtInfoCache = new G_Cache(30, 10); var Body_EvtInfoReg = /(?:([\w\-]+)\x1e)?([^(]+)\(([\s\S]*)?\)/; var Body_RootEvents = {}; var Body_SearchSelectorEvents = {}; var Body_FindVframeInfo = function(current, eventType) { var vf, tempId, selectorObject, eventSelector, names = [], begin = current, info = current.getAttribute(Body_MagixPrefix + eventType), match, view, vfs = [], selectorVfId; if (info) { match = Body_EvtInfoCache.get(info); if (!match) { match = info.match(Body_EvtInfoReg) || G_EMPTY_ARRAY; match = { v: match[1], n: match[2], i: match[3] }; /*jshint evil: true*/ match.p = match.i && G_ToTry(Function('return ' + match.i), G_EMPTY_ARRAY, current); Body_EvtInfoCache.set(info, match); } match = { r: info, //如果事件已经存在处理的vframe或节点上通过mx-owner指定处理的vframe v: match.v , p: match.p, i: match.i, n: match.n }; if (DEBUG) { match = Safeguard(match, { v: 1 }); } names.push(match); } //如果有匹配但没有处理的vframe或者事件在要搜索的选择器事件里 if ((match && !match.v) || Body_SearchSelectorEvents[eventType]) { selectorVfId = current.$v; //如果节点有缓存,则使用缓存 if (!selectorVfId) { //先找最近的vframe vfs.push(begin); while (begin != G_DOCBODY && (begin = begin.parentNode)) { //找最近的vframe,且节点上没有mx-autonomy属性 if ((Vframe_Vframes[tempId = begin.id] || (tempId = begin.$v))) { selectorVfId = tempId; break; } vfs.push(begin); } } if (selectorVfId) { //从最近的vframe向上查找带有选择器事件的view while ((info = vfs.pop())) { info.$v = selectorVfId; } do { vf = Vframe_Vframes[selectorVfId]; if (vf && (view = vf.$v)) { selectorObject = view.$so; eventSelector = selectorObject[eventType]; for (tempId in eventSelector) { if (G_TargetMatchSelector(current, tempId)) { names.push({ r: tempId, v: selectorVfId, n: tempId }); } } //防止跨view选中,到带模板的view时就中止或未指定 if (view.$t) { if (match && !match.v) match.v = selectorVfId;