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