PageSourceSearch

https://www.u-review.in.th/scripts/jquery-ui-1.10.4/ui/jquery.ui.autocomplete.js

js u-review.in.th collected 2026-09-26 11:43:06 UTC 15,896 bytes, 606 lines download raw bytes

vendor: 15,896 bytes, lines 1-606
1/*!
2 * jQuery UI Autocomplete 1.10.4
3 * http://jqueryui.com
4 *
5 * Copyright 2014 jQuery Foundation and other contributors
6 * Released under the MIT license.
7 * http://jquery.org/license
8 *
9 * http://api.jqueryui.com/autocomplete/
10 *
11 * Depends:
12 *	jquery.ui.core.js
13 *	jquery.ui.widget.js
14 *	jquery.ui.position.js
15 *	jquery.ui.menu.js
16 */
17(function( $, undefined ) {
18
19$.widget( "ui.autocomplete", {
20	version: "1.10.4",
21	defaultElement: "<input>",
22	options: {
23		appendTo: null,
24		autoFocus: false,
25		delay: 300,
26		minLength: 1,
27		position: {
28			my: "left top",
29			at: "left bottom",
30			collision: "none"
31		},
32		source: null,
33
34		// callbacks
35		change: null,
36		close: null,
37		focus: null,
38		open: null,
39		response: null,
40		search: null,
41		select: null
42	},
43
44	requestIndex: 0,
45	pending: 0,
46
47	_create: function() {
48		// Some browsers only repeat keydown events, not keypress events,
49		// so we use the suppressKeyPress flag to determine if we've already
50		// handled the keydown event. #7269
51		// Unfortunately the code for & in keypress is the same as the up arrow,
52		// so we use the suppressKeyPressRepeat flag to avoid handling keypress
53		// events when we know the keydown event was used to modify the
54		// search term. #7799
55		var suppressKeyPress, suppressKeyPressRepeat, suppressInput,
56			nodeName = this.element[0].nodeName.toLowerCase(),
57			isTextarea = nodeName === "textarea",
58			isInput = nodeName === "input";
59
60		this.isMultiLine =
61			// Textareas are always multi-line
62			isTextarea ? true :
63			// Inputs are always single-line, even if inside a contentEditable element
64			// IE also treats inputs as contentEditable
65			isInput ? false :
66			// All other element types are determined by whether or not they're contentEditable
67			this.element.prop( "isContentEditable" );
68
69		this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ];
70		this.isNewMenu = true;
71
72		this.element
73			.addClass( "ui-autocomplete-input" )
74			.attr( "autocomplete", "off" );
75
76		this._on( this.element, {
77			keydown: function( event ) {
78				if ( this.element.prop( "readOnly" ) ) {
79					suppressKeyPress = true;
80					suppressInput = true;
81					suppressKeyPressRepeat = true;
82					return;
83				}
84
85				suppressKeyPress = false;
86				suppressInput = false;
87				suppressKeyPressRepeat = false;
88				var keyCode = $.ui.keyCode;
89				switch( event.keyCode ) {
90				case keyCode.PAGE_UP:
91					suppressKeyPress = true;
92					this._move( "previousPage", event );
93					break;
94				case keyCode.PAGE_DOWN:
95					suppressKeyPress = true;
96					this._move( "nextPage", event );
97					break;
98				case keyCode.UP:
99					suppressKeyPress = true;
100					this._keyEvent( "previous", event );
101					break;
102				case keyCode.DOWN:
103					suppressKeyPress = true;
104					this._keyEvent( "next", event );
105					break;
106				case keyCode.ENTER:
107				case keyCode.NUMPAD_ENTER:
108					// when menu is open and has focus
109					if ( this.menu.active ) {
110						// #6055 - Opera still allows the keypress to occur
111						// which causes forms to submit
112						suppressKeyPress = true;
113						event.preventDefault();
114						this.menu.select( event );
115					}
116					break;
117				case keyCode.TAB:
118					if ( this.menu.active ) {
119						this.menu.select( event );
120					}
121					break;
122				case keyCode.ESCAPE:
123					if ( this.menu.element.is( ":visible" ) ) {
124						this._value( this.term );
125						this.close( event );
126						// Different browsers have different default behavior for escape
127						// Single press can mean undo or clear
128						// Double press in IE means clear the whole form
129						event.preventDefault();
130					}
131					break;
132				default:
133					suppressKeyPressRepeat = true;
134					// search timeout should be triggered before the input value is changed
135					this._searchTimeout( event );
136					break;
137				}
138			},
139			keypress: function( event ) {
140				if ( suppressKeyPress ) {
141					suppressKeyPress = false;
142					if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
143						event.preventDefault();
144					}
145					return;
146				}
147				if ( suppressKeyPressRepeat ) {
148					return;
149				}
150
151				// replicate some key handlers to allow them to repeat in Firefox and Opera
152				var keyCode = $.ui.keyCode;
153				switch( event.keyCode ) {
154				case keyCode.PAGE_UP:
155					this._move( "previousPage", event );
156					break;
157				case keyCode.PAGE_DOWN:
158					this._move( "nextPage", event );
159					break;
160				case keyCode.UP:
161					this._keyEvent( "previous", event );
162					break;
163				case keyCode.DOWN:
164					this._keyEvent( "next", event );
165					break;
166				}
167			},
168			input: function( event ) {
169				if ( suppressInput ) {
170					suppressInput = false;
171					event.preventDefault();
172					return;
173				}
174				this._searchTimeout( event );
175			},
176			focus: function() {
177				this.selectedItem = null;
178				this.previous = this._value();
179			},
180			blur: function( event ) {
181				if ( this.cancelBlur ) {
182					delete this.cancelBlur;
183					return;
184				}
185
186				clearTimeout( this.searching );
187				this.close( event );
188				this._change( event );
189			}
190		});
191
192		this._initSource();
193		this.menu = $( "<ul>" )
194			.addClass( "ui-autocomplete ui-front" )
195			.appendTo( this._appendTo() )
196			.menu({
197				// disable ARIA support, the live region takes care of that
198				role: null
199			})
200			.hide()
201			.data( "ui-menu" );
202
203		this._on( this.menu.element, {
204			mousedown: function( event ) {
205				// prevent moving focus out of the text field
206				event.preventDefault();
207
208				// IE doesn't prevent moving focus even with event.preventDefault()
209				// so we set a flag to know when we should ignore the blur event
210				this.cancelBlur = true;
211				this._delay(function() {
212					delete this.cancelBlur;
213				});
214
215				// clicking on the scrollbar causes focus to shift to the body
216				// but we can't detect a mouseup or a click immediately afterward
217				// so we have to track the next mousedown and close the menu if
218				// the user clicks somewhere outside of the autocomplete
219				var menuElement = this.menu.element[ 0 ];
220				if ( !$( event.target ).closest( ".ui-menu-item" ).length ) {
221					this._delay(function() {
222						var that = this;
223						this.document.one( "mousedown", function( event ) {
224							if ( event.target !== that.element[ 0 ] &&
225									event.target !== menuElement &&
226									!$.contains( menuElement, event.target ) ) {
227								that.close();
228							}
229						});
230					});
231				}
232			},
233			menufocus: function( event, ui ) {
234				// support: Firefox
235				// Prevent accidental activation of menu items in Firefox (#7024 #9118)
236				if ( this.isNewMenu ) {
237					this.isNewMenu = false;
238					if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) {
239						this.menu.blur();
240
241						this.document.one( "mousemove", function() {
242							$( event.target ).trigger( event.originalEvent );
243						});
244
245						return;
246					}
247				}
248
249				var item = ui.item.data( "ui-autocomplete-item" );
250				if ( false !== this._trigger( "focus", event, { item: item } ) ) {
251					// use value to match what will end up in the input, if it was a key event
252					if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) {
253						this._value( item.value );
254					}
255				} else {
256					// Normally the input is populated with the item's value as the
257					// menu is navigated, causing screen readers to notice a change and
258					// announce the item. Since the focus event was canceled, this doesn't
259					// happen, so we update the live region so that screen readers can
260					// still notice the change and announce it.
261					this.liveRegion.text( item.value );
262				}
263			},
264			menuselect: function( event, ui ) {
265				var item = ui.item.data( "ui-autocomplete-item" ),
266					previous = this.previous;
267
268				// only trigger when focus was lost (click on menu)
269				if ( this.element[0] !== this.document[0].activeElement ) {
270					this.element.focus();
271					this.previous = previous;
272					// #6109 - IE triggers two focus events and the second
273					// is asynchronous, so we need to reset the previous
274					// term synchronously and asynchronously :-(
275					this._delay(function() {
276						this.previous = previous;
277						this.selectedItem = item;
278					});
279				}
280
281				if ( false !== this._trigger( "select", event, { item: item } ) ) {
282					this._value( item.value );
283				}
284				// reset the term after the select event
285				// this allows custom select handling to work properly
286				this.term = this._value();
287
288				this.close( event );
289				this.selectedItem = item;
290			}
291		});
292
293		this.liveRegion = $( "<span>", {
294				role: "status",
295				"aria-live": "polite"
296			})
297			.addClass( "ui-helper-hidden-accessible" )
298			.insertBefore( this.element );
299
300		// turning off autocomplete prevents the browser from remembering the
301		// value when navigating through history, so we re-enable autocomplete
302		// if the page is unloaded before the widget is destroyed. #7790
303		this._on( this.window, {
304			beforeunload: function() {
305				this.element.removeAttr( "autocomplete" );
306			}
307		});
308	},
309
310	_destroy: function() {
311		clearTimeout( this.searching );
312		this.element
313			.removeClass( "ui-autocomplete-input" )
314			.removeAttr( "autocomplete" );
315		this.menu.element.remove();
316		this.liveRegion.remove();
317	},
318
319	_setOption: function( key, value ) {
320		this._super( key, value );
321		if ( key === "source" ) {
322			this._initSource();
323		}
324		if ( key === "appendTo" ) {
325			this.menu.element.appendTo( this._appendTo() );
326		}
327		if ( key === "disabled" && value && this.xhr ) {
328			this.xhr.abort();
329		}
330	},
331
332	_appendTo: function() {
333		var element = this.options.appendTo;
334
335		if ( element ) {
336			element = element.jquery || element.nodeType ?
337				$( element ) :
338				this.document.find( element ).eq( 0 );
339		}
340
341		if ( !element ) {
342			element = this.element.closest( ".ui-front" );
343		}
344
345		if ( !element.length ) {
346			element = this.document[0].body;
347		}
348
349		return element;
350	},
351
352	_initSource: function() {
353		var array, url,
354			that = this;
355		if ( $.isArray(this.options.source) ) {
356			array = this.options.source;
357			this.source = function( request, response ) {
358				response( $.ui.autocomplete.filter( array, request.term ) );
359			};
360		} else if ( typeof this.options.source === "string" ) {
361			url = this.options.source;
362			this.source = function( request, response ) {
363				if ( that.xhr ) {
364					that.xhr.abort();
365				}
366				that.xhr = $.ajax({
367					url: url,
368					data: request,
369					dataType: "json",
370					success: function( data ) {
371						response( data );
372					},
373					error: function() {
374						response( [] );
375					}
376				});
377			};
378		} else {
379			this.source = this.options.source;
380		}
381	},
382
383	_searchTimeout: function( event ) {
384		clearTimeout( this.searching );
385		this.searching = this._delay(function() {
386			// only search if the value has changed
387			if ( this.term !== this._value() ) {
388				this.selectedItem = null;
389				this.search( null, event );
390			}
391		}, this.options.delay );
392	},
393
394	search: function( value, event ) {
395		value = value != null ? value : this._value();
396
397		// always save the actual value, not the one passed as an argument
398		this.term = this._value();
399
400		if ( value.length < this.options.minLength ) {
401			return this.close( event );
402		}
403
404		if ( this._trigger( "search", event ) === false ) {
405			return;
406		}
407
408		return this._search( value );
409	},
410
411	_search: function( value ) {
412		this.pending++;
413		this.element.addClass( "ui-autocomplete-loading" );
414		this.cancelSearch = false;
415
416		this.source( { term: value }, this._response() );
417	},
418
419	_response: function() {
420		var index = ++this.requestIndex;
421
422		return $.proxy(function( content ) {
423			if ( index === this.requestIndex ) {
424				this.__response( content );
425			}
426
427			this.pending--;
428			if ( !this.pending ) {
429				this.element.removeClass( "ui-autocomplete-loading" );
430			}
431		}, this );
432	},
433
434	__response: function( content ) {
435		if ( content ) {
436			content = this._normalize( content );
437		}
438		this._trigger( "response", null, { content: content } );
439		if ( !this.options.disabled && content && content.length && !this.cancelSearch ) {
440			this._suggest( content );
441			this._trigger( "open" );
442		} else {
443			// use ._close() instead of .close() so we don't cancel future searches
444			this._close();
445		}
446	},
447
448	close: function( event ) {
449		this.cancelSearch = true;
450		this._close( event );
451	},
452
453	_close: function( event ) {
454		if ( this.menu.element.is( ":visible" ) ) {
455			this.menu.element.hide();
456			this.menu.blur();
457			this.isNewMenu = true;
458			this._trigger( "close", event );
459		}
460	},
461
462	_change: function( event ) {
463		if ( this.previous !== this._value() ) {
464			this._trigger( "change", event, { item: this.selectedItem } );
465		}
466	},
467
468	_normalize: function( items ) {
469		// assume all items have the right format when the first item is complete
470		if ( items.length && items[0].label && items[0].value ) {
471			return items;
472		}
473		return $.map( items, function( item ) {
474			if ( typeof item === "string" ) {
475				return {
476					label: item,
477					value: item
478				};
479			}
480			return $.extend({
481				label: item.label || item.value,
482				value: item.value || item.label
483			}, item );
484		});
485	},
486
487	_suggest: function( items ) {
488		var ul = this.menu.element.empty();
489		this._renderMenu( ul, items );
490		this.isNewMenu = true;
491		this.menu.refresh();
492
493		// size and position menu
494		ul.show();
495		this._resizeMenu();
496		ul.position( $.extend({
497			of: this.element
498		}, this.options.position ));
499
500		if ( this.options.autoFocus ) {
501			this.menu.next();
502		}
503	},
504
505	_resizeMenu: function() {
506		var ul = this.menu.element;
507		ul.outerWidth( Math.max(
508			// Firefox wraps long text (possibly a rounding bug)
509			// so we add 1px to avoid the wrapping (#7513)
510			ul.width( "" ).outerWidth() + 1,
511			this.element.outerWidth()
512		) );
513	},
514
515	_renderMenu: function( ul, items ) {
516		var that = this;
517		$.each( items, function( index, item ) {
518			that._renderItemData( ul, item );
519		});
520	},
521
522	_renderItemData: function( ul, item ) {
523		return this._renderItem( ul, item ).data( "ui-autocomplete-item", item );
524	},
525
526	_renderItem: function( ul, item ) {
527		return $( "<li>" )
528			.append( $( "<a>" ).text( item.label ) )
529			.appendTo( ul );
530	},
531
532	_move: function( direction, event ) {
533		if ( !this.menu.element.is( ":visible" ) ) {
534			this.search( null, event );
535			return;
536		}
537		if ( this.menu.isFirstItem() && /^previous/.test( direction ) ||
538				this.menu.isLastItem() && /^next/.test( direction ) ) {
539			this._value( this.term );
540			this.menu.blur();
541			return;
542		}
543		this.menu[ direction ]( event );
544	},
545
546	widget: function() {
547		return this.menu.element;
548	},
549
550	_value: function() {
551		return this.valueMethod.apply( this.element, arguments );
552	},
553
554	_keyEvent: function( keyEvent, event ) {
555		if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
556			this._move( keyEvent, event );
557
558			// prevents moving cursor to beginning/end of the text field in some browsers
559			event.preventDefault();
560		}
561	}
562});
563
564$.extend( $.ui.autocomplete, {
565	escapeRegex: function( value ) {
566		return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
567	},
568	filter: function(array, term) {
569		var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
570		return $.grep( array, function(value) {
571			return matcher.test( value.label || value.value || value );
572		});
573	}
574});
575
576
577// live region extension, adding a `messages` option
578// NOTE: This is an experimental API. We are still investigating
579// a full solution for string manipulation and internationalization.
580$.widget( "ui.autocomplete", $.ui.autocomplete, {
581	options: {
582		messages: {
583			noResults: "No search results.",
584			results: function( amount ) {
585				return amount + ( amount > 1 ? " results are" : " result is" ) +
586					" available, use up and down arrow keys to navigate.";
587			}
588		}
589	},
590
591	__response: function( content ) {
592		var message;
593		this._superApply( arguments );
594		if ( this.options.disabled || this.cancelSearch ) {
595			return;
596		}
597		if ( content && content.length ) {
598			message = this.options.messages.results( content.length );
599		} else {
600			message = this.options.messages.noResults;
601		}
602		this.liveRegion.text( message );
603	}
604});
605
606}( jQuery ));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.