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