现在的位置: 首页 > easyui > Form > combobox > 正文
jQuery Easyui 源码分析之combobox组件
2013年01月13日 combobox, 源码分析 ⁄ 共 11146字 评论数 2 ⁄ 被围观 16,403 views+
文章目录
[隐藏]

combobox组件继承combo组件,其实用频率较基础的combo组件要高得多,本篇文章对1.3.2的combobox组件源码做简单阐述和压缩码翻译,话不多说,直接上代码了:

代码概况:

/**
 * 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等函数不太了解的,请看以下几幅参照图:

需调整的情况一:

调整前:
scroll_1
调整后:
scroll_2

需调整的情况二:

调整前:
scroll_3
调整后:
情况二调整后位置

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

目前有 2 条留言 其中:访客:1 条, 博主:1 条

  1. coble : 2014年03月13日17:40:14  -49楼 @回复 回复

    你好,请问用combobox当输入检索条件后,回车要取第一个下接值,能否告知下思想,或是例子,谢谢!


    • 管理员
      世纪之光 : 2014年03月14日13:47:58  地下1层 @回复 回复

      可以通过重新定义options.keyHandler.enter的方式去实现,不过注意不要覆盖已经存在的逻辑,要在默认函数的基础上定义。

      1. $(“#combobox”).combobox(“options”).keyHandler.enter = function(){   
      2.     var values = $(this).combobox(“getValues”);   
      3.     $(this).combobox(“setValues”, values);   
      4.     $(this).combobox(“hidePanel”);   
      5.     //下面增加你自己想要处理的逻辑   
      6.     //TODO   
      7. }  

给我留言

留言无头像?


×