jquery autocomplete && linkage

30513 ワード

ネット上の既存のcomboboxを参考にして、改造と完備を経て、特に分かち合います.
次の機能をサポートします.
1.ドロップダウン・エントリのカスタム表示
2.ロード中の友好的なメッセージ
3.linkageと連携し、構造連動(国/地域)を容易にする
足りないところ、
1.jquery positionプラグインを使用してドロップダウン位置を位置決めし、自身に欠陥があることを実現する
2.勤務会社はURLパラメータのカスタマイズが重すぎて、JSコードはpostdata検査を埋め込む.linakgeキャッシュアルゴリズムが悪すぎます
.c-list {
	overflow:auto;
	position: absolute;
	z-index: 999999;
	display: none;
	border: 1px solid #CCCCCC;
	background-color: white;
	left:-9999px;
	top:-9999px;
}

.c-list .item {
	display:block;
	overflow: hidden;
	word-break: normal;
	word-wrap: break-word;
	cursor: pointer;
	padding: 2px 0px 2px 5px;
	border-bottom: 1px dashed #CCCCCC;
}

.c-list .item-selected {
	background: #c8e3fc;
}
 
;(function($) {
	
	$.i18n=$.i18n || {};
	$.i18n.autocomplete={
		NO_DATA:'    ',
		LOAD_ERROR:'    ',
		LOAD_TEXT:'     ...'
	};
	
	if (!$.browser) {
		var userAgent = navigator.userAgent.toLowerCase();
		// Figure out what browser is being used
		$.browser = {
			version : (userAgent.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/) || [])[1],
			safari : /webkit/.test(userAgent),
			opera : /opera/.test(userAgent),
			chrome: /chrome/.test(userAgent),
			msie : /msie/.test(userAgent) && !/opera/.test(userAgent),
			mozilla : /mozilla/.test(userAgent)
					&& !/(compatible|webkit)/.test(userAgent)
		};
	}

	/**          **/
	var ele = document.createElement("input");
	var _support_placeholder = 'placeholder' in ele;

	ele = null;

	/**
	 * autocomplete
	 * @constructor
	 */
	$.autocomplete = function($element, options) {
		var self = this,input = $element[0],name=options.name || input.name;
		this.$element = (options.cls ? $element : $element.addClass(options.cls)).removeAttr("name");
		input.autocomplete = 'off';
		input.value='';
		
		this.records=[];
		
		//       
		this._selected_=-1;
		
		// ajax    
		this.url = options.url;
		
		// ajax      
		this.urlParams = options.urlParams;

		// ajax             
		this.dataRoot=options.dataRoot;

		// ajax    
		this.delay = options.delay;

		//           ,     
		this.start=0;
		this.limit = options.limit;
		
		//        
		this.minChars = options.minChars;

		//     
		this.valueField = options.valueField;
	
		//    
		this.displayField = options.displayField;

		//                    
		this.textField=options.textField || (name+"name");
		
		//         
		this.queryField = options.queryField || this.displayField;
		
		//       
		this.tpl = "<li class='item' data-index='<% i %>'>" + (options.tpl || '<% item.'+this.displayField+'%>') + "</li>";

		//    placeholder
		var ph = options.placeholder;
		if (ph) {
			_support_placeholder ? $element.attr("placeholder", ph)
					: this.placeholder = ph;
		} else {
			if (!_support_placeholder) {
				ph = $element.attr("placeholder");
				if (ph) {
					this.placeholder = ph;
					$element.removeAttr("placeholder");
				}
			}
		}
		
		//     
		this._readOnly=false;
		options.readOnly || $element.prop("readOnly") ? this.readOnly(true) : false;

		//    DOM
		this.$hidden = $("<input type='hidden' ignoreElement='true' name='"+ name + "' />").appendTo(input.parentNode);
		
		//   DOM
		this.$list=$('<div class="c-list auto-list{cls}"><ul></ul></div>'.replace(/{cls}/,options.listCls ? ' '+ options.listCls: '')).appendTo(document.body);
		options.listWidth && this.$list.width(options.listWidth==='force' ? this.$element.outerWidth() - (this.$list.outerWidth() - this.$list.width()) : options.listWidth);
		options.listHeight && this.$list.height(options.listHeight);

		//       
		this._use_cache = !!options.useCache;
		if (this._use_cache) {
			this._cache_data = {};
			this._cache_length = 0;
			this._cache_size = options.cacheSize;
		}

		// set value
		this.reset(options.value || '');

		//   
		$element.focus(function() {

			var sf=self;
			
			//   
			if(sf.isReadOnly()){return true;}
			
			var $this=$(this);
		
			//    
			if ($this.hasClass("focus")) {return true;};
			
			//     
			$this.removeClass("placeholder").addClass("focus");
			
			// placeholder    (IE6/7/8)
			var ph=sf.placeholder,v=this.value;
			ph && v === ph ? this.value='' : sf.selectRange(0,v.length);

			//     
			sf.trigger("focus");

			//   DOM   
			if(sf.isActive()){return true;}

			//     DOOM
			sf.records.length && sf.show();

		}).blur(function() {

				var sf=self;
			
				//   
				if(sf.isReadOnly()){return true;}
			
				// IE doesn't prevent moving focus even with event.preventDefault()
				if (sf._ignore_blur_) {this.focus();sf.selectRange(0, this.value.length);sf._ignore_blur_ = false;return false;}
	
				//     
				$(this).removeClass("focus");
	
				//   
				sf.value(sf._selected_!=-1 ? sf.records[sf._selected_] : null);
				
				//     
				sf.hide();
				
				//     
				sf.trigger("blur");
				
				//    
				sf.validate(sf.value());

		}).click(function() {
			
			var sf=self;
			
			//   
			if(sf.isReadOnly()){return true;}
			
			//   DOM   
			if(sf.isActive()){return true;}

			//     DOOM
			sf.records.length && sf.show();

		}).keydown(function(e) {
			
			var sf=self;
			
			//   
			if(sf.isReadOnly()){return false;}

			switch (e.keyCode) {

				case 38: // up
					
					sf.isActive() && sf.focus('prev');
					
					return false;

				case 40: // down

					sf.isActive() ? sf.focus('next') : ( sf.records.length ? sf.show() : sf.activate());
					
					return false;

				case 9: // tab
					
					return true;
					
				case 13: // enter
					
					sf.isActive() && sf.select() && sf.selectRange(0, this.value.length);

					return false;

				case 27: // escape
					
					// Different browsers have different default behavior for escape
					// Single press can mean undo or clear
					// Double press in IE means clear the whole form
					// e.preventDefault();
					
					sf.isActive() && sf.hide();
					
					return false;
				case 37 : // left
					
					sf.isActive() && sf.hide();
					
					return false;
					
				case 39 : // right
					
					!sf.isActive() && !sf.records.length && sf.activate();
					
					return false;
				case 33 : // page up
					
					sf.isActive() && sf.focus('pageprev');
					
					return false;
				case 34 : // page down
					
					sf.isActive() && sf.focus('pagenext');
					
					return false;
				case 36 : // home
					
					sf.isActive() && sf.focus('first');
					
					return false;
				case 35 : // end
					
					sf.isActive() && sf.focus('last');
					
					return false;
				default:

					sf._selected_ = -1;
				
					sf._activate_timer_ && clearTimeout(sf._activate_timer_);
					
					sf._activate_timer_ = setTimeout(function() {sf.activate();}, sf.delay);

				}
		}).on("input.autocomplete",function(){
			
			// FIREFOX
			var sf=self;
			
			if(sf.isReadOnly()){return false;}
			
			sf._selected_ = -1;
			
			sf._activate_timer_ && clearTimeout(sf._activate_timer_);
			
			sf._activate_timer_ = setTimeout(function() {sf.activate();}, sf.delay);
		
		});
		
		//          ,     
		var celleditor=options.celleditor;
		
		this.$list.on("mouseover.autocomplete", " > ul > li", function(e) {
			
			self.focus($(this));
			
		}).on("click.autocomplete", " > ul > li", function(e) {
			
			self.select($(this));
			
		}).mousedown(function(e) {
			
			// prevent moving focus out of the text field
			e.preventDefault();

			// @IE6/7/8 even prevent default behavior , blur event triggered also
			self._ignore_blur_ = $.browser.msie && (+$.browser.version) < 9;
			
			//          ,      
			return celleditor ? false : true;
			
		}).scroll(function(e){return false;});

	};

	//   DOM    
	$.autocomplete.prototype.isActive = function() {return this.$list[0].style.display === 'block';};

	//     DOM
	$.autocomplete.prototype.show = function() {
		
		//   
		if(this.isReadOnly()){return this;}

		//     DOM  
		this.position();
		
		//      N 
		this.focus(this._selected_ == -1 ? 0 : this._selected_);

		//     
		!this.isActive() && this.trigger("active");
		
		return this;
	};

	//     DOM
	$.autocomplete.prototype.hide = function() {
		
		//   
		if(this.isReadOnly()){return this;}

		//    
		if(!this.isActive()){return this;}
		
		this.$list[0].style.display = 'none';
		
		//     
		this.trigger("deactive");
		
		return this;
	}
	
	//   DOM  
	$.autocomplete.prototype.position=function(){
	
		this.$list[0].style.display='block';
		
		// jQuery position  
		if ($.ui && $.fn.position) {
			
			this.$list.position( {
				my : "left top",
				at : "left bottom",
				collision : "fit",
				of : this.$element
			});
			
			return this;
		}
			
		//     ,     
		var offset = this.$element.offset();
		this.$list.css( {
			top : (offset.top + this.$element[0].offsetHeight) + 'px',
			left : offset.left + 'px'
		});

		return this;
	};

	//       
	$.autocomplete.prototype.activate = function() {

		var text = this.$element[0].value;
		
		// IE6/7/8
		this.placeholder && text == this.placeholder ? text = '' : false;
		
		//       
		text=text.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" );
		
		//       
		if(this._last_query===text){return this;}
		
		//      
		if(this.minChars && this.minChars > text.length){return this;}

		this._last_query=text;

		//       
		var opts={};
		opts[this.queryField]=text;
		
		//   
		if(this.limit > 0){
			opts.start= this.start ;
			opts.limit=this.limit;
		}

		//     
		this.load(opts);
		
		return this;
	};
	
	//   DOM    
	$.autocomplete.prototype.notice = function(msg) {
		
		var $ul=this.$list.find("> ul");
		
		//          
		if(msg==='load'){
			$ul.html("<li class='notice load'>"+$.i18n.autocomplete.LOAD_TEXT+"</li>");
			return this;
		}
		
		//    
		if(msg==='empty'){
			$ul.html("<li class='notice empty'>"+$.i18n.autocomplete.NO_DATA+"</li>");
			return this;
		}
		
		$ul.html(msg);
		
		return this;
	};
	
	//       
	$.autocomplete.prototype.load = function(opts, callback) {
		
		var self = this,cache,cacheKey;
		
		// ajax
		this._ajax_request && this._ajax_request.abort();

		typeof opts == 'function' ? (callback=opts,opts={}) : false;
		
		//     &&  
		opts=$.extend(true,{},this.urlParams,opts);

		//     
		this.trigger("beforeload",opts);
		
		//        
		cacheKey=this.queryField && opts[this.queryField];
		
		//             
		if(opts.postdata && typeof opts.postdata == 'object'){
			
			var sc=opts.searchcondition=opts.searchcondition || {};
			
			this.queryField ? sc[this.queryField]=opts[this.queryField] : false;
			
			$.toJSON ? opts.postdata=$.toJSON(opts.postdata) : false;
		}

		//     
		var ajaxCallback = function(data) {

			var sf=self,dataRoot=self.dataRoot;
			
			data && dataRoot && data[dataRoot] ? data=data[dataRoot] : false;
			
			data= data || [];
			
			sf._selected_= -1;
			
			//     
			sf.writeCache(cacheKey, data);
			
			//       
			typeof callback == 'function' && callback.call(sf, data);

			sf.json2Html(data);

			//      
			sf.trigger("load",data);

			sf.isActive() && sf.show();
		};
						
		//     
		this.notice('load').show();
		
		//         
		cache=cacheKey && this.readCache(cacheKey);
		if(cache){ajaxCallback(cache);return this;}

		var url=opts.url || this.url;
		delete opts.url;

		// ajax  
		this._ajax_request=$.ajax( {
			url : url,
			data : opts,
			method : 'POST',
			dataType : 'json',
			success : ajaxCallback,
			error : function() {ajaxCallback(false);}
		});


		return this;
		
	};

	//     
	$.autocomplete.prototype.focus = function($item) {

		var cls="item-selected",$ul=this.$list.find("> ul");
		
		//     
		if($item instanceof jQuery){
		
			!$item.hasClass(cls) && $ul.find(" > li."+cls).removeClass(cls);
		
		}
		
		//     
		if(typeof $item == 'number'){
			
			$item=$ul.find(" > li.item:eq("+$item+")");
			
			if(!$item.length){return this;}
			
			!$item.hasClass(cls) && $ul.find(" > li."+cls).removeClass(cls);
		}

		//    
		if($item == 'next'){
					
			$item=$ul.find(" > li."+cls).removeClass(cls);
			
			if(!$item.length){return this;}

			$item=$item.next();

			!$item.length ? $item=$ul.find("> li.item:eq(0)") : false;

		}
				
		//    
		if($item == 'prev'){
			
			$item=$ul.find(" > li."+cls).removeClass(cls);
			
			if(!$item.length){return this;}

			$item=$item.prev();

			!$item.length ? $item=$ul.find("> li.item:last-child") : false;

		}
		
		//    
		if($item == 'first'){
			
			$ul.find(" > li."+cls).removeClass(cls);

			$item=$ul.find(" > li.item:eq(0)");

			if(!$item.length){return this;}
		}
		
		//     
		if($item == 'last'){
			
			$ul.find(" > li."+cls).removeClass(cls);

			$item=$ul.find("> li.item:last-child");

			if(!$item.length){return this;}
		}

		//    
		if($item == 'pageprev'){
			return this.focus("first");
		}
		
		//    
		if($item == 'pagenext'){
			return this.focus("last");
		}
		
		if(!($item instanceof jQuery) || !$item.length){return this;}
		
		$item.addClass(cls);
		
		this.scroll($item);
		
		return this;
		
	};

	//     
	$.autocomplete.prototype.scroll = function( $item ) {

		var $list=this.$list,list=$list[0];
		
		if($list.outerHeight() >= $list.prop( "scrollHeight" )){return this;}
		
		var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;
	
		var borderTop = parseFloat( $.css( list, "borderTopWidth" ) ) || 0,paddingTop = parseFloat( $.css( list, "paddingTop" ) ) || 0;
		var offset = $item.offset().top - $list.offset().top - borderTop - paddingTop,scroll = $list.scrollTop();
		var elementHeight = $list.height(),itemHeight = $item.outerHeight();
		
		if ( offset < 0 ) {
			
			$list.scrollTop( scroll + offset + 17 );
		
		} else {
			if ( offset + itemHeight > elementHeight ) {
				
				//       
				var scrollbar=0;
				if($list.outerWidth() <= $list.prop( "scrollWidth" )){scrollbar=$.position && $.position.scrollbarWidth && $.position.scrollbarWidth() || 17;}
		
				$list.scrollTop( scroll + offset - elementHeight + itemHeight + scrollbar );	

			}
		}

		return this;
	},

	//     
	$.autocomplete.prototype.select = function($item) {
		
		//   
		var index = typeof $item =='number' ? $item : -1;

		// enter key press
		$item ===  undefined && this.isActive() ? $item = this.$list.find("> ul > li.item-selected") : false;

		// DOM
		$item instanceof jQuery ? index = +$item.data("index") : false;

		var record=this.records[index];
		
		this._selected_ !== index && this.trigger("beforeselect",record);

		this.$hidden[0].value = record[this.valueField];
		
		this.$element[0].value = record[this.displayField];

		this._selected_ !== index  && this.trigger("select",record);
		
		this._selected_ = index;
		
		this.hide();

		return this;
	};

	// MY GOD DOM
	$.autocomplete.prototype.selectRange = function(start, end) {
		
		var input = this.$element[0];
		
		if (input.setSelectionRange) {
		
			input.focus();
			input.setSelectionRange(start, end);
		
		} else if (input.createTextRange) {
			
			var range = input.createTextRange();
			
			range.collapse(true);
			
			range.moveEnd('character', end);
			
			range.moveStart('character', start);
		
			range.select();
		
		}
		
		return this;
	};

	//     
	$.autocomplete.prototype.find = function(key, value) {
		var records=this.records || [],l = records.length;
		
		//       
		if (typeof key == 'number') 
			return l > key ? records[key] : null;

		//       , name:'tom'
		while (l--)
			if (records[l][key] == value)
				return records[l];

		return null;
	};

	//       
	$.autocomplete.prototype.indexOf = function(key, value) {
		//     
		arguments.length == 1 && (value=key,key=this.valueField);

		//      
		typeof value == 'object' ? value = value[this.valueField] : false;
		
		var records=this.records || [],l = records.length;
		
		while(l--)
			if(records[l][key]== value)
				return l;
		
		return -1;
	};

	//     
	$.autocomplete.prototype.readCache = function(cacheKey) {
		
		this._cache_data=this._cache_data || {};
		
		if(this._use_cache && cacheKey){
			
			var cache=this._cache_data[cacheKey];
			
			//       
			cache ? cache.count=(cache.count || 0) + 1 : false;
			
			return cache ? cache.data : null;
		}
		
		return null;
		
	};

	//     
	$.autocomplete.prototype.writeCache = function(cacheKey, data) {
		
		this._cache_data=this._cache_data || {};
		
		if(this._use_cache && cacheKey && data){
			
			//     ,      ,      
			if(this._cache_length > this._cache_size){
				
				this._cache_data={};
				
				this._cache_length=0;
			
			}
			
			this._cache_length++;
			this._cache_data[cacheKey]={data:data,count:0};
		}
		
		return this;
	
	};

	// JSON     HTML   
	$.autocomplete.prototype.json2Html = function(json, template) {
		
		var tplEngine = function(tpl, data) {
		    var reg = /<%([^%>]+)?%>/g, 
		        regOut = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, 
		        code = 'var r=[];
', cursor = 0; var add = function(line, js) { js? (code += line.match(regOut) ? line + '
' : 'r.push(' + line + ');
') : (code += line != '' ? 'r.push("' + line.replace(/"/g, '\\"') + '");
' : ''); return add; } while(match = reg.exec(tpl)) { add(tpl.slice(cursor, match.index))(match[1], true); cursor = match.index + match[0].length; } add(tpl.substr(cursor, tpl.length - cursor)); code += 'return r.join("");'; return new Function(code.replace(/[\r\t
]/g, '')).apply(data); }; template=template || this.tpl; var code='<% for(var i=0,l=this && this.length || 0;i<l;i++){var item=this[i]; %>'+ template+ '<% } %>'; this.records=json; this.notice(json.length===0 ? 'empty' : tplEngine(code,json)); return this; } // $.autocomplete.prototype.text=function(){ var text=this.$element[0].value; return this.placeholder && text===this.placeholder ? "" : text; }; // $.autocomplete.prototype.validate=function(){return $.validate ? $.validate(this.$element) : true;}; // $.autocomplete.prototype.isReadOnly=function(){return this._read_only===true;}; // $.autocomplete.prototype.readOnly = function(readOnly) { readOnly=!!readOnly; this._read_only=readOnly; this.$element[readOnly ? "addClass" : "removeClass"]("readOnly").prop("readOnly",readOnly); return this; }; // $.autocomplete.prototype.value = function(value,text) { // getter method if (value === undefined) {return this.$hidden[0].value;} // value===null ? value='' : false; var oldValue = this.value(); // var index = value === '' ? -1 : this.indexOf(value); var record = index != -1 ? this.records[index] : null; // if(record == null && value ){ record = value; if(typeof value == 'string'){ record={}; record[this.valueField] = value; record[this.displayField] = text || value; } // DOM if(!this.records.length){ index = 0; this.json2Html([record]); } } value = record ? record[this.valueField] : ''; text = record ? record[this.displayField] : ''; this._selected_ = index; this.$hidden[0].value = value; var ph=this.placeholder; // placeholder this.$element[0].value = ph && text==="" ? ph : text; ph && this.$element[(text ? "remove" : "add") + "Class"]("placeholder"); var newValue = this.value(); // oldValue != newValue && this.trigger("select",record); // oldValue != newValue && this.trigger("change",newValue,oldValue); return this; }; // $.autocomplete.prototype.reset = function(value,text) { this._last_query=''; this._default_value= value != null && typeof value == 'object' ? value[this.valueField] : value; // this._default_value = this._default_value === undefined ? '' : ''+this._default_value; this.value(value,text); return this; }; // $.autocomplete.prototype.addChild = function(child) { this.children=this.children || []; for(var i=0,l=this.children.length;i<l;i++) if(this.children[i]===child) break; l==0 || i===l ? (child.parent=this,this.children.push(child)) : false; return this; }; // $.autocomplete.prototype.removeChild = function(child) { this.children=this.children || []; for(var i=0,l=this.children.length;i<l;i++) if(this.children[i]===child) break; i!==l ? (child.parent=null,this.children.splice(i,1)) : false; return this; }; // $.autocomplete.prototype.hasChild = function() { return this.children && this.children.length > 0; }; $.autocomplete.prototype.trigger = function(event) { var params = [ this]; for ( var i = 1, l = arguments.length; i < l; i++) { params[i] = arguments[i]; } var type=event.toUpperCase(); event=new jQuery.Event(type, type+"."+this.widget ); this.$element.triggerHandler(event, params); return event; }; $.autocomplete.prototype.destroy=function(keep){ this.$list.off(".autocomplete").remove(); this.$element.off(".autocomplete").removeData(); this.off(); var name=this.$hidden[0].name; this.$hidden.remove(); !!keep ? this.$element[0].name=name : this.$element.remove(); }; $.autocomplete.prototype.on = function(event, handler) { this.$element.on(event.toUpperCase() + "." + this.widget, handler); return this; }; $.autocomplete.prototype.one = function(event, handler) { this.$element.one(event.toUpperCase() + "." + this.widget, handler); return this; }; $.autocomplete.prototype.off = function(event, handler) { this.$element.off(((event && event.toUpperCase()) || '') + "." + this.widget, handler); return this; }; $.autocomplete.prototype.widget = 'autocomplete'; $.fn.autocomplete = function(options) { var f=null,fields=[]; for(var i=0,l=this.length;i<l;i++){ f=$.data(this[i], "widget"); !f ? (f=new $.autocomplete(this.eq(i), $.extend( {}, $.fn.autocomplete.defaults, options)),$.data(this[i], "widget", f)) : false; fields[i]=f; } return l===1 ? fields[0] : fields; }; /** * Default options for autocomplete plugin */ $.fn.autocomplete.defaults = { dataRoot:'dataroot', /** **/ minChars : 1, /** **/ name : '', /** class input **/ cls : '', /** class **/ listCls : '', /** , auto **/ listHeight:'', /** , **/ listWidth:'force', /** **/ delay : 250, /** **/ tpl : '', /** **/ displayField : 'name', /** **/ valueField : 'code', /** **/ textField:'', /** **/ queryField : '', /** **/ url : '', urlParams : {}, /** **/ limit : 10, /** **/ useCache : true, cacheSize : 10 }; })(jQuery);
 
           :  

    <input type='text' id='remote' />  
      
    <script>  
      
  
    //    JSON  :result:{data:[{name:' ',code:'1'},{name:' ',code:'2'}]}  
    $("#remote").combobox({url:'getSexData.action',dataRoot:'result'});  
      
    </script>