ecui
Version:
Enterprise Classic User Interface.
769 lines (684 loc) • 25.6 kB
JavaScript
/*
Suggest - 定义模拟下拉框行为的基本操作。
下拉框控件,继承自输入控件,实现了选项组接口,扩展了原生 SelectElement 的功能,允许指定下拉选项框的最大选项数量,在屏幕显示不下的时候,会自动显示在下拉框的上方。在没有选项时,下拉选项框有一个选项的高度。下拉框控件允许使用键盘与滚轮操作,在下拉选项框打开时,可以通过回车键或鼠标点击选择,上下键选择选项的当前条目,在关闭下拉选项框后,只要拥有焦点,就可以通过滚轮上下选择选项。
下拉框控件直接HTML初始化的例子:
<div ecui="type:suggest;">
</div>
属性
_nOptionSize - 下接选择框可以用于选择的条目数量
_cSelected - 当前选中的选项
_uText - suggest的文本框
_uOptions - 下拉选择框
*/
//{if 0}//
; (function() {
var core = ecui,
array = core.array,
dom = core.dom,
string = core.string,
ui = core.ui,
util = core.util,
trim = string.trim,
undefined, DOCUMENT = document,
MATH = Math,
MAX = MATH.max,
MIN = MATH.min,
indexOf = array.indexOf,
children = dom.children,
createDom = dom.create,
getParent = dom.getParent,
getPosition = dom.getPosition,
getText = dom.getText,
insertAfter = dom.insertAfter,
insertBefore = dom.insertBefore,
moveElements = dom.moveElements,
removeDom = dom.remove,
encodeHTML = string.encodeHTML,
extend = util.extend,
getView = util.getView,
setDefault = util.setDefault,
$fastCreate = core.$fastCreate,
getAttributeName = core.getAttributeName,
getFocused = core.getFocused,
inheritsControl = core.inherits,
intercept = core.intercept,
mask = core.mask,
restore = core.restore,
setFocused = core.setFocused,
triggerEvent = core.triggerEvent,
UI_INPUT_CONTROL = ui.InputControl,
UI_INPUT_CONTROL_CLASS = UI_INPUT_CONTROL.prototype,
UI_BUTTON = ui.Button,
UI_SCROLLBAR = ui.Scrollbar,
UI_PANEL = ui.Panel,
UI_PANEL_CLASS = UI_PANEL.prototype,
UI_ITEM = ui.Item,
UI_ITEM_CLASS = UI_ITEM.prototype,
UI_ITEMS = ui.Items;
//{/if}//
//{if $phase == "define"}//
///__gzip_original__UI_SELECT
///__gzip_original__UI_SUGGEST_CLASS
/**
* 初始化下拉框控件。
* options 对象支持的属性如下:
* browser 是否使用浏览器原生的滚动条,默认使用模拟的滚动条
* optionSize 下拉框最大允许显示的选项数量,默认为10
* optionsElement 下拉选项主元素
* @public
*
* @param {Object} options 初始化选项
*/
var UI_SUGGEST = ui.Suggest = inheritsControl(UI_INPUT_CONTROL, 'ui-suggest',
function(el, options) {
var me = this;
var name = el.name || options.name || '',
type = this.getType(),
id = options.id || 'id_notset',
optionsEl = createDom(type + '-options' + this.Options.TYPES, 'position:absolute;z-index:65535;display:none');
optionsEl.setAttribute('ecui_id', id);
setDefault(options, 'hidden', true);
moveElements(el, optionsEl);
el.innerHTML = '<span class="ui-input"></span><input name="' + name + '" value="' + encodeHTML(options.value || '') + '">';
el.appendChild(optionsEl);
//延迟400ms触发query事件
this._nTimeout = 400;
//延迟的句柄
this._nTimeoutHandler = null;
return el;
},
function(el, options) {
if (options.timeout) {
this._nTimeout = options.timeout;
}
if (options.hide) {
this.getOuter().style.display = 'none';
}
el = children(el);
var me = this;
//上次输入的值
this._nLastText = '';
this._uText = $fastCreate(UI_INPUT_CONTROL, el[0], this, {
capturable: false
});
this._uOptions = $fastCreate(this.Options, removeDom(el[2]), this, {
hScroll: false,
browser: options.browser
});
this.$setBody(this._uOptions.getBody());
// 初始化下拉区域最多显示的选项数量
this._nOptionSize = options.optionSize || 10;
this.$initItems();
//注册change事件
this._uText.$change = EVENT_TEXT_CHANGE;
//取消滚轮事件
this._uText.$mousewheel = function() {};
}),
UI_SUGGEST_CLASS = UI_SUGGEST.prototype,
/**
* 初始化下拉框控件的下拉选项框部件。
* @public
*
* @param {Object} options 初始化选项
*/
UI_SUGGEST_OPTIONS_CLASS = (UI_SUGGEST_CLASS.Options = inheritsControl(UI_PANEL)).prototype,
/**
* 初始化下拉框控件的选项部件。
* @public
*
* @param {Object} options 初始化选项
*/
UI_SUGGEST_ITEM_CLASS = (UI_SUGGEST_CLASS.Item = inheritsControl(UI_ITEM, null, null,
function(el, options) {
this._sValue = options.value === undefined ? getText(el) : '' + options.value;
})).prototype;
/**
* 事件处理
* @event 注册input文本框的onchange事件
*/
function EVENT_TEXT_CHANGE() {
var par = this.getParent();
var value = par.getValue();
//触发onchange事件
triggerEvent(par, 'change', value);
var txt = par.getText();
var lastTxt = par._nLastText;
//trim后没有内容 所以不查找
if (trim(txt) == '') {
par._eInput.value = '';
return;
}
//如果输入不相符 就致空value
if (trim(txt) != trim(lastTxt)) {
par._eInput.value = '';
}
//因为改变了文本,理论值不一样了
//触发onquery事件
if (par._nTimeoutHandler) {
//清除之前的句柄
clearTimeout(par._nTimeoutHandler);
}
//延迟触发onquery事件
par._nTimeoutHandler = setTimeout(function() {
//清空事件句柄
par._nTimeoutHandler = null;
triggerEvent(par, 'query', value);
},
par._nTimeout);
}
/**
* 填充输入的文字自动匹配value值
* 用户输入文本以后 寻找相关的id,并填入sugguest框
* @param {ecui{Object}} suggest控件本身
* @param {array[Object]} suggest控件本身
* @param {string} suggest控件本身的text
*/
function AUTO_FILL_VALUE(ele, list, text) {
list = list || [];
var value = null;
for (var i = 0,
item; item = list[i++];) {
if (item.text == text) {
value = item.value;
break;
}
}
//渲染
if (value != null) {
ele._eInput.value = value;
//触发onslect
triggerEvent(ele, 'select', {
value: value,
text: text
});
}
}
//{else}//
/**
* 下拉框刷新。
* @private
*
* @param {ecui.ui.Select} control 下拉框控件
*/
function UI_SUGGEST_FLUSH(control) {
var options = control._uOptions,
scrollbar = options.$getSection('VScrollbar'),
el = options.getOuter(),
pos = getPosition(control.getOuter()),
selected = control._cSelected,
optionTop = pos.top + control.getHeight();
if (!getParent(el)) {
// 第一次显示时需要进行下拉选项部分的初始化,将其挂载到 DOM 树中
DOCUMENT.body.appendChild(el);
control.cache(false, true);
control.$alterItems();
} else {
control.cache(false, true);
control.$alterItems();
}
if (options.isShow()) {
if (selected) {
//setFocused(selected);
}
scrollbar.setValue(scrollbar.getStep() * indexOf(control.getItems(), selected));
// 以下使用control代替optionHeight
control = options.getHeight();
// 如果浏览器下部高度不够,将显示在控件的上部
options.setPosition(pos.left, optionTop + control <= getView().bottom ? optionTop: pos.top - control);
}
}
/**
* 改变下拉框当前选中的项。
* @private
* @param {ecui.ui.Select} control 下拉框控件
* @param {ecui.ui.Select.Item} item 新选中的项
*/
function UI_SUGGEST_CHANGE_SELECTED(control, item) {
if (item !== control._cSelected) {
UI_INPUT_CONTROL_CLASS.setValue.call(control, item ? item._sValue: '');
var text = item ? item.getBody().innerHTML: '';
control._uText.setValue(text);
//FIX: setValue为一个对象
control._cSelected = item;
if (control._uOptions.isShow()) {
setFocused(item);
}
//设置选中的值
if (item && item._sValue) {
//suggest选择事件
//选择事件的触发
//
triggerEvent(control, 'select', {
value: item._sValue,
text: text
});
triggerEvent(control, item._sValue);
//设置焦点到最后
//bugfix ie可能会选择以后会有显示在前边问题
setFocused(control);
control.setFocusToEnd();
}
}
}
extend(UI_SUGGEST_CLASS, UI_ITEMS);
/**
* 销毁选项框部件时需要检查是否展开,如果展开需要先关闭。
* @override
*/
UI_SUGGEST_OPTIONS_CLASS.$dispose = function() {
this.hide();
UI_PANEL_CLASS.$dispose.call(this);
};
/**
* 关闭选项框部件时,需要恢复强制拦截的环境。
* @override
*/
UI_SUGGEST_OPTIONS_CLASS.$hide = function() {
UI_PANEL_CLASS.$hide.call(this);
mask();
restore();
};
/**
* 对于下拉框选项,鼠标移入即自动获得焦点。
* @override
*/
UI_SUGGEST_ITEM_CLASS.$mouseover = function(event) {
UI_ITEM_CLASS.$mouseover.call(this, event);
setFocused(this);
};
/**
* 获取选项的值。
* getValue 方法返回选项控件的值,即选项选中时整个下拉框控件的值。
* @public
*
* @return {string} 选项的值
*/
UI_SUGGEST_ITEM_CLASS.getValue = function() {
return this._sValue;
};
/**
* 获取选项的值。
* getText 方法返回选项控件的值,即选项选中时整个下拉框控件的值。
* @public
*
* @return {string} 选项的值
*/
UI_SUGGEST_ITEM_CLASS.getText = function() {
return this._eBody.innerHTML;
};
/**
* 设置选项的值。
* setValue 方法设置选项控件的值,即选项选中时整个下拉框控件的值。
* @public
*
* @param {string} value 选项的值
*/
UI_SUGGEST_ITEM_CLASS.setValue = function(value) {
var parent = this.getParent();
this._sValue = value;
if (parent && this == parent._cSelected) {
// 当前被选中项的值发生变更需要同步更新控件的值
UI_INPUT_CONTROL_CLASS.setValue.call(parent, value);
}
};
/**
* 下拉框控件激活时,显示选项框,产生遮罩层阻止对页面内 DOM 节点的点击,并设置框架进入强制点击拦截状态。
* @override
*/
UI_SUGGEST_CLASS.$activate = function(event) {
if (! (event.getControl() instanceof UI_SCROLLBAR)) {
UI_INPUT_CONTROL_CLASS.$activate.call(this, event);
//这里不需要弹出层 ???
return;
this._uOptions.show();
// 拦截之后的点击,同时屏蔽所有的控件点击事件
intercept(this);
mask(0, 65534);
UI_SUGGEST_FLUSH(this);
event.stopPropagation();
}
};
/**
* 选项控件发生变化的处理。
* 在 选项组接口 中,选项控件发生添加/移除操作时调用此方法。虚方法,子控件必须实现。
* @protected
*/
UI_SUGGEST_CLASS.$alterItems = function() {
var options = this._uOptions,
scrollbar = options.$getSection('VScrollbar'),
optionSize = this._nOptionSize,
step = this.getBodyHeight(),
width = this.getWidth(),
itemLength = this.getItems().length;
if (getParent(options.getOuter())) {
// 设置选项框
scrollbar.setStep(step);
// 为了设置激活状态样式, 因此必须控制下拉框中的选项必须在滚动条以内
this.setItemSize(width - options.getMinimumWidth() - (itemLength > optionSize ? scrollbar.getWidth() : 0), step);
// 设置options框的大小,如果没有元素,至少有一个单位的高度
options.$$mainHeight = itemLength * step + options.$$bodyHeightRevise;
options.$setSize(width, (MIN(itemLength, optionSize) || 1) * step + options.getMinimumHeight());
}
};
/**
* @override
*/
UI_SUGGEST_CLASS.$cache = function(style, cacheSize) { (getParent(this._uOptions.getOuter()) ? UI_ITEMS: UI_INPUT_CONTROL_CLASS).$cache.call(this, style, cacheSize);
this._uText.cache(false, true);
this._uOptions.cache(false, true);
};
/**
* 控件在下拉框展开时,需要拦截浏览器的点击事件,如果点击在下拉选项区域,则选中当前项,否则直接隐藏下拉选项框。
* @override
*/
UI_SUGGEST_CLASS.$intercept = function(event) {
//__transform__control_o
this._uOptions.hide();
for (var control = event.getControl(); control; control = control.getParent()) {
if (control instanceof this.Item) {
if (control != this._cSelected) {
// 检查点击是否在当前下拉框的选项上
UI_SUGGEST_CHANGE_SELECTED(this, control);
//onchange事件需要参数 在这里获取并发送
var obj = {
text: control.getText(),
value: control._sValue
};
triggerEvent(this, 'change', obj);
//设置焦点到最后
//bugfix ie可能会选择以后会有显示在前边问题
//不需要这里调用了 直接在选中的时候调用
//setFocused(this);
//this.setFocusToEnd();
}
break;
}
}
event.exit();
};
/**
* 接管对上下键与回车/ESC键的处理。
* @override
*/
UI_SUGGEST_CLASS.$keydown = UI_SUGGEST_CLASS.$keypress = function(event) {
UI_INPUT_CONTROL_CLASS['$' + event.type](event);
var options = this._uOptions,
scrollbar = options.$getSection('VScrollbar'),
optionSize = this._nOptionSize,
which = event.which,
list = this.getItems(),
length = list.length,
focus = getFocused();
if (this.isFocused()) {
// 当前不能存在鼠标操作,否则屏蔽按键
if (which == 40 || which == 38) {
//bugfix
//shift + 40 是( ,shift + 38 是 &
if (event && event._oNative.shiftKey) {
return true;
}
if (length) {
if (options.isShow()) {
var uFocus = list[which = MIN(MAX(0, indexOf(list, focus) + which - 39), length - 1)];
setFocused(uFocus);
which -= scrollbar.getValue() / scrollbar.getStep();
scrollbar.skip(which < 0 ? which: which >= optionSize ? which - optionSize + 1 : 0);
event.cancelBubble = true;
} else {
//不需要选择列表里的item
return false;
//this.setSelectedIndex(MIN(MAX(0, indexOf(list, this._cSelected) + which - 39), length - 1));
}
}
return false;
} else if (which == 27 || which == 13 && options.isShow()) {
// 回车键选中,ESC键取消
options.hide();
if (which == 13) {
if (focus instanceof this.Item) {
UI_SUGGEST_CHANGE_SELECTED(this, focus);
//onchange事件需要参数 在这里获取并发送
var obj = {
text: focus.getText(),
value: focus._sValue
};
//触发change事件
triggerEvent(this, 'change', obj);
}
}
return false;
} else {
//可以支持用户继续输入
//bugfix: 这里有一个bug,keypress和keydown,which可能是0 所以不用处理不然firefox会丢失 item的焦点
if (which != 0) {
setFocused(this._uText);
}
}
}
};
/**
* 如果控件拥有焦点,则当前选中项随滚轮滚动而自动指向前一项或者后一项。
* @override
*/
UI_SUGGEST_CLASS.$mousewheel = function(event) {
if (this.isFocused()) {
var options = this._uOptions,
list = this.getItems(),
length = list.length;
if (options.isShow()) {
options.$mousewheel(event);
} else {
//options表示当前选项的index
options = indexOf(list, this._cSelected) + (event.detail > 0 ? 1 : -1);
this.setSelectedIndex(length ? MIN(MAX(0, options), length - 1) : null);
if (options >= 0 && options < length) {
//鼠标滚动触发change事件
//triggerEvent(this, 'change');
}
}
event.exit();
}
};
/**
* @override
*/
UI_SUGGEST_CLASS.$ready = function() {
this.setValue(this.getValue());
};
/**
* 下拉框移除子选项时,如果选项是否被选中,需要先取消选中。
* @override
*/
UI_SUGGEST_CLASS.remove = function(item) {
if ('number' == typeof item) {
item = this.getItems()[item];
}
if (item == this._cSelected) {
//UI_SUGGEST_CHANGE_SELECTED(this);
}
return UI_ITEMS.remove.call(this, item);
};
/**
* 添加选项需要根据情况继续cache操作
* @override
*/
UI_SUGGEST_CLASS.add = function(item, index, options) {
item = UI_ITEMS.add.call(this, item, index, options);
if (getParent(this._uOptions.getOuter())) {
item.cache(true, true);
}
return item;
};
/**
* @override
*/
UI_SUGGEST_CLASS.$setSize = function(width, height) {
UI_INPUT_CONTROL_CLASS.$setSize.call(this, width, height);
this.$locate();
height = this.getBodyHeight();
// 设置文本区域 不需要减去height
// bugfix: 并非select 所以不要右侧的下拉箭头
this._uText.$setSize(width = this.getBodyWidth(), height);
//this._uText.$setSize(width = this.getBodyWidth() - height, height);
};
/**
* 获取被选中的选项控件。
* @public
*
* @return {ecui.ui.Item} 选项控件
*/
UI_SUGGEST_CLASS.getSelected = function() {
return this._cSelected || null;
};
/**
* 获取选项的文本
* @return {string} 选择的文本
*/
UI_SUGGEST_CLASS.getText = function() {
var txt = this._uText.getValue();
return txt;
};
/**
* 获取选项的文本
* @param {string} 设置文本
* @return {string} 选择的文本
*/
UI_SUGGEST_CLASS.setText = function(txt) {
this._uText.setValue(txt);
};
/**
* 获取suggest的文本框里的值
*/
UI_SUGGEST_CLASS.getValue = function() {
var value = this._eInput.value;
var text = this.getText();
var obj = {
value: value,
text: text
};
return obj;
};
/**
* 设置下拉框允许显示的选项数量。
* 如果实际选项数量小于这个数量,没有影响,否则将出现垂直滚动条,通过滚动条控制其它选项的显示。
* @public
*
* @param {number} value 显示的选项数量,必须大于 1
*/
UI_SUGGEST_CLASS.setOptionSize = function(value) {
this._nOptionSize = value;
this.$alterItems();
UI_SUGGEST_FLUSH(this);
};
/**
* 根据序号选中选项。
* @public
*
* @param {number} index 选项的序号
*/
UI_SUGGEST_CLASS.setSelectedIndex = function(index) {
UI_SUGGEST_CHANGE_SELECTED(this, this.getItems()[index]);
};
/**
* 设置控件的值。
* setValue 方法设置控件的值,设置的值必须与一个子选项的值相等,否则将被设置为空,使用 getValue 方法获取设置的值。
* @public
*
* @param {string} value 需要选中的值
*/
UI_SUGGEST_CLASS.setValue = function(oValue) {
//{text:XX,value:XX}
var value = oValue;
if ('[object Object]' == Object.prototype.toString.call(oValue)) {
value = oValue.value;
}
for (var i = 0,
list = this.getItems(), o; o = list[i++];) {
if (o._sValue == value) {
UI_SUGGEST_CHANGE_SELECTED(this, o);
//text
this._nLastText = oValue.text || o.getBody().innerHTML;
return;
}
}
// 找不到满足条件的项,将选中的值清除
UI_SUGGEST_CHANGE_SELECTED(this);
};
/**
* 清除suggest里的内容
*/
UI_SUGGEST_CLASS.clear = function() {
var items = this.getItems() || [],
len = items.length;
while (len-->0) {
this.remove(0);
}
this._uOptions.reset();
};
UI_SUGGEST_CLASS.setInitValue = function(o) {
//清空
this.clear();
var item = null;
var el = null;
var control = this;
item = this.add(o.text, null, {
value: o.value
});
control.$alterItems();
this.setValue(o);
};
/**
* 重新渲染suggest里的内容
* @param {Array[Object]} 数据源
*/
UI_SUGGEST_CLASS.update = function(list) {
//清空
this.clear();
var item = null;
var el = null;
var control = this;
for (var i = 0, o; o = list[i++];) {
item = this.add(o.text, null, {
value: o.value
});
//以后可以增加title的标识的变量
if (true) {
item.getOuter().title = o.text;
}
}
if (!this._uOptions.isShow()) {
this._uOptions.show();
// 拦截之后的点击,同时屏蔽所有的控件点击事件
intercept(this);
mask(0, 65534);
UI_SUGGEST_FLUSH(this);
} else {
control.$alterItems();
}
//自动填充相关id,用户
var txt = this.getText(txt);
AUTO_FILL_VALUE(this, list, txt);
//updae控件以后需要focus到文本框
setFocused(control._uText);
};
//聚焦到最后
UI_SUGGEST_CLASS.setFocusToEnd = function() {
var input = this._uText;
core.setFocused(input);
input = input._eInput;
var len = input.value.length;
if (document.selection) {
var sel = input.createTextRange();
sel.moveStart('character', len);
sel.collapse();
sel.select();
} else if (typeof input.selectionStart == 'number' && typeof input.selectionEnd == 'number') {
input.selectionStart = input.selectionEnd = len;
}
};
UI_SUGGEST_CLASS.$mousewheel = function() {
};
//{/if}//
//{if 0}//
})();
//{/if}//