/[projects]/misc/horsensspejder-web/jquery/jquery-ui-1.10.3/ui/jquery.ui.autocomplete.js
ViewVC logotype

Annotation of /misc/horsensspejder-web/jquery/jquery-ui-1.10.3/ui/jquery.ui.autocomplete.js

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2125 - (hide annotations) (download) (as text)
Wed Mar 12 19:30:05 2014 UTC (10 years, 4 months ago) by torben
File MIME type: application/javascript
File size: 15983 byte(s)
initial import
1 torben 2125 /*!
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 ));

  ViewVC Help
Powered by ViewVC 1.1.20