easy-magix
Version:
view manager framewrok
1,831 lines (1,707 loc) • 115 kB
JavaScript
//#exclude(define,before);
/*!3.7.0 Licensed MIT*/
/*
author:kooboy_li@163.com
loader:cmd
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(require) {
if (typeof DEBUG == 'undefined') DEBUG = true;
var $ = require('$');
var G_NOOP = function() {};
var G_IsObject = $.isPlainObject;
var G_IsArray = $.isArray;
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 (window.seajs) {
seajs.use(name, fn);
} else {
var a = [];
if (!G_IsArray(name)) name = [name];
for (var i = 0; i < name.length; i++) {
a.push(require(name[i]));
}
if (fn) fn.apply(G_NULL, a);
}
} else {
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
* // <div id="magix_vf_defer"></div>
*
*
* //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
* // <div id="zone">
* // <div mx-view="path/to/v1"></div>
* // </div>
*
* 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
* // <div> mx-view="path/to/v1" id="test"></div>
* 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) {