zoukankan      html  css  js  c++  java
  • 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中的几句代码的用意我不是十分清楚,希望知晓的童鞋们告知一下。

  • 相关阅读:
    阿里取消周报
    摇滚明星原则
    t
    B树、B+树索引算法原理(下)
    订单业务楼层化 view管理器和model管理器进行了model和view的全面封装处理 三端不得不在每个业务入口上线时约定好降级开关,于是代码中充满了各种各样的降级开关字段
    单元测试
    项目管理图 有任务分解、技术风险 风险预案
    CPU飙高,系统性能问题如何排查?

    如何让淘宝不卡顿? 读写比例 动态扩容 分布式化路线 mysql 优化
  • 原文地址:https://www.cnblogs.com/doudou618/p/5523853.html
Copyright © 2011-2022 走看看