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

需调整的情况一:

调整前:
scroll_1
调整后:
scroll_2

需调整的情况二:

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

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

原文地址:https://www.cnblogs.com/doudou618/p/5523853.html