blob: 387967825e31aa3df050872b188ffef67183adde [file] [log] [blame]
/**
* @file 组件基类
* @author sushuang(sushuang@baidu.com)
*/
/* jshint evil:true */
define(function (require) {
var $ = require('jquery');
var lib = require('dt/lib');
var tpl = require('dt/tpl');
var getByPath = lib.getByPath;
var setByPath = lib.setByPath;
var inner = lib.makeInner();
// 基础css class
var BASE_CSS = 'dtui-cpt';
var ORIGINAL_HTML_KEY = '\x06\x06\x06__originalHTML';
/**
* 使用例子(模板以etpl为例):
*
* 首先在 componentConfig.js 中挂上 someCpt 对应的 AMD module:
* cptClasses['someCpt'] = require('some/some/some');
*
* 能够在本component的模板中声明子component:
* {{ target: tplOfThisComponent }}
* ...
* <div data-cpt="
* type: 'someCpt',
* name: 'aSubCpt'
* viewModel: {
* someAttr1: viewModel.some,
* someAttr2: lib.ob(123)
* }"></div>
* ...
* {{ /target }}
*
* 另一个例子:
* {{ target: tplOfThisComponent }}
* ...
* <div data-cpt="
* type: 'someCpt',
* name: 'aSubCpt',
* viewModelGet: 'someData'"></div>
* ...
* {{ /target }}
*
* 然后在 本Component的_prepare 方法中,
* 把子component的初始化数据挂在 this._viewModel().someData 上即可传入子component。
*
* data-cpt中可使用的属性:
* type: 必选。表示组件类的别名。
* name: 可选,给定name则能在父component取得子component实例。
* 例如:name为'aSubCpt',则 this._sub('aSubCpt') 就是子component实例。
* 支持对象和数组,例如:
* name为'items[aSubCpt]', 则 this._sub('items')是包含子component的数组。
* name为'items.aa.bb.aSubCpt', 则 this._sub('items.aa')是 {bb: subComponent}。
* viewModel: 可选。表示要传入的viewModel,如 viewModel: {list: this.viewModel('dataList')}。
* viewModelGet: 可选。表示viewModel的引用路径,如 viewModel: 'dataList'。
*
* 父component被dispose时,会自动dispose用模板声明方式建立起来的子component。
*
* 每个component自动含有这两个observable对象:
* this.viewModel('visiable') // 控制可见性(display:none)
* this.viewModel('disabled') // 控制disabled状态(css加class:xxx-disabled)
* 两个对象均可依据外部传入的值进行初始化,如:
* <div data-cpt="
* type: 'someCpt',
* viewModel: {disabled: viewModel.disabled}"></div>
*
* 在component内部手动创建一些实体时,如果需要进行dispose,可方便得使用_disposable方法。
*
* 如果想延迟模板的渲染和子component的创建,可不设定tplTarget,并在适当时机手动调用_applyTpl方法。
*
* @exports Component
*/
var Component = inner.attach(lib.newClass(lib.enableEvent({
/**
* 代码中获取define中内容,请使用:this._getDefineProperty('nameKey')
*
* @protected
*/
_define: {
/**
* 本组件对应的tpl内容
* 例如使用 require('tpl!somefile.html') 得到
* (构建的时候会自动合并所有tpl文件)
* 或者直接在js中写html内容
*
* @type {string}
* @protected
*/
tpl: '',
/**
* 本组件对应的tpl的target(参见etpl)
* 如果被设定,则在component创建过程中自动进行_applyTpl。
*
* @type {string}
* @protected
*/
tplTarget: '',
/**
* 本组件对应的默认的css类型,可以不设置。
* 如需要多个css,请传数组。
*
* @type {(string|Array)}
* @protected
*/
css: '',
/**
* 本component的属性,以及默认值。
* 会和祖先类的viewModel merge。
* 控件初始化传入的viewModel中的属性,必须现在此处定义。
* 设定成“函数”而非直接对象,因为不应在prototype上挂单例
*(否则就需要viewModel里面的数据提供clone能力了),
* 而应是每次实例构造时候生成实例。
*
* @protected
* @return {Object} viewModel
*/
viewModel: function () {
return {
// 本文中 disabled 表示不能接受用户输入,
// 但是并不表示不能被程序更新,也不意味着不可见。
// 在initMainEl中进行初始化
// @type {lib.ob}
disabled: null,
// 可见与否。不可从外部传入。
// 在initMainEl中进行初始化,根据主元素的初始display状态来初始化。
// @type {lib.ob}
visible: null,
// 额外的css class。如果需要多个css,请传数组:['css1', 'css2']
// @type {(string|Array.<string>)}
css: '',
// 如果需要的话,可以从外部传入基础模板target(很少会这么做)
// @type {string}
tplTarget: null,
// 默认是innerHTML方式,还可设为'prepend', 'append'方式
// @type {string}
tplRenderMode: 'html'
};
},
/**
* @see this.viewModel(path)
* @see lib.getByPath
* 默认viewModel中的内容是private的。
* 在此处可以定义public的属性路径(复杂路径可为a.b[3])。
* 从而可用 this.viewModel(path) 获取。
* 默认全private是为了避免对象细节不受控得暴露,从而以后难于修改。
*
* @type {Array}
* @protected
*/
viewModelPublic: ['disabled', 'visible'],
/**
* 每项为viewModel第一层属性名。
* 设置此项时,此项指定的viewModel中第一层属性,会被clone赋值
*(无法clone的部分引用赋值,无法clone @see lib/model.js)。
* 此项为空时,外部传入的viewModel对第一层以下的内容全是引用赋值。
*
* @type {Array}
*/
viewModelMerge: [],
/**
* 是否只有viewModel中规定的属性可以传进来。
* 默认true。
*
* @type {boolean}
*/
viewModelOnlyAccessDeclaredProperties: true,
/**
* 是否阻止初始化子Componnet。某些Component不希望拥有子Component,
* 而是使子Component隶属于祖先(例如tab控件)。
*
* @type {Boolean}
*/
suppressConstructSub: false
}, // end _define
/**
* 渲染前的准备
* 子类来实现此方法
*
* @protected
*/
_prepare: $.noop,
/**
* 自身的初始化
* 子类来实现此方法
*
* @protected
*/
_init: $.noop,
/**
* 自身的析构,在子组件析构前执行。
* 子类来实现此方法。
*
* @protected
*/
_dispose: $.noop,
/**
* 自身的析构,最后执行。
* 子类来实现此方法。
*
* @protected
*/
_disposeFinally: $.noop,
/**
* 自身的初始化后的执行逻辑,可能反复调用。
* 子类来实现此方法。
*
* @protected
* @param {boolean} isRefresh 初始化后第一次执行则值为false,以后均为true
*/
_run: $.noop,
/**
* 得到带有本实例的 namespace 的 dom event。
* 带有 namespace 的 dom event 会在 dispose 时自动被 off 掉。
* 所以约定所有的 dom event 都要用此方法生成,例如:
* $el.on(this._event('click'), function () {... handler ... });
*
* @protected
* @param {string=} eventName dom事件名。
* 如果缺省,则返回事件 namespace,可直接用来el.off()。
* 例如:$el.off(this._event())。
* @return {string} 加上了本实例的 namespace 的 dom事件名;或者事件 namespace。
*/
_event: function (eventName) {
var ns = inner(this).eventNamespace;
return (eventName != null ? eventName : '') + ns;
},
/**
* parse构造时传入的viewModel。如需要可override。不要去直接调用此方法。
*
* @protected
* @param {*} inputViewModel 输入的viewModel
* @param {Object} cptDef 模板中的配置参数
* @return {Object} inputViewModel 输入的viewModel
*/
_parseViewModel: function (inputViewModel, cptDef) {
return inputViewModel;
},
/**
* parse构造时传入的def。如需要可override。不要去直接调用此方法。
*
* @protected
* @param {Object} cptDef cpt定义
* @return {Object} cptDef cpt定义
*/
_parseCptDef: function (cptDef) {
return cptDef;
},
/**
* 构造。不建议override此方法。
*
* @protected
* @param {(HTMLElement|jQuery)=} $el 主容器
* @param {Object=} viewModel viewModel
* @param {Object=} def 模板中data-cpt中声明的变量
*/
_construct: function ($el, viewModel, def) {
arguments.length > 0 && lib.assert($el != null); // 这种错误太常见了
// 可以初始化无主元素的component
if (!$el || $.isPlainObject($el)) {
def = viewModel;
viewModel = $el;
$el = null;
}
else {
$el = $($el);
}
def = $.extend({}, def);
lib.isComponent(this, true); // 标记为Component。
var innerThis = inner(this);
innerThis.cptDef = def;
// 事件的名空间
innerThis.eventNamespace = '.namespace' + lib.localUID();
// 存放子组件,格式为:{name: someSubComponent}
innerThis.subComponents = {};
innerThis.sub$Els = {};
innerThis.disposed = false;
innerThis.cptDisposableArea = {};
innerThis.cptInstanceUID = 'cpt-instance-' + lib.localUID();
initViewModelControlMap.call(this);
viewModel = this._parseViewModel(viewModel || {}, def) || {};
setViewModel.call(this, viewModel);
initCssDefine.call(this);
initInnerViewModel.call(this);
initMainEl.call(this, $el);
this._prepare();
applyBaseTpl.call(this);
this._init();
},
/**
* 析构。不建议override此方法。
* 此方法只可被使用一次。析构完后不能再运行。
*
* @public
*/
dispose: function () {
var innerThis = inner(this);
if (!innerThis.disposed) {
var el = this.el();
var originInnerHTML = this[ORIGINAL_HTML_KEY] || '';
// 最先自己进行清除
this._dispose();
disposeSub.call(this);
disposeEls.call(this);
this._disposable(false);
disposeBase.call(this);
// 最后一次机会自己清除
this._disposeFinally();
el.innerHTML = originInnerHTML;
innerThis.disposed = true;
}
},
/**
* @public
*/
isDisposed: function () {
return !!inner(this).disposed;
},
/**
* 一般用此来判断是否可以响应用户交互,或者响应异步回调。
*
* @public
*/
isFrozen: function () {
return this._viewModel().disabled() || this.isDisposed();
},
/**
* 只是 syntactic sugar。
* 匿名注册需要被自动dispose的对象:this._disposable(obj); // obj 对象有dispose方法。
* 实名注册需要被自动dispose的对象:this._disposable(obj, 'someName');
* 指定名称执行dispose:this._disposable('someName');
* 无论是匿名还是实名,注册的对象 obj,会自动在整个cpt dispose时自动被调用dispose。
* 派生类不可使用_disposable(false),因为它只可被使用一次,析构完后不能再运行。
*
* @protected
* @param {(Object|jQuery|string|boolean)} o 要被dispose的对象(需要有dispose方法),或者是name。
* @param {string=} name 一般不需要,除非想指定名称去dispose。
* @return {Object} o
*/
_disposable: function (o, name) {
lib.assert(o != null); // 常见的错误
var area = inner(this).cptDisposableArea;
// o为 null 或 undefined 时什么也不做,这是为了支持编码的简洁
if (o == null) {
return o;
}
// 全部dispose
else if (o === false) {
for (var n in area) {
if (area.hasOwnProperty(n)) {
disposeDisposable.call(this, area[n]);
area[n] = null;
}
}
}
// 指定名称进行dispose
else if ($.type(o) === 'string') {
disposeDisposable.call(this, area[o]);
area[o] = null;
}
// 注册可以被dispose的对象
else {
lib.assert($.isFunction(o.dispose) || lib.isJQuery(o), 'No dispose method nor not jQuery!');
name = name || '\x0E\x0E-component-anonymouse-disposable' + lib.localUID();
area[name] = o;
}
return o;
},
/**
* 通常情况下并不需要去实现resize,除非Component不得不手动resize。
* (例如缓存了offsetXXX、使用了canvas等等)
*
* @public
*/
resize: $.noop,
/**
* 延迟创建$el并初始化。
* 在构建Component时,可以不传入el,而后准备好el和tpl后手动调用此方法进行初始化。
*
* @protected
* @param {(HTMLElement|jQuery)} $el 主元素
*/
_manuInitView: function ($el) {
initMainEl.call(this, $el);
this._constructSub($el);
},
/**
* 模板渲染,并创建子控件。
* Component子类中,可使用此方法,渲染模板片段。
*(一般用在存在异步获取数据后再渲染的情况下)
* 注意:此方法不支持多次使用。
*
* @protected
* @param {HTMLElement|jQuery} el 在此容器上渲染模板。
* @param {string} tplTarget tpl的target名
*/
_applyTpl: function (el, tplTarget) {
if (tplTarget) {
this._renderTpl(tplTarget, null, el);
this._constructSub(el);
}
},
/**
* 渲染模板。直接渲染到dom中 或者 只返回html字符串
*
* @protected
* @param {string} tplTarget 模板的target名
* @param {Object=} viewModel 渲染用的model。缺省则为this._viewModel()
* @param {(HTMLElement|jQuery)=} el 如果传此参数,则渲染到此el中;否则,仅返回结果html。
* @return {string} 模板渲染结果html
*/
_renderTpl: function (tplTarget, viewModel, el) {
var model = {
// 模板中直接引用 viewModel.something
viewModel: viewModel || this._viewModel(),
// 语言字符从中取得,模板中使用方式例如:lang.html.some1,lang.string.some2
lang: this.getLang(),
constant: this.getConstant()
};
var result = tpl.render(
tplTarget, model, this._getDefineProperty('tpl')
) || '';
el && $(el)[this._viewModel().tplRenderMode](result);
return result;
},
/**
* 解析模板创建子cpt。一般此方法自动被调用,不手动调用。
*
* @protected
* @param {HTMLElement|jQuery} container 在此容器内检查子cpt并创建。
* @param {boolean} includeContainer 是否也解析container上的data-cpt。默认false
*/
_constructSub: function (container, includeContainer) {
var $container = $(container);
lib.assert($container.length);
if (includeContainer && $container.data('cpt')) {
this._constructSubCpt.call(this, container);
}
var that = this;
$('*[data-cpt]', $container).each(function (index, subEl) {
that._constructSubCpt(subEl);
});
},
/**
* @protected
*/
_constructSubCpt: function (subEl) {
var $subEl = $(subEl);
var originInnerHTML = subEl.innerHTML;
var thisViewModel = this._viewModel();
var def = this._parseCptDef(
(new Function(
'viewModel', 'lib', 'lang', 'constant',
'return {' + $subEl.data('cpt') + '};'
)).call(this, thisViewModel, lib, this.getLang(), this.getConstant())
);
var Clz = this.getCptClass(def.type);
var subViewModel = def.viewModelGet
? getByPath(def.viewModelGet, thisViewModel)
: def.viewModel;
var subCpt = new Clz(subEl, subViewModel, def);
subCpt[ORIGINAL_HTML_KEY] = originInnerHTML;
return this._sub(
// 如果没定义name,则表示匿名
def.name || ('\x0E\x0F-sub-cpt-name-' + lib.localUID()),
subCpt
);
},
/**
* 依照模板的定义,重新创建子component,旧的子component被dispose。
* 如果组件的dispose没有写完整,则可能出现内存泄露、事件处理器指向旧对象等问题。
*
* @public
* @param {string} subPath 如:'zzz.xxx'
*/
recreateSubCpt: function (subPath) {
var subCpt = this._sub(subPath);
if (!subCpt) {
return;
}
var subEl = subCpt.el();
subCpt.dispose();
this._sub(subPath, null);
this._constructSubCpt(subEl);
},
/**
* 构造完成后执行,可反复调用
*
* @public
* @param {boolean} isRefresh 初始化后第一次执行则值为false,以后均为true
*/
traversalRun: function (isRefresh) {
// 先执行sub
traverseSub(
inner(this).subComponents,
function (subCpt) {
subCpt.traversalRun(isRefresh);
}
);
// 最后执行自己
this._run(isRefresh);
},
/**
* 设置或得到子component
*
* @protected
* @param {string} name 子component路径。可以为xxx.xxx[2]这样的object路径。
* @param {*} o 一般为子component,或者array等。如果缺省,则表示意图是“get Component”
* @param {boolean} overlapSilently 当set时,是否可以overlap,默认false
* @return {Component} 子组件实例
*/
_sub: function (name, o, overlapSilently) {
var subComponents = inner(this).subComponents;
if (arguments.length < 2) {
return getByPath(
name,
subComponents,
getByPath.actionChoice.notPlainReturn
);
}
else {
setByPath(
name,
o,
subComponents,
overlapSilently
? setByPath.actionChoice.notPlainOverlap
: setByPath.actionChoice.notPlainThrow
);
return o;
}
},
/**
* 得到_define中的内容
*
* @protected
* @param {string} key define property key
* @return {*} define property
*/
_getDefineProperty: function (key) {
return this.constructor.defineProperties[key];
},
/**
* 得到本component的主元素
*
* @public
* @return {HTMLElemnet} 主元素dom节点
*/
el: function () {
return inner(this).el;
},
/**
* 设置或得到元素(jquery)
* 如果缺省参数,得到的是本component的主元素。
* 这些元素会在dispose时自动off(this._event());
*
* @public
* @param {string=} name 自己起名字。
* 可以为xxx.xxx[2]这样的object路径。
* 如果缺省表示得到主元素
* @param {*} o 一般为要设置的元素,或者数组等
* 如果缺省表示得到主元素
* @param {boolean} overlapSilently 当set时,是否可以overlap,默认false
* @return {HTMLElemnet}
*/
$el: function (name, o, overlapSilently) {
var innerThis = inner(this);
if (arguments.length === 0) {
// 得到主元素
return innerThis.$el;
}
if (arguments.length === 1) {
return getByPath(
name,
innerThis.sub$Els,
getByPath.actionChoice.notPlainReturn
);
}
else {
setByPath(
name,
o,
innerThis.sub$Els,
overlapSilently
? setByPath.actionChoice.notPlainOverlap
: setByPath.actionChoice.notPlainThrow
);
return o;
}
},
/**
* 得到本component的基础css
*
* @public
* @return {HTMLElemnet}
*/
css: function () {
return this._getDefineProperty('css') || '';
},
/**
* 返回 view model中的指定内容
*
* @public
* @param {string=} path 如可以为:'xxxx.bbbb[3]'
* @return {*}
*/
viewModel: function (path) {
if (canPublic.call(this, path)) {
return getByPath(path, inner(this).viewModel);
}
else {
throw new Error(path + ' is private!');
}
},
/**
* 返回 view model 整体,私有,外部不可调用
*
* @protected
* @return {Object} viewModel
*/
_viewModel: function () {
return inner(this).viewModel;
},
/**
* 得到cpt的定义
*
* @public
* @param {string} key 则返回def的某个key对应的值。如:'some/some/some'
* @return {Class} component类定义
*/
getCptClass: function (key) {
return Component.cptClasses[key];
},
/**
* 得到本component的定义
*
* @public
* @param {string} key 则返回def的某个key对应的值。
* @return {*}
*/
getCptDef: function (key) {
return inner(this).cptDef[key];
},
/**
* 得到本component的uid
*
* @public
* @return {string} uid
*/
uid: function () {
return inner(this).cptInstanceUID;
},
/**
* 得到本component的基本css class
*
* @public
* @param {string=} suffix 例如'-disabled'
* @return {Array} 例如['cpt', 'someThisCss', 'someInputExtraCss']
*/
getFullCss: function (suffix) {
var csses = [BASE_CSS];
var innerThis = inner(this);
csses.push.apply(csses, innerThis.basicCss || []);
csses.push.apply(csses, innerThis.extraCss || []);
return $.map(
csses,
function (css) {
return css + (suffix || '');
}
);
},
/**
* 是否禁用了
*
* @public
* @return {boolean} 是否禁用
*/
isDisabled: function () {
return !!lib.peek(this._viewModel().disabled);
},
/**
* @public
* @protected
*/
getLang: function () {
// 可以在业务中自己配置,或者重载 getLang 方法
return Component.defaultLanguageSet;
},
/**
* @public
* @protected
*/
getConstant: function () {
// 可以在业务中自己配置,或者重载 getLang 方法
return Component.defaultConstant;
},
/**
* 生成本对象的副本。一般不需要用到。子类可重载。
* @see lib.cloneAtom
*
* @public
* @return {Object} component instance
*/
clone: $.noop,
/**
* 从最祖先类遍历到本类
*
* @protected
* @param {Function} callback callback
*/
_traverseFromAncestor: function (callback) {
var clzChain = [];
for (
var Clz = this.constructor;
Clz;
Clz = Clz.prototype._superClass
) {
clzChain.push(Clz);
}
for (var i = clzChain.length; i--;) {
callback.call(this, clzChain[i]);
}
},
/**
* 创建或得到或清除local observable对象。
* 用于提供“Component间局部通讯”功能(例如某panel由子Component触发刷新)。
* 此对象在以自身为根的树上,对于子Component可见。
* 如果本Component没有主dom(this.el()为空),则不支持local observable功能。
*
* 使用方式:
* 创建:this.localOb('LOCAL_OB_SOME', lib.ob());
* 移除:this.localOb('LOCAL_OB_SOME', false); // 一般不需要这么做。
* 获得并注册监听:lib.localOb('LOCAL_OB_SOME').subscribe(someHandlerFn, this);
* 向最近祖先上的localOb发消息:this.getAncestorLocalOb('LOCAL_OB_SOME')({someDataKey: 'someData'});
*
* @public
* @param {string} key 如:'LOCAL_OB_SCROLL_RESIZE', 见contant.LocalObKey。
* @param {(Object|Function|false)=} ob obsevable对象,参见dataDriven.js,或函数。
* 如果此参数为false,表示删除此local ob。
* @return {Object} observable对象
*/
localOb: function (key, ob) {
var ret;
var innerThis = inner(this);
var localObRepo = innerThis.localObRepo || (innerThis.localObRepo = {});
if (arguments.length > 1) {
if (ob === false) { // 清除local observable。
ret = localObRepo[key];
delete localObRepo[key];
}
else { // 增添local observable
lib.assert(localObRepo[key] == null); // 不允许覆盖防止编码错误
ret = localObRepo[key] = ob;
}
}
else {
ret = localObRepo[key];
}
return ret;
},
/**
* 得到祖先中最近出现的匹配给定key的local observable对象。
* 使用这种设定的原因是,例如这个场景:
* 某外层Component有key为'refresh'的localOb,
* 中层的Component也可设key为'refresh'的localOb,并在ob的监听函数中触发外层的refresh localOb。
* 从而内层的Component可以触发这个链式refresh。
*
* @public
* @param {string} key 如:'LOCAL_OB_SCROLL_RESIZE', 见contant.LocalObKey。
* @return {Object} localOb
*/
getAncestorLocalOb: function (key) {
// 因为不维护Component的父指针(为了灵活和简化),所以按照dom结构向上找。
var $el = this.$el();
var cpt;
var localOb;
while (
($el = $el && $el.parent())[0]
&& !(localOb = (cpt = lib.getComponent($el)) && cpt.localOb(key))
&& $el[0] !== document.body // 最高到body(含)停止
) {} // jshint ignore:line
return localOb; // jshint ignore:line
}
})));
/**
* 各种component类型的定义集合。
* 约定外部只在componentConfig.js中使用。
*
* @public
*/
Component.cptClasses = {};
/**
* 各种语言的话术集合。
* 约定外部只在componentConfig.js中使用。
*
* @public
*/
Component.defaultLanguageSet = {};
/**
* 常量集合。
* 约定外部只在componentConfig.js中使用。
*
* @public
*/
Component.defaultConstant = {};
/**
* @inner
*/
function initInnerViewModel() {
var viewModel = this._viewModel();
// visible不可从外部传入,因为基于css的display,
// 不需要和其他cpt(尤其不应该和子cpt)分享。
viewModel.visible = lib.ob(true);
// disabledOb允许从外部传入,本Component的disabledOb自动监听传入的disabledOb。
// 从而外部的disabledOb可以控制本Component,而反之不可。
var outerDisabledOb = viewModel.disabled;
viewModel.disabled = lib.ob(false);
if (outerDisabledOb != null) {
this._disposable(outerDisabledOb.subscribe(handleDisable, this));
}
function handleDisable(val) {
viewModel.disabled(val);
}
}
/**
* 主节点初始化。
*
* @inner
* @param {(HTMLElement|jQuery)} $el 主节点
*/
function initMainEl($el) {
if (!$el) {
return;
}
var innerThis = inner(this);
$el = innerThis.$el = $($el);
innerThis.el = $el[0];
// 设置componentEl标志
lib.bindComponent($el, this);
$el.addClass(this.getFullCss().join(' '));
var viewModel = this._viewModel();
var disabledOb = viewModel.disabled;
var visibleOb = viewModel.visible;
this._disposable(disabledOb.subscribe(elChangeDisable, this));
this._disposable(visibleOb.subscribe(elChangeVisible, this));
// 设置disable初始状态
elChangeDisable.call(this, disabledOb());
// 用当前mainEl的display来初始化visible。
// 这么做的原因是:
// (1)符合设style="display:none"的习惯。
// (2)方便于多个component控制一个dom时行为不冲突。
// 例如:tab的content也是cpt时,tab改变了display,但是cpt还没有初始化,就造成了不一致。
visibleOb($el[0].style.display !== 'none');
}
function elChangeDisable(v) {
this.$el()[v ? 'addClass' : 'removeClass'](
this.getFullCss('-disabled').join(' ')
);
}
function elChangeVisible(v) {
this.$el()[v ? 'show' : 'hide']();
}
function applyBaseTpl() {
var tplTarget = inner(this).cptDef.tplTarget
|| this._getDefineProperty('tplTarget');
var $el = this.$el();
if (tplTarget && $el && $el.length) {
this._renderTpl(tplTarget, null, $el);
}
if (!this._getDefineProperty('suppressConstructSub') && $el && $el.length) {
this._constructSub($el);
}
}
// 析构基础
// (此方法写在闭包中是为了禁止子类调用)
function disposeBase() {
var $el = this.$el();
var innerThis = inner(this);
// 清除自己的事件挂载
$el.off(innerThis.eventNamespace);
$el.removeClass(
this.getFullCss().join(' ')
);
// 并不默认清除 main el 中的内容,因为可能并不属于自身可控。
innerThis.viewModel = null;
innerThis.el = null;
innerThis.$el = null;
innerThis.subComponents = null;
innerThis.sub$Els = null;
lib.bindComponent($el, false);
}
// 递归dispose component
function traverseSub(o, callback, elOrCpt) {
if (
(!elOrCpt && lib.isComponent(o))
|| (elOrCpt && lib.isJQuery(o))
) {
callback(o);
}
else if ($.type(o) === 'object' && !lib.isAtom(o)) {
for (var i in o) {
if (o.hasOwnProperty(i)
// 当subcpt移除时会设为null。虽然如果编码错误也可能导致为null,
// 但这两种情况不好区分,所以暂时不fail fast了。
&& o[i] != null
) {
traverseSub(o[i], callback, elOrCpt);
}
}
}
}
/**
* 设置view model
*
* @inner
* @param {Object=} viewModel 外部输入的viewModel
* @return {Object} viewModel
*/
function setViewModel(viewModel) {
// 初始化 viewModel,从祖先类开始 merge。
var innerThis = inner(this);
var base = innerThis.viewModel = {};
var that = this;
this._traverseFromAncestor(function (Clz) {
base = lib.merge(
base,
// 定义中的viewModel是个函数
Clz.defineProperties.viewModel.call(that),
{
onlyMergeOwnPropertyInTarget: false,
levelOneNeedMerge: null,
clone: false
}
);
});
// 注意,如果viewModel是atom的(@see lib.atom),则会直接引用传递。
base = lib.merge(
base,
viewModel,
// 规定:只有base中有声明(表示默认值),才能被设值
{
onlyMergeOwnPropertyInTarget:
!!this._getDefineProperty('viewModelOnlyAccessDeclaredProperties'),
levelOneNeedMerge: innerThis.viewModelMergeMap || {}
}
);
innerThis.viewModel = base;
return base;
}
function initViewModelControlMap() {
// 缓存成map便于查询
var innerThis = inner(this);
var viewModelPublicMap = innerThis.viewModelPublicMap = {};
var viewModelMergeMap = innerThis.viewModelMergeMap = {};
// 从祖先开始merge
this._traverseFromAncestor(function (Clz) {
var pub = Clz.defineProperties.viewModelPublic || [];
for (var i = 0, len = pub.length; i < len; i++) {
viewModelPublicMap[pub[i]] = 1;
}
var mrg = Clz.defineProperties.viewModelMerge || [];
for (var i = 0, len = mrg.length; i < len; i++) {
viewModelMergeMap[mrg[i]] = 1;
}
});
}
function initCssDefine() {
var viewModel = this._viewModel();
var innerThis = inner(this);
// 从祖先遍历到本类,找到所有基础css。
var basicCss = [];
this._traverseFromAncestor(function (Clz) {
var css = Clz.defineProperties.css;
if ($.isArray(css)) {
basicCss.push.apply(basicCss, css);
}
else if (css) {
basicCss.push(css);
}
});
innerThis.basicCss = basicCss;
// 可多个extra c
var extraCss = viewModel && viewModel.css || [];
if (!$.isArray(extraCss)) {
extraCss = [extraCss];
}
innerThis.extraCss = extraCss;
}
function canPublic(path) {
path = getByPath.normalizePath(path);
return !!inner(this).viewModelPublicMap[path];
}
function disposeDisposable(o) {
// o 为含有 dispose方法 的对象 或 jQuery
// jQuery时意为清除本名空间的事件
if (o && $.isFunction(o.dispose)) {
try {
o.dispose();
}
catch (ex) {
if (Component.debug) {
throw ex;
}
// 线上为了减少用户影响,吞食dispose的ex。
else if (Component.printLog) {
Component.printLog.error(ex);
}
}
}
else if (o && lib.isJQuery(o)) {
o.off(this._event());
}
}
/**
* 递归dispose所有子component 或 指定根节点的子components。
*
* @inner
* @param {*} root 如果缺省则默认为 inner(this).subComponents
*/
function disposeSub(root) {
root = root || inner(this).subComponents;
traverseSub(
root,
function (subCpt) {
try {
subCpt.dispose();
}
catch (ex) {
if (Component.debug) {
throw ex;
}
// 线上为了减少用户影响,吞食dispose的ex。
else if (Component.printLog) {
Component.printLog.error(ex);
}
}
}
);
// 清空root
if (!lib.isAtom(root)) {
for (var i in root) {
if (root.hasOwnProperty(i)) {
root[i] = null;
}
}
}
}
/**
* 递归dispose所有存储的el 或 指定根节点的存储的el。
*
* @inner
* @param {*} root 如果缺省则默认为 inner(this).sub$els
*/
function disposeEls(root) {
root = root || inner(this).sub$els;
var ns = this._event();
traverseSub(
root,
function ($el) {
$el.off(ns);
},
true
);
// 清空root
if (!lib.isAtom(root)) {
for (var i in root) {
if (root.hasOwnProperty(i)) {
root[i] = null;
}
}
}
}
/**
* 打log到debug控制台(如果浏览器支持的话).
* 可手动挂载到Component.printLog上。
*
* @public
* @param {string} data
*/
Component.consoleLog = function (data) {
var console = window.console;
if (lib.isObject(console) && typeof console.log === 'function') {
console.log(lib.stringifyJSON(data));
}
};
return Component;
});