combox源码解析

/**
* jQuery EasyUI 1.3.2
*
* Copyright (c) 2009-2013 www.jeasyui.com. All rights reserved.
*
* Licensed under the GPL or commercial licenses To use it on other terms please
* contact us: jeasyui@gmail.com http://www.gnu.org/licenses/gpl.txt
* http://www.jeasyui.com/license_commercial.php
* 注释由小雪完成,更多组件源码内容到www.easyui.info上搜索"easyui源码分析"关键字
* 该源码完全由压缩码翻译而来,并非网络上放出的源码,请勿索要。
*/
(function($) {
/**
* 滚动选项到合适位置:
* 如果item被卷在panel上方,则滚动到panel可见区的最上方;
* 如果item被卷在panel下方,则滚动到panel可见区的最下方;
* parmas[target] 承载combobox的DOM
* params[value] valueField字段对应的某个值
*/
function scrollItem(target, value) {
var panel = $(target).combo("panel");
var item = panel.find("div.combobox-item[value=\"" + value + "\"]");
if (item.length) {
if (item.position().top <= 0) {
var h = panel.scrollTop() + item.position().top;
panel.scrollTop(h);
} else {
if (item.position().top + item.outerHeight() > panel.height()) {
var h = panel.scrollTop() + item.position().top
+ item.outerHeight() - panel.height();
panel.scrollTop(h);
}
}
}
};
/**
* 选中上一个可见选项,如果该选项被滚动条卷去,则滚动该选项到合适位置。
* parmas[target] 承载combobox的DOM
*/
function selectPrev(target) {
var panel = $(target).combo("panel");
var values = $(target).combo("getValues");
var item = panel.find("div.combobox-item[value=\"" + values.pop() + "\"]");
if (item.length) {
var prev = item.prev(":visible");
if (prev.length) {
item = prev;
}
} else {
item = panel.find("div.combobox-item:visible:last");
}
var value = item.attr("value");
select(target, value);
scrollItem(target, value);
};
/**
* 选中下一个可见选项,如果该选项被滚动条卷去,则滚动该选项到合适位置。
* parmas[target] 承载combobox的DOM
*/
function selectNext(target) {
var panel = $(target).combo("panel");
var values = $(target).combo("getValues");
var item = panel.find("div.combobox-item[value=\"" + values.pop() + "\"]");
if (item.length) {
var next = item.next(":visible");
if (next.length) {
item = next;
}
} else {
item = panel.find("div.combobox-item:visible:first");
}
var value = item.attr("value");
select(target, value);
scrollItem(target, value);
};
/**
* 选中指定valueField值的项
* 选中后调用onSelect事件
* parmas[target] 承载combobox的DOM
* params[value] valueField字段对应的某个值
*/
function select(target, value) {
var opts = $.data(target, "combobox").options;
var data = $.data(target, "combobox").data;
if (opts.multiple) {
var values = $(target).combo("getValues");
for (var i = 0; i < values.length; i++) {
if (values[i] == value) {
return;
}
}
values.push(value);
setValues(target, values);
} else {
setValues(target, [value]);
}
for (var i = 0; i < data.length; i++) {
if (data[i][opts.valueField] == value) {
opts.onSelect.call(target, data[i]);
return;
}
}
};
/**
* 取消选中指定valueField值的项
* 取消选中后调用onUnselect事件
* parmas[target] 承载combobox的DOM
* params[value] valueField字段对应的某个值
*/
function unselect(target, value) {
var opts = $.data(target, "combobox").options;
var data = $.data(target, "combobox").data;
var values = $(target).combo("getValues");
for (var i = 0; i < values.length; i++) {
if (values[i] == value) {
values.splice(i, 1);
setValues(target, values);
break;
}
}
for (var i = 0; i < data.length; i++) {
if (data[i][opts.valueField] == value) {
opts.onUnselect.call(target, data[i]);
return;
}
}
};
/**
* 设置combobox的值
* parmas[target] 承载combobox的DOM
* params[values] valueField字段对应的多个值
* params[remainText] 是保留文本,为false将重新设置文本
*/
function setValues(target, values, remainText) {
var opts = $.data(target, "combobox").options;
var data = $.data(target, "combobox").data;
var panel = $(target).combo("panel");
panel.find("div.combobox-item-selected")
.removeClass("combobox-item-selected");
var vv = [], ss = [];
for (var i = 0; i < values.length; i++) {
var v = values[i];
var s = v;
for (var j = 0; j < data.length; j++) {
if (data[j][opts.valueField] == v) {
s = data[j][opts.textField];
break;
}
}
vv.push(v);
ss.push(s);
panel.find("div.combobox-item[value=\"" + v + "\"]")
.addClass("combobox-item-selected");
}
$(target).combo("setValues", vv);
if (!remainText) {
$(target).combo("setText", ss.join(opts.separator));
}
};
/**
* 从select标签的option中获取combobox的data
* parmas[target] 承载combobox的DOM
*/
function getDataTag(target) {
var opts = $.data(target, "combobox").options;
var data = [];
$(">option", target).each(function() {
var item = {};
item[opts.valueField] = $(this).attr("value") != undefined ? $(this)
.attr("value") : $(this).html();
item[opts.textField] = $(this).html();
item["selected"] = $(this).attr("selected");
data.push(item);
});
return data;
};
/**
* 装载数据
* parmas[target] 承载combobox的DOM
* params[data] 要装载的数据,从远程url获取或者开发者传入的数组
* params[remainText] 是保留文本,为false将重新设置文本
*/
function loadData(target, data, remainText) {
var opts = $.data(target, "combobox").options;
var panel = $(target).combo("panel");
//先将数据存储到与target绑定的对象上
$.data(target, "combobox").data = data;
//获取values,注意这里是从input.combo-value隐藏域中获取的
var values = $(target).combobox("getValues");
//清空下拉面板中的列表选项
panel.empty();
//根据data循环生成下拉面板列表选项
for (var i = 0; i < data.length; i++) {
var v = data[i][opts.valueField];
var s = data[i][opts.textField];
var item = $("<div class=\"combobox-item\"></div>").appendTo(panel);
item.attr("value", v);
if (opts.formatter) {
//如果定义了formatter,则将formatter返回的DOM结构填充到div.combobox-item里
//formatter入参为data元素
item.html(opts.formatter.call(target, data[i]));
} else {
//直接将文本填充到div.combobox-item里
item.html(s);
}
//如果某个data元素设置了selected属性(即默认值,注意可以有多个的),
//则将默认值与现有的隐藏域列表的值相比较,如果不在该列表中,则添加进去。
//注意这里只是增加了values数组的元素个数并未处理隐藏域列表,隐藏域列表的处理放在setValues方法里。
if (data[i]["selected"]) {
(function() {
for (var i = 0; i < values.length; i++) {
if (v == values[i]) {
return;
}
}
values.push(v);
})();
}
}
//设置值,设置的时候会处理隐藏域列表
if (opts.multiple) {//复选
setValues(target, values, remainText);
} else {//单选
if (values.length) {
setValues(target, [values[values.length - 1]], remainText);
} else {
setValues(target, [], remainText);
}
}
//此处触发onLoadSuccess事件,入参为data
opts.onLoadSuccess.call(target, data);
//绑定下拉面板选项的hover和click事件
//这个地方用事件委托是不是要好点呢?可以节省不少资源。
$(".combobox-item", panel).hover(function() {
$(this).addClass("combobox-item-hover");
}, function() {
$(this).removeClass("combobox-item-hover");
}).click(function() {
var item = $(this);
if (opts.multiple) {
if (item.hasClass("combobox-item-selected")) {
unselect(target, item.attr("value"));
} else {
select(target, item.attr("value"));
}
} else {
select(target, item.attr("value"));
$(target).combo("hidePanel");
}
});
};
/**
* 请求远程数据
* parmas[target] 承载combobox的DOM
* params
 
 请求数据的远程url
* params[param] 发送给远程url的查询参数
* params[remainText] 是保留文本,为false将重新设置文本
*/
function request(target, url, param, remainText) {
var opts = $.data(target, "combobox").options;
if (url) {
opts.url = url;
}
param = param || {};
//触发onBeforeLoad事件,返回false将取消数据请求
if (opts.onBeforeLoad.call(target, param) == false) {
return;
}
//loader为配适器,用于定义如何获取远程数据
opts.loader.call(target, param, function(data) {
//请求成功的话,装载请求到的数据
loadData(target, data, remainText);
}, function() {
//触发请求出错事件
opts.onLoadError.apply(this, arguments);
});
};
/**
* 数据过滤(本地)或者请求(远程)
* parmas[target] 承载combobox的DOM
* parmas[q] 用户输入的文本
*/
function doQuery(target, q) {
var opts = $.data(target, "combobox").options;
//设置values?谁会输入valueField呢?q为text,把text作为value带进去是什么意思呢?
//个人觉得这个地方不合理
if (opts.multiple && !q) {
setValues(target, [], true);
} else {
setValues(target, [q], true);
}
if (opts.mode == "remote") {//如果为remote模式,则请求远程数据
request(target, null, {
q : q
}, true);
} else {//本地模式
var panel = $(target).combo("panel");
//隐藏所有下拉选项
panel.find("div.combobox-item").hide();
var data = $.data(target, "combobox").data;
for (var i = 0; i < data.length; i++) {
//如果根据text过滤到(过滤规则为:包含用户输入值即匹配)匹配选项,则显示、设置选项。
if (opts.filter.call(target, q, data[i])) {
var v = data[i][opts.valueField];
var s = data[i][opts.textField];
var item = panel.find("div.combobox-item[value=\"" + v + "\"]");
//显示item
item.show();
if (s == q) {//完全匹配(即完全等于)
//设置values
setValues(target, [v], true);
//添加选中样式
item.addClass("combobox-item-selected");
}
}
}
}
};
/**
* 实例化combo组件
* parmas[target] 承载combobox的DOM
*/
function create(target) {
var opts = $.data(target, "combobox").options;
$(target).addClass("combobox-f");
$(target).combo($.extend({}, opts, {
onShowPanel : function() {
$(target).combo("panel").find("div.combobox-item").show();
scrollItem(target, $(target).combobox("getValue"));
opts.onShowPanel.call(target);
}
}));
};
//构造函数
$.fn.combobox = function(options, params) {
if (typeof options == "string") {
var method = $.fn.combobox.methods[options];
if (method) {
//有mothed则调用之
return method(this, params);
} else {
//没方法,则继承combo组件的同名方法
return this.combo(options, params);
}
}
options = options || {};
return this.each(function() {
var state = $.data(this, "combobox");
if (state) {
//更新属性数据
$.extend(state.options, options);
//创建combo
create(this);
} else {
//绑定属性数据到target上
state = $.data(this, "combobox", {
options : $.extend({},
$.fn.combobox.defaults,
$.fn.combobox.parseOptions(this),
options)
});
//创建combo
create(this);
//从html中装载数据
loadData(this, getDataTag(this));
}
if (state.options.data) {
//装载指定的数组
loadData(this, state.options.data);
}
//请求远程数据
request(this);
});
};
//定义对外接口方法
$.fn.combobox.methods = {
options : function(jq) {
var opts = $.data(jq[0], "combobox").options;
opts.originalValue = jq.combo("options").originalValue;
return opts;
},
getData : function(jq) {
return $.data(jq[0], "combobox").data;
},
setValues : function(jq, values) {
return jq.each(function() {
setValues(this, values);
});
},
setValue : function(jq, value) {
return jq.each(function() {
setValues(this, [value]);
});
},
clear : function(jq) {
return jq.each(function() {
$(this).combo("clear");
var panel = $(this).combo("panel");
panel.find("div.combobox-item-selected")
.removeClass("combobox-item-selected");
});
},
reset : function(jq) {
return jq.each(function() {
var opts = $(this).combobox("options");
if (opts.multiple) {
$(this).combobox("setValues", opts.originalValue);
} else {
$(this).combobox("setValue", opts.originalValue);
}
});
},
loadData : function(jq, data) {
return jq.each(function() {
loadData(this, data);
});
},
reload : function(jq, url) {
return jq.each(function() {
request(this, url);
});
},
select : function(jq, value) {
return jq.each(function() {
select(this, value);
});
},
unselect : function(jq, value) {
return jq.each(function() {
unselect(this, value);
});
}
};
//定义属性转换器
$.fn.combobox.parseOptions = function(target) {
var t = $(target);
return $.extend({}, $.fn.combo.parseOptions(target), $.parser
.parseOptions(target, ["valueField", "textField", "mode",
"method", "url"]));
};
//定义属性和事件的默认值
$.fn.combobox.defaults = $.extend({}, $.fn.combo.defaults, {
valueField : "value",
textField : "text",
mode : "local",
method : "post",
url : null,
data : null,
keyHandler : {
up : function() {
selectPrev(this);
},
down : function() {
selectNext(this);
},
enter : function() {
var values = $(this).combobox("getValues");
$(this).combobox("setValues", values);
$(this).combobox("hidePanel");
},
query : function(q) {
doQuery(this, q);
}
},
filter : function(q, row) {
var opts = $(this).combobox("options");
return row[opts.textField].indexOf(q) == 0;
},
formatter : function(row) {
var opts = $(this).combobox("options");
return row[opts.textField];
},
loader : function(param, onLoadSuccess, onLoadError) {
var opts = $(this).combobox("options");
if (!opts.url) {
return false;
}
$.ajax({
type : opts.method,
url : opts.url,
data : param,
dataType : "json",
success : function(data) {
onLoadSuccess(data);
},
error : function() {
onLoadError.apply(this, arguments);
}
});
},
onBeforeLoad : function(param) {
},
onLoadSuccess : function() {
},
onLoadError : function() {
},
onSelect : function(record) {
},
onUnselect : function(record) {
}
});
})(jQuery);

scrollItem 函数:

/**
* 滚动选项到合适位置:
* 如果item被卷在panel上方,则滚动到panel可见区的最上方;
* 如果item被卷在panel下方,则滚动到panel可见区的最下方;
* parmas[target] 承载combobox的DOM
* params[value] valueField字段对应的某个值
*/
function scrollItem(target, value) {
var panel = $(target).combo("panel");
var item = panel.find("div.combobox-item[value=\"" + value + "\"]");
if (item.length) {
if (item.position().top <= 0) {
var h = panel.scrollTop() + item.position().top;
panel.scrollTop(h);
} else {
if (item.position().top + item.outerHeight() > panel.height()) {
var h = panel.scrollTop() + item.position().top
+ item.outerHeight() - panel.height();
panel.scrollTop(h);
}
}
}
}

对于jquery的position和scrollTop等函数不太了解的,请看以下几幅参照图:

需调整的情况一:

调整前:
combox源码解析
调整后:
combox源码解析

需调整的情况二:

调整前:
combox源码解析
调整后:
combox源码解析

其它内部函数没有什么太难理解的地方,不过代码中内部函数doQuery中的几句代码的用意我不是十分清楚,希望知晓的童鞋们告知一下。

上一篇:Query on a tree——树链剖分整理


下一篇:SPOJ Query on a tree 树链剖分 水题