magix
Version:
view manager framewrok
1,534 lines (1,532 loc) • 168 kB
JavaScript
//#snippet;
//#uncheck = jsThis,jsLoop;
//#exclude = loader,allProcessor;
/*!3.8.16 Licensed MIT*/
/*
author:kooboy_li@163.com
loader:amd
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', ['$'], function ($) {
if (typeof DEBUG == 'undefined')
window.DEBUG = true;
var G_IsObject = $.isPlainObject;
var G_IsArray = $.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_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 (_magix) {
require([name], fn);
}
}
}
else if (fn) {
fn();
}
};
function T() { }
var G_Extend = function (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;
};
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);
}
};
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 = $.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]; //历史路径