and a note on manual changes in dataTables.bootstrap.css
[myslice.git] / third-party / jquery-ui-1.10.2 / ui / jquery.ui.autocomplete.js
1 /*!
2  * jQuery UI Autocomplete 1.10.2
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.2",
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                                         event.preventDefault();
146                                         return;
147                                 }
148                                 if ( suppressKeyPressRepeat ) {
149                                         return;
150                                 }
151
152                                 // replicate some key handlers to allow them to repeat in Firefox and Opera
153                                 var keyCode = $.ui.keyCode;
154                                 switch( event.keyCode ) {
155                                 case keyCode.PAGE_UP:
156                                         this._move( "previousPage", event );
157                                         break;
158                                 case keyCode.PAGE_DOWN:
159                                         this._move( "nextPage", event );
160                                         break;
161                                 case keyCode.UP:
162                                         this._keyEvent( "previous", event );
163                                         break;
164                                 case keyCode.DOWN:
165                                         this._keyEvent( "next", event );
166                                         break;
167                                 }
168                         },
169                         input: function( event ) {
170                                 if ( suppressInput ) {
171                                         suppressInput = false;
172                                         event.preventDefault();
173                                         return;
174                                 }
175                                 this._searchTimeout( event );
176                         },
177                         focus: function() {
178                                 this.selectedItem = null;
179                                 this.previous = this._value();
180                         },
181                         blur: function( event ) {
182                                 if ( this.cancelBlur ) {
183                                         delete this.cancelBlur;
184                                         return;
185                                 }
186
187                                 clearTimeout( this.searching );
188                                 this.close( event );
189                                 this._change( event );
190                         }
191                 });
192
193                 this._initSource();
194                 this.menu = $( "<ul>" )
195                         .addClass( "ui-autocomplete ui-front" )
196                         .appendTo( this._appendTo() )
197                         .menu({
198                                 // custom key handling for now
199                                 input: $(),
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                         .insertAfter( 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 ));