ecui
Version:
Enterprise Classic User Interface.
1,320 lines (1,173 loc) • 82.6 kB
JavaScript
//{if 0}//
(function() {
var core = ecui,
array = core.array,
dom = core.dom,
ext = core.ext,
string = core.string,
util = core.util,
ui = core.ui,
undefined,
WINDOW = window,
DOCUMENT = document,
DATE = Date,
MATH = Math,
REGEXP = RegExp,
ABS = MATH.abs,
MAX = MATH.max,
MIN = MATH.min,
ISNAN = isNaN,
USER_AGENT = navigator.userAgent,
isStrict = DOCUMENT.compatMode == 'CSS1Compat',
ieVersion = /msie (\d+\.\d)/i.test(USER_AGENT) ? DOCUMENT.documentMode || (REGEXP.$1 - 0) : undefined,
firefoxVersion = /firefox\/(\d+\.\d)/i.test(USER_AGENT) ? REGEXP.$1 - 0 : undefined,
indexOf = array.indexOf,
remove = array.remove,
addClass = dom.addClass,
contain = dom.contain,
createDom = dom.create,
getAttribute = dom.getAttribute,
getParent = dom.getParent,
getPosition = dom.getPosition,
getStyle = dom.getStyle,
insertHTML = dom.insertHTML,
ready = dom.ready,
removeDom = dom.remove,
removeClass = dom.removeClass,
setStyle = dom.setStyle,
toCamelCase = string.toCamelCase,
attachEvent = util.attachEvent,
blank = util.blank,
detachEvent = util.detachEvent,
extend = util.extend,
getView = util.getView,
inherits = util.inherits,
timer = util.timer,
toNumber = util.toNumber;
//{/if}//
//{if $phase == "define"}//
var NORMAL = core.NORMAL = 0,
LOADING = core.LOADING = 1,
REPAINT = core.REPAINT = 2;
//__gzip_unitize__event
var $bind,
$connect,
$clearState,
$create,
$fastCreate,
calcHeightRevise,
calcLeftRevise,
calcTopRevise,
calcWidthRevise,
createControl,
disposeControl,
drag,
/**
* 从指定的 Element 对象开始,依次向它的父节点查找绑定的 ECUI 控件。
* findControl 方法,会返回从当前 Element 对象开始,依次向它的父 Element 查找到的第一个绑定(参见 $bind 方法)的 ECUI 控件。findControl 方法一般在控件创建时使用,用于查找父控件对象。
* @public
*
* @param {HTMLElement} el Element 对象
* @return {ecui.ui.Control} ECUI 控件对象,如果不能找到,返回 null
*/
findControl = core.findControl = function(el) {
for (; el; el = getParent(el)) {
if (el.getControl) {
return el.getControl();
}
}
return null;
},
getActived,
getAttributeName,
getFocused,
getHovered,
getKey,
getMouseX,
getMouseY,
getOptions,
getScrollNarrow,
getStatus,
inheritsControl,
intercept,
isContentBox,
loseFocus,
mask,
needInitClass,
query,
restore,
setFocused,
triggerEvent,
wrapEvent,
eventNames = [
'mousedown', 'mouseover', 'mousemove', 'mouseout', 'mouseup',
'click', 'dblclick', 'focus', 'blur', 'activate', 'deactivate',
'keydown', 'keypress', 'keyup', 'mousewheel'
];
(function() {
/**
* 创建 ECUI 事件对象。
* @public
*
* @param {string} type 事件类型
* @param {Event} event 浏览器原生事件对象,忽略将自动填充
*/
///__gzip_original__UI_EVENT_CLASS
var UI_EVENT = ui.Event = function(type, event) {
this.type = type;
if (event) {
this.pageX = event.pageX;
this.pageY = event.pageY;
this.which = event.which;
this.target = event.target;
this._oNative = event;
} else {
this.pageX = mouseX;
this.pageY = mouseY;
this.which = keyCode;
this.target = DOCUMENT;
}
},
UI_EVENT_CLASS = UI_EVENT.prototype,
ecuiName = 'ecui', // Element 中用于自动渲染的 ecui 属性名称
isGlobalId, // 是否自动将 ecui 的标识符全局化
structural, // DOM结构生成的方式,0表示填充所有内容,1表示不填充控件的class,2表示完全不填充
flgContentBox, // 在计算宽度与高度时,是否需要修正内填充与边框样式的影响
flgFixedOffset, // 在计算相对位置时,是否需要修正边框样式的影响
scrollNarrow, // 浏览器滚动条相对窄的一边的长度
initRecursion = 0, // init 操作的递归次数
lastClientWidth, // 浏览器之前的宽度
plugins = {}, // 扩展组件列表
maskElements = [], // 遮罩层组
mouseX, // 当前鼠标光标的X轴坐标
mouseY, // 当前鼠标光标的Y轴坐标
keyCode = 0, // 当前键盘按下的键值,解决keypress与keyup中得不到特殊按键的keyCode的问题
lastClick, // 上一次产生点击事件的信息
status, // 框架当前状态
allControls = [], // 全部生成的控件,供释放控件占用的内存使用
independentControls = [], // 独立的控件,即使用create($create)方法创建的控件
namedControls, // 所有被命名的控件的集合
uniqueIndex = 0, // 控件的唯一序号
connectedControls = {}, // 等待关联的控件集合
activedControl, // 当前环境下被激活的控件,即鼠标左键按下时对应的控件,直到左键松开后失去激活状态
hoveredControl, // 当前环境下鼠标悬停的控件
focusedControl, // 当前环境下拥有焦点的控件
eventListeners = {}, // 控件事件监听描述对象
envStack = [], // 高优先级事件调用时,保存上一个事件环境的栈
currEnv = { // 当前操作的环境
// 鼠标点击时控件如果被屏弊需要取消点击事件的默认处理,此时链接将不能提交
click: function(event) {
event = wrapEvent(event);
//__transform__control_o
var control = findControl(event.target);
if (control && control.isDisabled()) {
event.preventDefault();
}
},
// 鼠标左键按下需要改变框架中拥有焦点的控件
mousedown: function(event) {
if (activedControl) {
// 如果按下鼠标左键后,使用ALT+TAB使浏览器失去焦点然后松开鼠标左键,
// 需要恢复激活控件状态,第一次点击失效
bubble(activedControl, 'deactivate');
activedControl = null;
return;
}
event = wrapEvent(event);
//__transform__control_o
var control = event.getControl(),
// 修复ie下跨iframe导致的事件类型错误的问题
flag = ieVersion < 8 && isScrollClick(event),
target = control;
if (!(lastClick && isDblClick())) {
lastClick = {
time: new DATE().getTime()
};
}
if (control) {
if (flag) {
// IE8以下的版本,如果为控件添加激活样式,原生滚动条的操作会失效
// 常见的表现是需要点击两次才能进行滚动操作,而且中途不能离开控件区域
// 以免触发悬停状态的样式改变。
return;
}
for (; target; target = target.getParent()) {
if (target.isFocusable()) {
if (!(target != control && target.contain(focusedControl))) {
// 允许获得焦点的控件必须是当前激活的控件,或者它没有焦点的时候才允许获得
// 典型的用例是滚动条,滚动条不需要获得焦点,如果滚动条的父控件没有焦点
// 父控件获得焦点,否则焦点不发生变化
setFocused(target);
}
break;
}
}
if (!flag) {
// 如果不是在原生滚动条区域,进行左键按下的处理
mousedown(control, event);
}
} else {
if (control = findControl(target = event.target)) {
// 如果点击的是失效状态的控件,检查是否需要取消文本选择
onselectstart(control, event);
// 检查是否INPUT/SELECT/TEXTAREA/BUTTON标签,需要失去焦点
if (target.tagName == 'INPUT' || target.tagName == 'SELECT' ||
target.tagName == 'TEXTAREA' || target.tagName == 'BUTTON') {
timer(function() {
target.blur();
});
}
}
// 点击到了空白区域,取消控件的焦点
setFocused();
// 正常情况下 activedControl 是 null,如果是down按下但未点击到控件,此值为undefined
activedControl = undefined;
}
},
// 鼠标移入的处理,需要计算是不是位于当前移入的控件之外,如果是需要触发移出事件
mouseover: function(event) {
if (currEnv.type != 'drag' && currEnv.type != 'zoom') {
event = wrapEvent(event);
//__transform__control_o
var control = event.getControl(),
parent = getCommonParent(control, hoveredControl);
bubble(hoveredControl, 'mouseout', event, parent);
bubble(control, 'mouseover', event, parent);
hoveredControl = control;
}
},
mousemove: function(event) {
event = wrapEvent(event);
//__transform__control_o
var control = event.getControl();
bubble(control, 'mousemove', event);
},
mouseup: function(event) {
event = wrapEvent(event);
//__transform__control_o
var control = event.getControl(),
commonParent;
if (activedControl !== null) {
// 如果为 null 表示之前没有触发 mousedown 事件就触发了 mouseup,
// 这种情况出现在鼠标在浏览器外按下了 down 然后回浏览器区域 up,
// 或者是 ie 系列浏览器在触发 dblclick 之前会触发一次单独的 mouseup,
// dblclick 在 ie 下的事件触发顺序是 mousedown/mouseup/click/mouseup/dblclick
bubble(control, 'mouseup', event);
if (activedControl) {
commonParent = getCommonParent(control, activedControl);
bubble(commonParent, 'click', event);
// 点击事件在同时响应鼠标按下与弹起周期的控件上触发(如果之间未产生鼠标移动事件)
// 模拟点击事件是为了解决控件的 Element 进行了 remove/append 操作后 click 事件不触发的问题
if (lastClick) {
if (isDblClick() && lastClick.target == control) {
bubble(commonParent, 'dblclick', event);
lastClick = null;
} else {
lastClick.target = control;
}
}
bubble(activedControl, 'deactivate', event);
}
// 将 activeControl 的设置复位,此时表示没有鼠标左键点击
activedControl = null;
}
}
},
dragEnv = { // 拖曳操作的环境
type: 'drag',
mousemove: function(event) {
event = wrapEvent(event);
//__transform__target_o
var target = currEnv.target,
// 计算期待移到的位置
expectX = target.getX() + mouseX - currEnv.x,
expectY = target.getY() + mouseY - currEnv.y,
// 计算实际允许移到的位置
x = MIN(MAX(expectX, currEnv.left), currEnv.right),
y = MIN(MAX(expectY, currEnv.top), currEnv.bottom);
if (triggerEvent(target, 'dragmove', event, [x, y])) {
target.setPosition(x, y);
}
currEnv.x = mouseX + target.getX() - expectX;
currEnv.y = mouseY + target.getY() - expectY;
},
mouseup: function(event) {
event = wrapEvent(event);
//__transform__target_o
var target = currEnv.target;
triggerEvent(target, 'dragend', event);
activedControl = currEnv.actived;
restore();
currEnv.mouseover(event);
currEnv.mouseup(event);
}
},
interceptEnv = { // 强制点击拦截操作的环境
type: 'intercept',
mousedown: function(event) {
event = wrapEvent(event);
//__transform__target_o
var target = currEnv.target,
env = currEnv,
control = event.getControl();
lastClick = null;
if (!isScrollClick(event)) {
if (control && !control.isFocusable()) {
// 需要捕获但不激活的控件是最高优先级处理的控件,例如滚动条
mousedown(control, event);
} else if (triggerEvent(target, 'intercept', event)) {
// 默认仅拦截一次,框架自动释放环境
restore();
} else if (!event.cancelBubble) {
if (env == currEnv) {
// 不改变当前操作环境表示希望继续进行点击拦截操作
// 例如弹出菜单点击到选项上时,不自动关闭并对下一次点击继续拦截
if (control) {
mousedown(control, event);
}
} else {
// 手动释放环境会造成向外层环境的事件传递
currEnv.mousedown(event);
}
}
}
}
},
zoomEnv = { // 缩放操作的环境
type: 'zoom',
mousemove: function(event) {
event = wrapEvent(event);
//__gzip_original__minWidth
//__gzip_original__maxWidth
//__gzip_original__minHeight
//__gzip_original__maxHeight
//__transform__target_o
var target = currEnv.target,
width = currEnv.width = mouseX - currEnv.x + currEnv.width,
height = currEnv.height = mouseY - currEnv.y + currEnv.height,
minWidth = currEnv.minWidth,
maxWidth = currEnv.maxWidth,
minHeight = currEnv.minHeight,
maxHeight = currEnv.maxHeight;
currEnv.x = mouseX;
currEnv.y = mouseY;
width = minWidth > width ? minWidth : maxWidth < width ? maxWidth : width;
height = minHeight > height ? minHeight : maxHeight < height ? maxHeight : height;
// 如果宽度或高度是负数,需要重新计算定位
target.setPosition(currEnv.left + MIN(width, 0), currEnv.top + MIN(height, 0));
if (triggerEvent(target, 'zoom', event)) {
target.setSize(ABS(width), ABS(height));
}
},
mouseup: function(event) {
event = wrapEvent(event);
//__transform__target_o
var target = currEnv.target;
triggerEvent(target, 'zoomend', event);
activedControl = currEnv.actived;
restore();
repaint();
currEnv.mouseover(event);
currEnv.mouseup(event);
}
},
/**
* 初始化指定的 Element 对象对应的 DOM 节点树。
* init 方法将初始化指定的 Element 对象及它的子节点,如果这些节点拥有初始化属性(参见 getAttributeName 方法),将按照规则为它们绑定 ECUI 控件,每一个节点只会被绑定一次,重复的绑定无效。页面加载完成时,将会自动针对 document.body 执行这个方法,相当于自动执行以下的语句:ecui.init(document.body)
* @public
*
* @param {Element} el Element 对象
*/
init = core.init = function(el) {
if (!initEnvironment() && el) {
var i = 0,
list = [],
options = el.all || el.getElementsByTagName('*'),
elements = [el],
o, namedMap = {};
if (!(initRecursion++)) {
// 第一层 init 循环的时候需要关闭resize事件监听,防止反复的重入
detachEvent(WINDOW, 'resize', repaint);
}
for (; o = options[i++];) {
if (getAttribute(o, ecuiName)) {
elements.push(o);
}
}
for (i = 0; el = elements[i]; i++) {
options = getOptions(el);
// 以下使用 el 替代 control
if (o = options.type) {
options.main = el;
list.push($create(ui[toCamelCase(o.charAt(0).toUpperCase() + o.slice(1))], options));
if (options.id) {
namedMap[options.id] = list[list.length - 1];
}
}
}
for (i = 0; o = list[i++];) {
o.cache();
}
for (i = 0; o = list[i++];) {
o.init();
}
if (!(--initRecursion)) {
attachEvent(WINDOW, 'resize', repaint);
}
return namedMap;
}
},
/**
* 重绘浏览器区域的控件。
* repaint 方法在页面改变大小时自动触发,一些特殊情况下,例如包含框架的页面,页面变化时不会触发 onresize 事件,需要手工调用 repaint 函数重绘所有的控件。
* @public
*/
repaint = core.repaint = function() {
var i = 0,
list = [],
widthList = [],
o;
if (ieVersion) {
// 防止 ie6/7 下的多次重入
o = (isStrict ? DOCUMENT.documentElement : DOCUMENT.body).clientWidth;
if (lastClientWidth != o) {
lastClientWidth = o;
} else {
// 如果高度发生变化,相当于滚动条的信息发生变化,因此需要产生scroll事件进行刷新
onscroll(new UI_EVENT('scroll'));
return;
}
}
status = REPAINT;
o = currEnv.type;
// 隐藏所有遮罩层
mask(false);
if (o != 'zoom') {
// 改变窗体大小需要清空拖拽状态
if (o == 'drag') {
currEnv.mouseup();
}
// 按广度优先查找所有正在显示的控件,保证子控件一定在父控件之后
for (o = null; o !== undefined; o = list[i++]) {
for (var j = 0, controls = query({
parent: o
}); o = controls[j++];) {
if (o.isShow() && o.isResizable()) {
list.push(o);
}
}
}
for (i = 0; o = list[i++];) {
// 避免在resize中调用repaint从而引起反复的reflow
o.repaint = blank;
triggerEvent(o, 'resize');
delete o.repaint;
if (ieVersion < 8) {
// 修复ie6/7下宽度自适应错误的问题
o = getStyle(j = o.getMain());
if (o.width == 'auto' && o.display == 'block') {
j.style.width = '100%';
}
}
}
if (ieVersion < 8) {
// 由于强制设置了100%,因此改变ie下控件的大小必须从内部向外进行
// 为避免多次reflow,增加一次循环
for (i = 0; o = list[i];) {
widthList[i++] = o.getMain().offsetWidth;
}
for (; o = list[i--];) {
o.getMain().style.width =
widthList[i] - (flgContentBox ? o.$getBasicWidth() * 2 : 0) + 'px';
}
}
for (i = 0; o = list[i++];) {
o.cache(true, true);
}
for (i = 0; o = list[i++];) {
o.$setSize(o.getWidth(), o.getHeight());
}
}
if (ieVersion < 8) {
// 解决 ie6/7 下直接显示遮罩层,读到的浏览器大小实际未更新的问题
timer(mask, 0, null, true);
} else {
mask(true);
}
status = NORMAL;
};
/**
* 使一个 Element 对象与一个 ECUI 控件 在逻辑上绑定。
* 一个 Element 对象只能绑定一个 ECUI 控件,重复绑定会自动取消之前的绑定。
* @protected
*
* @param {HTMLElement} el Element 对象
* @param {ecui.ui.Control} control ECUI 控件
*/
$bind = core.$bind = function(el, control) {
el._cControl = control;
el.getControl = getControlByElement;
};
/**
* 清除控件的状态。
* 控件在销毁、隐藏与失效等情况下,需要使用 $clearState 方法清除已经获得的焦点与激活等状态。
* @protected
*
* @param {ecui.ui.Control} control ECUI 控件
*/
$clearState = core.$clearState = function(control) {
var o = control.getParent();
loseFocus(control);
if (control.contain(activedControl)) {
bubble(activedControl, 'deactivate', null, activedControl = o);
}
if (control.contain(hoveredControl)) {
bubble(hoveredControl, 'mouseout', null, hoveredControl = o);
}
};
/**
* 为两个 ECUI 控件 建立连接。
* 使用页面静态初始化或页面动态初始化(参见 ECUI 使用方式)方式,控件创建时,需要的关联控件也许还未创建。$connect 方法提供将指定的函数滞后到对应的控件创建后才调用的模式。如果 targetId 对应的控件还未创建,则调用会被搁置,直到需要的控件创建成功后,再自动执行(参见 create 方法)。
* @protected
*
* @param {Object} caller 发起建立连接请求的对象
* @param {Function} func 用于建立连接的方法,即通过调用 func.call(caller, ecui.get(targetId)) 建立连接
* @param {string} targetId 被连接的 ECUI 控件 标识符,即在标签的 ecui 属性中定义的 id 值
*/
$connect = core.$connect = function(caller, func, targetId) {
if (targetId) {
var target = namedControls[targetId];
if (target) {
func.call(caller, target);
} else {
(connectedControls[targetId] = connectedControls[targetId] || [])
.push({
func: func,
caller: caller
});
}
}
};
/**
* 创建 ECUI 控件。
* $create 方法创建控件时不会自动渲染控件。在大批量创建控件时,为了加快渲染速度,应该首先使用 $create 方法创建所有控件完成后,再批量分别调用控件的 cache、init 与 repaint 方法渲染控件。options 对象支持的属性如下:
* id {string} 当前控件的 id,提供给 $connect 与 get 方法使用
* main {HTMLElement} 与控件绑捆的 Element 对象(参见 getMain 方法),如果忽略此参数将创建 Element 对象与控件绑捆
* parent {ecui.ui.Control} 父控件对象或者父 Element 对象
* primary {string} 控件的基本样式(参见 getMainClass 方法),如果忽略此参数将使用主元素的 className 属性
* @protected
*
* @param {Function} type 控件的构造函数
* @param {Object} options 初始化选项(参见 ECUI 控件)
* @return {ecui.ui.Control} ECUI 控件
*/
$create = core.$create = function(type, options) {
type = type.client || type;
options = options || {};
//__gzip_original__parent
var i = 0,
parent = options.parent,
el = options.main,
o = options.primary || '',
className;
options.uid = 'ecui-' + (++uniqueIndex);
if (el) {
if (structural) {
className = el.className;
} else {
el.className = className = el.className + ' ' + o + type.agent.TYPES;
}
// 如果没有指定基本样式,使用控件的样式作为基本样式
if (!o) {
/\s*([^\s]+)/.test(className);
options.primary = REGEXP.$1;
}
// 如果指定的元素已经初始化,直接返回
if (el.getControl) {
return el.getControl();
}
} else {
// 没有传入主元素,需要自动生成,此种情况比较少见
el = options.main = createDom(o + type.agent.TYPES);
if (!o) {
options.primary = type.agent.types[0];
}
}
// 生成控件
type = new type(el, options);
if (parent) {
//{if 0}//
if (parent instanceof ui.Control) {
//{else}// if (parent instanceof UI_CONTROL) {
//{/if}//
type.setParent(parent);
} else {
type.appendTo(parent);
}
} else {
type.$setParent(findControl(getParent(type.getOuter())));
}
oncreate(type, options);
independentControls.push(type);
// 处理所有的关联操作
if (el = connectedControls[options.id]) {
for (connectedControls[options.id] = null; o = el[i++];) {
o.func.call(o.caller, type);
}
}
return type;
};
/**
* 快速创建 ECUI 控件。
* $fastCreate 方法仅供控件生成自己的部件使用,生成的控件不在控件列表中注册,不自动刷新也不能通过 query 方法查询(参见 $create 方法)。$fastCreate 方法通过分解 Element 对象的 className 属性得到样式信息,其中第一个样式为类型样式,第二个样式为基本样式。
* @protected
*
* @param {Function} type 控件的构造函数
* @param {HTMLElement} el 控件对应的 Element 对象
* @param {ecui.ui.Control} parent 控件的父控件
* @param {Object} options 初始化选项(参见 ECUI 控件)
* @return {ecui.ui.Control} ECUI 控件
*/
$fastCreate = core.$fastCreate = function(type, el, parent, options) {
type = type.client || type;
options = options || {};
options.uid = 'ecui-' + (++uniqueIndex);
if (!options.primary) {
/\s*([^\s]+)/.test(el.className);
options.primary = REGEXP.$1;
}
type = new type(el, options);
type.$setParent(parent);
oncreate(type, options);
return type;
};
/**
* 添加控件的事件监听函数。
* @public
*
* @param {ecui.ui.Control} control ECUI 控件
* @param {string} name 事件名称
* @param {Function} caller 监听函数
*/
core.addEventListener = function(control, name, caller) {
name = control.getUID() + name;
(eventListeners[name] = eventListeners[name] || []).push(caller);
};
/**
* 获取高度修正值(即计算 padding, border 样式对 height 样式的影响)。
* IE 的盒子模型不完全遵守 W3C 标准,因此,需要使用 calcHeightRevise 方法计算 offsetHeight 与实际的 height 样式之间的修正值。
* @public
*
* @param {CssStyle} style CssStyle 对象
* @return {number} 高度修正值
*/
calcHeightRevise = core.calcHeightRevise = function(style) {
return flgContentBox ? toNumber(style.borderTopWidth) + toNumber(style.borderBottomWidth) +
toNumber(style.paddingTop) + toNumber(style.paddingBottom) : 0;
};
/**
* 获取左定位修正值(即计算 border 样式对 left 样式的影响)。
* opera 等浏览器,offsetLeft 与 left 样式的取值受到了 border 样式的影响,因此,需要使用 calcLeftRevise 方法计算 offsetLeft 与实际的 left 样式之间的修正值。
* @public
*
* @param {HTMLElement} el Element 对象
* @return {number} 左定位修正值
*/
calcLeftRevise = core.calcLeftRevise = function(el) {
//__transform__style_o
var style = getStyle(el.offsetParent);
return !firefoxVersion || style.overflow != 'visible' && getStyle(el, 'position') == 'absolute' ?
toNumber(style.borderLeftWidth) * flgFixedOffset : 0;
};
/**
* 获取上定位修正值(即计算 border 样式对 top 样式的影响)。
* opera 等浏览器,offsetTop 与 top 样式的取值受到了 border 样式的影响,因此,需要使用 calcTopRevise 方法计算 offsetTop 与实际的 top 样式之间的修正值。
* @public
*
* @param {HTMLElement} el Element 对象
* @return {number} 上定位修正值
*/
calcTopRevise = core.calcTopRevise = function(el) {
//__transform__style_o
var style = getStyle(el.offsetParent);
return !firefoxVersion || style.overflow != 'visible' && getStyle(el, 'position') == 'absolute' ?
toNumber(style.borderTopWidth) * flgFixedOffset : 0;
};
/**
* 获取宽度修正值(即计算 padding,border 样式对 width 样式的影响)。
* IE 的盒子模型不完全遵守 W3C 标准,因此,需要使用 calcWidthRevise 方法计算 offsetWidth 与实际的 width 样式之间的修正值。
* @public
*
* @param {CssStyle} style CssStyle 对象
* @return {number} 宽度修正值
*/
calcWidthRevise = core.calcWidthRevise = function(style) {
return flgContentBox ? toNumber(style.borderLeftWidth) + toNumber(style.borderRightWidth) +
toNumber(style.paddingLeft) + toNumber(style.paddingRight) : 0;
};
/**
* 创建 ECUI 控件。
* 标准的创建 ECUI 控件 的工厂方法,适用于少量创建控件,生成的控件不需要任何额外的调用即可正常的显示,对于批量创建控件,请使用 $create 方法。options 对象支持的属性如下:
* id {string} 当前控件的 id,提供给 $connect 与 get 方法使用
* main {HTMLElement} 与控件绑捆的 Element 对象(参见 getMain 方法),如果忽略此参数将创建 Element 对象与控件绑捆
* parent {ecui.ui.Control} 父控件对象或者父 Element 对象
* primary {string} 控件的基本样式(参见 getMainClass 方法),如果忽略此参数将使用主元素的 className 属性
* @public
*
* @param {string|Function} type 控件的类型名或控件的构造函数
* @param {Object} options 初始化选项(参见 ECUI 控件)
* @return {ecui.ui.Control} ECUI 控件
*/
createControl = core.create = function(type, options) {
type = $create('string' == typeof(type) ? ui[type] : type, options);
type.cache();
type.init();
return type;
};
/**
* 释放 ECUI 控件及其子控件占用的内存。
* @public
*
* @param {ecui.ui.Control|HTMLElement} control 需要释放的控件对象或包含控件的 Element 对象
*/
disposeControl = core.dispose = function(control) {
var i = allControls.length,
//{if 0}//
type = control instanceof ui.Control,
//{else}// type = control instanceof UI_CONTROL,
//{/if}//
namedMap = {},
controls = [],
o;
if (type) {
$clearState(control);
} else {
o = findControl(getParent(control));
if (focusedControl && contain(control, focusedControl.getOuter())) {
setFocused(o);
}
if (activedControl && contain(control, activedControl.getOuter())) {
bubble(activedControl, 'deactivate', null, activedControl = o);
}
if (hoveredControl && contain(control, hoveredControl.getOuter())) {
bubble(hoveredControl, 'mouseout', null, hoveredControl = o);
}
}
for (o in namedControls) {
namedMap[namedControls[o].getUID()] = o;
}
for (; i--;) {
o = allControls[i];
if (type ? control.contain(o) : !!o.getOuter() && contain(control, o.getOuter())) {
// 需要删除的控件先放入一个集合中等待遍历结束后再删除,否则控件链将产生变化
controls.push(o);
remove(independentControls, o);
if (o = namedMap[o.getUID()]) {
delete namedControls[o];
}
allControls.splice(i, 1);
}
}
for (; o = controls[++i];) {
o.$dispose();
}
};
/**
* 释放 ECUI 控件及其子控件占用的内存。
* 与公共的dispose方法相比只会清除已命名的子控件,对于未命名的控件(一般是由控件生成的)需要手动以独立模式调用$dispose进行清除工作
* 由于公共的dispose方法会对所有的控件进行包含关系检查,因此在页面有大量控件的情况下(大表格+大树)会由于大量的contain调用严重影响性能....
* @private
*
* @param {ecui.ui.Control|HTMLElement} control 需要释放的控件对象或包含控件的 Element 对象
* @param {Boolean} singel 以单独模式清除控件,该模式下不会自动清除任何子控件 如果没有子控件通过该模式能提高运行速度
*/
core.$dispose = function(control, singelMode) {
var type = control instanceof ui.Control,
namedMap = {},
controls = [],
all = [],
o, i;
if (type) {
$clearState(control);
} else {
o = findControl(getParent(control));
if (focusedControl && contain(control, focusedControl.getOuter())) {
setFocused(o);
}
if (activedControl && contain(control, activedControl.getOuter())) {
bubble(activedControl, 'deactivate', null, activedControl = o);
}
if (hoveredControl && contain(control, hoveredControl.getOuter())) {
bubble(hoveredControl, 'mouseout', null, hoveredControl = o);
}
}
for (o in namedControls) {
namedMap[namedControls[o].getUID()] = o;
all.push(namedControls[o]);
}
if (singelMode) {
all = [control];
}
for (i = 0; o = all[i]; i++) {
if (type ? control.contain(o) : !!o.getOuter() && contain(control, o.getOuter())) {
// 需要删除的控件先放入一个集合中等待遍历结束后再删除,否则控件链将产生变化
controls.push(o);
remove(independentControls, o);
remove(allControls, o);
if (o = namedMap[o.getUID()]) {
delete namedControls[o];
}
}
}
for (i = 0; o = controls[i]; i++) {
o.$dispose();
}
};
/**
* 将指定的 ECUI 控件 设置为拖拽状态。
* 只有在鼠标左键按下时,才允许调用 drag 方法设置待拖拽的 {'controls'|menu},在拖拽操作过程中,将依次触发 ondragstart、ondragmove 与 ondragend 事件。range 参数支持的属性如下:
* top {number} 控件允许拖拽到的最小Y轴坐标
* right {number} 控件允许拖拽到的最大X轴坐标
* bottom {number} 控件允许拖拽到的最大Y轴坐标
* left {number} 控件允许拖拽到的最小X轴坐标
* @public
*
* @param {ecui.ui.Control} control 需要进行拖拽的 ECUI 控件对象
* @param {ecui.ui.Event} event 事件对象
* @param {Object} range 控件允许拖拽的范围,省略参数时,控件默认只允许在 offsetParent 定义的区域内拖拽,如果
* offsetParent 是 body,则只允许在当前浏览器可视范围内拖拽
*/
drag = core.drag = function(control, event, range) {
if (event.type == 'mousedown') {
//__gzip_original__currStyle
var parent = control.getOuter().offsetParent,
style = getStyle(parent);
// 拖拽范围默认不超出上级元素区域
extend(dragEnv, parent.tagName == 'BODY' || parent.tagName == 'HTML' ? getView() : {
top: 0,
right: parent.offsetWidth - toNumber(style.borderLeftWidth) - toNumber(style.borderRightWidth),
bottom: parent.offsetHeight - toNumber(style.borderTopWidth) - toNumber(style.borderBottomWidth),
left: 0
});
extend(dragEnv, range);
dragEnv.right = MAX(dragEnv.right - control.getWidth(), dragEnv.left);
dragEnv.bottom = MAX(dragEnv.bottom - control.getHeight(), dragEnv.top);
initDragAndZoom(control, event, dragEnv, 'drag');
}
};
/**
* 获取指定名称的 ECUI 控件。
* 使用页面静态初始化或页面动态初始化(参见 ECUI 使用方式)创建的控件,如果在 ecui 属性中指定了 id,就可以通过 get 方法得到控件,也可以在 Element 对象上使用 getControl 方法。
* @public
*
* @param {string} id ECUI 控件的名称,通过 Element 对象的初始化选项 id 定义
* @return {ecui.ui.Control} 指定名称的 ECUI 控件对象,如果不存在返回 null
*/
core.get = function(id) {
initEnvironment();
return namedControls[id] || null;
};
/**
* 获取当前处于激活状态的 ECUI 控件。
* 激活状态,指鼠标在控件区域左键从按下到弹起的全过程,无论鼠标移动到哪个位置,被激活的控件对象不会发生改变。处于激活状态的控件及其父控件,都具有激活状态样式。
* @public
*
* @return {ecui.ui.Control} 处于激活状态的 ECUI 控件,如果不存在返回 null
*/
getActived = core.getActived = function() {
return activedControl || null;
};
/**
* 获取当前的初始化属性名。
* getAttributeName 方法返回页面静态初始化(参见 ECUI 使用方式)使用的属性名,通过在 BODY 节点的 data-ecui 属性中指定,默认使用 ecui 作为初始化属性名。
* @public
*
* @return {string} 当前的初始化属性名
*/
getAttributeName = core.getAttributeName = function() {
return ecuiName;
};
/**
* 获取当前处于焦点状态的控件。
* 焦点状态,默认优先处理键盘/滚轮等特殊事件。处于焦点状态的控件及其父控件,都具有焦点状态样式。通常鼠标左键的点击将使控件获得焦点状态,之前拥有焦点状态的控件将失去焦点状态。
* @public
*
* @return {ecui.ui.Control} 处于焦点状态的 ECUI 控件,如果不存在返回 null
*/
getFocused = core.getFocused = function() {
return focusedControl || null;
};
/**
* 获取当前处于悬停状态的控件。
* 悬停状态,指鼠标当前位于控件区域。处于悬停状态的控件及其父控件,都具有悬停状态样式。
* @public
*
* @return {ecui.ui.Control} 处于悬停状态的 ECUI 控件,如果不存在返回 null
*/
getHovered = core.getHovered = function() {
return hoveredControl;
};
/**
* 获取当前有效的键值码。
* getKey 方法返回最近一次 keydown 事件的 keyCode/which 值,用于解决浏览器的 keypress 事件中特殊按键(例如方向键等)没有编码值的问题。
* @public
*
* @return {number} 键值码
*/
getKey = core.getKey = function() {
return keyCode;
};
/**
* 获取当前鼠标光标的页面X轴坐标或相对于控件内部区域的X轴坐标。
* getMouseX 方法计算相对于控件内部区域的X轴坐标时,按照浏览器盒子模型的标准,需要减去 Element 对象的 borderLeftWidth 样式的值。
* @public
*
* @param {ecui.ui.Control} control ECUI 控件,如果省略参数,将获取鼠标在页面的X轴坐标,否则获取鼠标相对于控件内部区域的X轴坐标
* @return {number} X轴坐标值
*/
getMouseX = core.getMouseX = function(control) {
if (control) {
control = control.getBody();
return mouseX - getPosition(control).left - toNumber(getStyle(control, 'borderLeftWidth'));
}
return mouseX;
};
/**
* 获取当前鼠标光标的页面Y轴坐标或相对于控件内部区域的Y轴坐标。
* getMouseY 方法计算相对于控件内部区域的Y轴坐标时,按照浏览器盒子模型的标准,需要减去 Element 对象的 borderTopWidth 样式的值。
* @public
*
* @param {ecui.ui.Control} control ECUI 控件,如果省略参数,将获取鼠标在页面的Y轴坐标,否则获取鼠标相对于控件内部区域的Y轴坐标
* @return {number} Y轴坐标值
*/
getMouseY = core.getMouseY = function(control) {
if (control) {
control = control.getBody();
return mouseY - getPosition(control).top - toNumber(getStyle(control, 'borderTopWidth'));
}
return mouseY;
};
/**
* 获取所有被命名的控件。
* @public
*
* @return {Object} 所有被命名的控件集合
*/
core.getNamedControls = function() {
return extend({}, namedControls);
};
/**
* 从 Element 对象中获取初始化选项对象。
* @public
*
* @param {HTMLElement} el Element 对象
* @param {string} attributeName 当前的初始化属性名(参见 getAttributeName 方法)
* @return {Object} 初始化选项对象
*/
getOptions = core.getOptions = function(el, attributeName) {
attributeName = attributeName || ecuiName;
var text = getAttribute(el, attributeName),
options;
if (text) {
el.removeAttribute(attributeName);
if (core.onparseoptions) {
if (options = core.onparseoptions(text)) {
return options;
}
}
text = text.replace(/\:(\s*)\;/g, ":false;");
for (
options = {};
/^(\s*;)?\s*(ext\-)?([\w\-]+)\s*(:\s*([^;\s]+(\s+[^;\s]+)*)\s*)?($|;)/.test(text);
) {
text = REGEXP["$'"];
el = REGEXP.$5;
attributeName = REGEXP.$2 ? (options.ext = options.ext || {}) : options;
attributeName[toCamelCase(REGEXP.$3)] = function() {
var item = RegExp.$5;
if (/^(true|false)$/.test(item)) {
return !!(item == "true");
} else if (/^(-)?(|\.|\d)+$/.test(item)) {
if (item.length > 10) {
return item;
} else {
return Number(item);
}
} else if (item == "undefined") {
return undefined;
} else if (item == "null") {
return null;
} else {
return item;
}
}();
}
return options;
} else {
return {};
}
};
/**
* 获取浏览器滚动条的厚度。
* getScrollNarrow 方法对于垂直滚动条,返回的是滚动条的宽度,对于水平滚动条,返回的是滚动条的高度。
* @public
*
* @return {number} 浏览器滚动条相对窄的一边的长度
*/
getScrollNarrow = core.getScrollNarrow = function() {
return scrollNarrow;
};
/**
* 获取框架当前的状态。
* getStatus 方法返回框架当前的工作状态,目前支持三类工作状态:NORMAL(正常状态)、LOADING(加载状态)与REPAINT(刷新状态)
* @public
*
* @return {boolean} 框架当前的状态
*/
getStatus = core.getStatus = function() {
return status;
};
/**
* 控件继承。
* @public
*
* @param {Function} superClass 父控件类
* @param {string} type 子控件的类型样式
* @param {Function} preprocess 控件正式生成前对选项信息与主元素结构信息调整的预处理函数
* @param {Function} subClass 子控件的标准构造函数,如果忽略将直接调用父控件类的构造函数
* @return {Function} 新控件的构造函数
*/
inheritsControl = core.inherits = function(superClass, type, preprocess, subClass) {
var agent = function(options) {
return createControl(agent.client, options);
},
client = agent.client = function(el, options) {
if (agent.preprocess) {
el = agent.preprocess.call(this, el, options) || el;
}
if (superClass) {
superClass.client.call(this, el, options);
}
if (subClass) {
subClass.call(this, el, options);
}
};
agent.preprocess = preprocess;
if (superClass) {
inherits(agent, superClass);
if (type && type.charAt(0) == '*') {
(agent.types = superClass.types.slice())[0] = type.slice(1);
} else {
agent.types = (type ? [type] : []).concat(superClass.types);
}
} else {
// ecui.ui.Control的特殊初始化设置
agent.types = [];
}
agent.TYPES = ' ' + agent.types.join(' ');
inherits(client, agent);
client.agent = agent;
return agent;
};
/**
* 设置框架拦截之后的一次点击,并将点击事件发送给指定的 ECUI 控件。
* intercept 方法将下一次的鼠标点击事件转给指定控件的 $intercept 方法处理,相当于拦截了一次框架的鼠标事件点击操作,框架其它的状态不会自动改变,例如拥有焦点的控件不会改变。如果 $intercept 方法不阻止冒泡,将自动调用 restore 方法。
* @public
*
* @param {ecui.ui.Control} control ECUI 控件
*/
intercept = core.intercept = function(control) {
interceptEnv.target = control;
setEnv(interceptEnv);
};
/**
* 判断容器默认是否基于 content-box 属性进行布局。
* isContentBox 返回的是容器默认的布局方式,针对具体的元素,需要访问 box-sizing 样式来确认它的布局方式。
* @public
*
* @return {boolean} 容器是否使用 content-box 属性布局
*/
isContentBox = core.isContentBox = function() {
return flgContentBox;
};
/**
* 使控件失去焦点。
* loseFocus 方法不完全是 setFocused 方法的逆向行为。如果控件及它的子控件不处于焦点状态,执行 loseFocus 方法不会发生变化。如果控件或它的子控件处于焦点状态,执行 loseFocus 方法将使控件失去焦点状态,如果控件拥有父控件,此时父控件获得焦点状态。
* @public
*
* @param {ecui.ui.Control} control ECUI 控件
*/
loseFocus = core.loseFocus = function(control) {
if (control.contain(focusedControl)) {
setFocused(control.getParent());
}
};
/**
* 使用一个层遮罩整个浏览器可视化区域。
* 遮罩层的 z-index 样式默认取值为 32767,请不要将 Element 对象的 z-index 样式设置大于 32767。当框架中至少一个遮罩层工作时,body 标签将增加一个样式 ecui-mask,IE6/7 的原生 select 标签可以使用此样式进行隐藏,解决强制置顶的问题。
* @public
*
* @param {number} opacity 透明度,如 0.5,如果省略参数将关闭遮罩层
* @param {number} zIndex 遮罩层的 zIndex 样式值,如果省略使用 32767
*/
mask = core.mask = function(opacity, zIndex) {
//__gzip_original__body
var i = 0,
body = DOCUMENT.body,
o = getView(),
// 宽度向前扩展2屏,向后扩展2屏,是为了解决翻屏滚动的剧烈闪烁问题
// 不直接设置为整个页面的大小,是为了解决IE下过大的遮罩层不能半透明的问题
top = MAX(o.top - o.height * 2, 0),
left = MAX(o.left - o.width * 2, 0),
text = ';top:' + top + 'px;left:' + left +
'px;width:' + MIN(o.width * 5, o.pageWidth - left) +
'px;height:' + MIN(o.height * 5, o.pageHeight - top) + 'px;display:';
if ('boolean' == typeof opacity) {
text += opacity ? 'block' : 'none';
for (; o = maskElements[i++];) {
o.style.cssText += text;
}
} else if (opacity === undefined) {
removeDom(maskElements.pop());
if (!maskElements.length) {
removeClass(body, 'ecui-mask');
}
} else {
if (!maskElements.length) {
addClass(body, 'ecui-mask');
}
maskElements.push(o = body.appendChild(createDom(
'',
'position:absolute;background-color:#000;z-index:' + (zIndex || 32767)
)));
setStyle(o, 'opacity', opacity);
if (ieVersion < 8) {
o.innerHTML = '<iframe style="width:100%;filter:alpha(opacity=0);-moz-opacity:0; height:100%; position:absolute; z-index:' + (zIndex - 2) + '"></iframe>';
//mask cannot be removed bug fix
try {
var ifrm = baidu.dom.query('iframe', o)[0];
ifrm.contentWindow.document.onclick = function() {
o.removeChild(ifrm);
baidu.event.fire(o, 'mousedown');
baidu.event.fire(body, 'mousedown');
};
} catch (ex) {}
}
o.style.cssText += text + 'block';
}
};
/**
* 判断是否需要初始化 class 属性。
* @public
*
* @return {boolean} 是否需要初始化 class 属性
*/
needInitClass = core.needInitClass = function() {
return !structural;
};
/**
* 查询满足条件的控件列表。
* query 方法允许按多种条件组合查询满足需要的控件,如果省略条件表示不进行限制。condition参数对象支持的属性如下:
* type {Function} 控件的类型构造函数
* parent {ecui.ui.Control} 控件的父控件对象
* custom {Function} 自定义查询函数,传入的参数是控件对象,query 方法会将自己的 this 指针传入查询函数中