source: branches/2.3/themes/default/js/ui/jquery.ui.autocomplete.js @ 12526

Last change on this file since 12526 was 12526, checked in by patdenice, 12 years ago

merge r12525 from trunk to branch 2.3
feature:2487
Update jQuery to 1.6.4 and jQuery UI to 1.8.16

File size: 16.1 KB
Line 
1/*
2 * jQuery UI Autocomplete 1.8.16
3 *
4 * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
5 * Dual licensed under the MIT or GPL Version 2 licenses.
6 * http://jquery.org/license
7 *
8 * http://docs.jquery.com/UI/Autocomplete
9 *
10 * Depends:
11 *      jquery.ui.core.js
12 *      jquery.ui.widget.js
13 *      jquery.ui.position.js
14 */
15(function( $, undefined ) {
16
17// used to prevent race conditions with remote data sources
18var requestIndex = 0;
19
20$.widget( "ui.autocomplete", {
21        options: {
22                appendTo: "body",
23                autoFocus: false,
24                delay: 300,
25                minLength: 1,
26                position: {
27                        my: "left top",
28                        at: "left bottom",
29                        collision: "none"
30                },
31                source: null
32        },
33
34        pending: 0,
35
36        _create: function() {
37                var self = this,
38                        doc = this.element[ 0 ].ownerDocument,
39                        suppressKeyPress;
40
41                this.element
42                        .addClass( "ui-autocomplete-input" )
43                        .attr( "autocomplete", "off" )
44                        // TODO verify these actually work as intended
45                        .attr({
46                                role: "textbox",
47                                "aria-autocomplete": "list",
48                                "aria-haspopup": "true"
49                        })
50                        .bind( "keydown.autocomplete", function( event ) {
51                                if ( self.options.disabled || self.element.propAttr( "readOnly" ) ) {
52                                        return;
53                                }
54
55                                suppressKeyPress = false;
56                                var keyCode = $.ui.keyCode;
57                                switch( event.keyCode ) {
58                                case keyCode.PAGE_UP:
59                                        self._move( "previousPage", event );
60                                        break;
61                                case keyCode.PAGE_DOWN:
62                                        self._move( "nextPage", event );
63                                        break;
64                                case keyCode.UP:
65                                        self._move( "previous", event );
66                                        // prevent moving cursor to beginning of text field in some browsers
67                                        event.preventDefault();
68                                        break;
69                                case keyCode.DOWN:
70                                        self._move( "next", event );
71                                        // prevent moving cursor to end of text field in some browsers
72                                        event.preventDefault();
73                                        break;
74                                case keyCode.ENTER:
75                                case keyCode.NUMPAD_ENTER:
76                                        // when menu is open and has focus
77                                        if ( self.menu.active ) {
78                                                // #6055 - Opera still allows the keypress to occur
79                                                // which causes forms to submit
80                                                suppressKeyPress = true;
81                                                event.preventDefault();
82                                        }
83                                        //passthrough - ENTER and TAB both select the current element
84                                case keyCode.TAB:
85                                        if ( !self.menu.active ) {
86                                                return;
87                                        }
88                                        self.menu.select( event );
89                                        break;
90                                case keyCode.ESCAPE:
91                                        self.element.val( self.term );
92                                        self.close( event );
93                                        break;
94                                default:
95                                        // keypress is triggered before the input value is changed
96                                        clearTimeout( self.searching );
97                                        self.searching = setTimeout(function() {
98                                                // only search if the value has changed
99                                                if ( self.term != self.element.val() ) {
100                                                        self.selectedItem = null;
101                                                        self.search( null, event );
102                                                }
103                                        }, self.options.delay );
104                                        break;
105                                }
106                        })
107                        .bind( "keypress.autocomplete", function( event ) {
108                                if ( suppressKeyPress ) {
109                                        suppressKeyPress = false;
110                                        event.preventDefault();
111                                }
112                        })
113                        .bind( "focus.autocomplete", function() {
114                                if ( self.options.disabled ) {
115                                        return;
116                                }
117
118                                self.selectedItem = null;
119                                self.previous = self.element.val();
120                        })
121                        .bind( "blur.autocomplete", function( event ) {
122                                if ( self.options.disabled ) {
123                                        return;
124                                }
125
126                                clearTimeout( self.searching );
127                                // clicks on the menu (or a button to trigger a search) will cause a blur event
128                                self.closing = setTimeout(function() {
129                                        self.close( event );
130                                        self._change( event );
131                                }, 150 );
132                        });
133                this._initSource();
134                this.response = function() {
135                        return self._response.apply( self, arguments );
136                };
137                this.menu = $( "<ul></ul>" )
138                        .addClass( "ui-autocomplete" )
139                        .appendTo( $( this.options.appendTo || "body", doc )[0] )
140                        // prevent the close-on-blur in case of a "slow" click on the menu (long mousedown)
141                        .mousedown(function( event ) {
142                                // clicking on the scrollbar causes focus to shift to the body
143                                // but we can't detect a mouseup or a click immediately afterward
144                                // so we have to track the next mousedown and close the menu if
145                                // the user clicks somewhere outside of the autocomplete
146                                var menuElement = self.menu.element[ 0 ];
147                                if ( !$( event.target ).closest( ".ui-menu-item" ).length ) {
148                                        setTimeout(function() {
149                                                $( document ).one( 'mousedown', function( event ) {
150                                                        if ( event.target !== self.element[ 0 ] &&
151                                                                event.target !== menuElement &&
152                                                                !$.ui.contains( menuElement, event.target ) ) {
153                                                                self.close();
154                                                        }
155                                                });
156                                        }, 1 );
157                                }
158
159                                // use another timeout to make sure the blur-event-handler on the input was already triggered
160                                setTimeout(function() {
161                                        clearTimeout( self.closing );
162                                }, 13);
163                        })
164                        .menu({
165                                focus: function( event, ui ) {
166                                        var item = ui.item.data( "item.autocomplete" );
167                                        if ( false !== self._trigger( "focus", event, { item: item } ) ) {
168                                                // use value to match what will end up in the input, if it was a key event
169                                                if ( /^key/.test(event.originalEvent.type) ) {
170                                                        self.element.val( item.value );
171                                                }
172                                        }
173                                },
174                                selected: function( event, ui ) {
175                                        var item = ui.item.data( "item.autocomplete" ),
176                                                previous = self.previous;
177
178                                        // only trigger when focus was lost (click on menu)
179                                        if ( self.element[0] !== doc.activeElement ) {
180                                                self.element.focus();
181                                                self.previous = previous;
182                                                // #6109 - IE triggers two focus events and the second
183                                                // is asynchronous, so we need to reset the previous
184                                                // term synchronously and asynchronously :-(
185                                                setTimeout(function() {
186                                                        self.previous = previous;
187                                                        self.selectedItem = item;
188                                                }, 1);
189                                        }
190
191                                        if ( false !== self._trigger( "select", event, { item: item } ) ) {
192                                                self.element.val( item.value );
193                                        }
194                                        // reset the term after the select event
195                                        // this allows custom select handling to work properly
196                                        self.term = self.element.val();
197
198                                        self.close( event );
199                                        self.selectedItem = item;
200                                },
201                                blur: function( event, ui ) {
202                                        // don't set the value of the text field if it's already correct
203                                        // this prevents moving the cursor unnecessarily
204                                        if ( self.menu.element.is(":visible") &&
205                                                ( self.element.val() !== self.term ) ) {
206                                                self.element.val( self.term );
207                                        }
208                                }
209                        })
210                        .zIndex( this.element.zIndex() + 1 )
211                        // workaround for jQuery bug #5781 http://dev.jquery.com/ticket/5781
212                        .css({ top: 0, left: 0 })
213                        .hide()
214                        .data( "menu" );
215                if ( $.fn.bgiframe ) {
216                         this.menu.element.bgiframe();
217                }
218        },
219
220        destroy: function() {
221                this.element
222                        .removeClass( "ui-autocomplete-input" )
223                        .removeAttr( "autocomplete" )
224                        .removeAttr( "role" )
225                        .removeAttr( "aria-autocomplete" )
226                        .removeAttr( "aria-haspopup" );
227                this.menu.element.remove();
228                $.Widget.prototype.destroy.call( this );
229        },
230
231        _setOption: function( key, value ) {
232                $.Widget.prototype._setOption.apply( this, arguments );
233                if ( key === "source" ) {
234                        this._initSource();
235                }
236                if ( key === "appendTo" ) {
237                        this.menu.element.appendTo( $( value || "body", this.element[0].ownerDocument )[0] )
238                }
239                if ( key === "disabled" && value && this.xhr ) {
240                        this.xhr.abort();
241                }
242        },
243
244        _initSource: function() {
245                var self = this,
246                        array,
247                        url;
248                if ( $.isArray(this.options.source) ) {
249                        array = this.options.source;
250                        this.source = function( request, response ) {
251                                response( $.ui.autocomplete.filter(array, request.term) );
252                        };
253                } else if ( typeof this.options.source === "string" ) {
254                        url = this.options.source;
255                        this.source = function( request, response ) {
256                                if ( self.xhr ) {
257                                        self.xhr.abort();
258                                }
259                                self.xhr = $.ajax({
260                                        url: url,
261                                        data: request,
262                                        dataType: "json",
263                                        autocompleteRequest: ++requestIndex,
264                                        success: function( data, status ) {
265                                                if ( this.autocompleteRequest === requestIndex ) {
266                                                        response( data );
267                                                }
268                                        },
269                                        error: function() {
270                                                if ( this.autocompleteRequest === requestIndex ) {
271                                                        response( [] );
272                                                }
273                                        }
274                                });
275                        };
276                } else {
277                        this.source = this.options.source;
278                }
279        },
280
281        search: function( value, event ) {
282                value = value != null ? value : this.element.val();
283
284                // always save the actual value, not the one passed as an argument
285                this.term = this.element.val();
286
287                if ( value.length < this.options.minLength ) {
288                        return this.close( event );
289                }
290
291                clearTimeout( this.closing );
292                if ( this._trigger( "search", event ) === false ) {
293                        return;
294                }
295
296                return this._search( value );
297        },
298
299        _search: function( value ) {
300                this.pending++;
301                this.element.addClass( "ui-autocomplete-loading" );
302
303                this.source( { term: value }, this.response );
304        },
305
306        _response: function( content ) {
307                if ( !this.options.disabled && content && content.length ) {
308                        content = this._normalize( content );
309                        this._suggest( content );
310                        this._trigger( "open" );
311                } else {
312                        this.close();
313                }
314                this.pending--;
315                if ( !this.pending ) {
316                        this.element.removeClass( "ui-autocomplete-loading" );
317                }
318        },
319
320        close: function( event ) {
321                clearTimeout( this.closing );
322                if ( this.menu.element.is(":visible") ) {
323                        this.menu.element.hide();
324                        this.menu.deactivate();
325                        this._trigger( "close", event );
326                }
327        },
328       
329        _change: function( event ) {
330                if ( this.previous !== this.element.val() ) {
331                        this._trigger( "change", event, { item: this.selectedItem } );
332                }
333        },
334
335        _normalize: function( items ) {
336                // assume all items have the right format when the first item is complete
337                if ( items.length && items[0].label && items[0].value ) {
338                        return items;
339                }
340                return $.map( items, function(item) {
341                        if ( typeof item === "string" ) {
342                                return {
343                                        label: item,
344                                        value: item
345                                };
346                        }
347                        return $.extend({
348                                label: item.label || item.value,
349                                value: item.value || item.label
350                        }, item );
351                });
352        },
353
354        _suggest: function( items ) {
355                var ul = this.menu.element
356                        .empty()
357                        .zIndex( this.element.zIndex() + 1 );
358                this._renderMenu( ul, items );
359                // TODO refresh should check if the active item is still in the dom, removing the need for a manual deactivate
360                this.menu.deactivate();
361                this.menu.refresh();
362
363                // size and position menu
364                ul.show();
365                this._resizeMenu();
366                ul.position( $.extend({
367                        of: this.element
368                }, this.options.position ));
369
370                if ( this.options.autoFocus ) {
371                        this.menu.next( new $.Event("mouseover") );
372                }
373        },
374
375        _resizeMenu: function() {
376                var ul = this.menu.element;
377                ul.outerWidth( Math.max(
378                        ul.width( "" ).outerWidth(),
379                        this.element.outerWidth()
380                ) );
381        },
382
383        _renderMenu: function( ul, items ) {
384                var self = this;
385                $.each( items, function( index, item ) {
386                        self._renderItem( ul, item );
387                });
388        },
389
390        _renderItem: function( ul, item) {
391                return $( "<li></li>" )
392                        .data( "item.autocomplete", item )
393                        .append( $( "<a></a>" ).text( item.label ) )
394                        .appendTo( ul );
395        },
396
397        _move: function( direction, event ) {
398                if ( !this.menu.element.is(":visible") ) {
399                        this.search( null, event );
400                        return;
401                }
402                if ( this.menu.first() && /^previous/.test(direction) ||
403                                this.menu.last() && /^next/.test(direction) ) {
404                        this.element.val( this.term );
405                        this.menu.deactivate();
406                        return;
407                }
408                this.menu[ direction ]( event );
409        },
410
411        widget: function() {
412                return this.menu.element;
413        }
414});
415
416$.extend( $.ui.autocomplete, {
417        escapeRegex: function( value ) {
418                return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
419        },
420        filter: function(array, term) {
421                var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
422                return $.grep( array, function(value) {
423                        return matcher.test( value.label || value.value || value );
424                });
425        }
426});
427
428}( jQuery ));
429
430/*
431 * jQuery UI Menu (not officially released)
432 *
433 * This widget isn't yet finished and the API is subject to change. We plan to finish
434 * it for the next release. You're welcome to give it a try anyway and give us feedback,
435 * as long as you're okay with migrating your code later on. We can help with that, too.
436 *
437 * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
438 * Dual licensed under the MIT or GPL Version 2 licenses.
439 * http://jquery.org/license
440 *
441 * http://docs.jquery.com/UI/Menu
442 *
443 * Depends:
444 *      jquery.ui.core.js
445 *  jquery.ui.widget.js
446 */
447(function($) {
448
449$.widget("ui.menu", {
450        _create: function() {
451                var self = this;
452                this.element
453                        .addClass("ui-menu ui-widget ui-widget-content ui-corner-all")
454                        .attr({
455                                role: "listbox",
456                                "aria-activedescendant": "ui-active-menuitem"
457                        })
458                        .click(function( event ) {
459                                if ( !$( event.target ).closest( ".ui-menu-item a" ).length ) {
460                                        return;
461                                }
462                                // temporary
463                                event.preventDefault();
464                                self.select( event );
465                        });
466                this.refresh();
467        },
468       
469        refresh: function() {
470                var self = this;
471
472                // don't refresh list items that are already adapted
473                var items = this.element.children("li:not(.ui-menu-item):has(a)")
474                        .addClass("ui-menu-item")
475                        .attr("role", "menuitem");
476               
477                items.children("a")
478                        .addClass("ui-corner-all")
479                        .attr("tabindex", -1)
480                        // mouseenter doesn't work with event delegation
481                        .mouseenter(function( event ) {
482                                self.activate( event, $(this).parent() );
483                        })
484                        .mouseleave(function() {
485                                self.deactivate();
486                        });
487        },
488
489        activate: function( event, item ) {
490                this.deactivate();
491                if (this.hasScroll()) {
492                        var offset = item.offset().top - this.element.offset().top,
493                                scroll = this.element.scrollTop(),
494                                elementHeight = this.element.height();
495                        if (offset < 0) {
496                                this.element.scrollTop( scroll + offset);
497                        } else if (offset >= elementHeight) {
498                                this.element.scrollTop( scroll + offset - elementHeight + item.height());
499                        }
500                }
501                this.active = item.eq(0)
502                        .children("a")
503                                .addClass("ui-state-hover")
504                                .attr("id", "ui-active-menuitem")
505                        .end();
506                this._trigger("focus", event, { item: item });
507        },
508
509        deactivate: function() {
510                if (!this.active) { return; }
511
512                this.active.children("a")
513                        .removeClass("ui-state-hover")
514                        .removeAttr("id");
515                this._trigger("blur");
516                this.active = null;
517        },
518
519        next: function(event) {
520                this.move("next", ".ui-menu-item:first", event);
521        },
522
523        previous: function(event) {
524                this.move("prev", ".ui-menu-item:last", event);
525        },
526
527        first: function() {
528                return this.active && !this.active.prevAll(".ui-menu-item").length;
529        },
530
531        last: function() {
532                return this.active && !this.active.nextAll(".ui-menu-item").length;
533        },
534
535        move: function(direction, edge, event) {
536                if (!this.active) {
537                        this.activate(event, this.element.children(edge));
538                        return;
539                }
540                var next = this.active[direction + "All"](".ui-menu-item").eq(0);
541                if (next.length) {
542                        this.activate(event, next);
543                } else {
544                        this.activate(event, this.element.children(edge));
545                }
546        },
547
548        // TODO merge with previousPage
549        nextPage: function(event) {
550                if (this.hasScroll()) {
551                        // TODO merge with no-scroll-else
552                        if (!this.active || this.last()) {
553                                this.activate(event, this.element.children(".ui-menu-item:first"));
554                                return;
555                        }
556                        var base = this.active.offset().top,
557                                height = this.element.height(),
558                                result = this.element.children(".ui-menu-item").filter(function() {
559                                        var close = $(this).offset().top - base - height + $(this).height();
560                                        // TODO improve approximation
561                                        return close < 10 && close > -10;
562                                });
563
564                        // TODO try to catch this earlier when scrollTop indicates the last page anyway
565                        if (!result.length) {
566                                result = this.element.children(".ui-menu-item:last");
567                        }
568                        this.activate(event, result);
569                } else {
570                        this.activate(event, this.element.children(".ui-menu-item")
571                                .filter(!this.active || this.last() ? ":first" : ":last"));
572                }
573        },
574
575        // TODO merge with nextPage
576        previousPage: function(event) {
577                if (this.hasScroll()) {
578                        // TODO merge with no-scroll-else
579                        if (!this.active || this.first()) {
580                                this.activate(event, this.element.children(".ui-menu-item:last"));
581                                return;
582                        }
583
584                        var base = this.active.offset().top,
585                                height = this.element.height();
586                                result = this.element.children(".ui-menu-item").filter(function() {
587                                        var close = $(this).offset().top - base + height - $(this).height();
588                                        // TODO improve approximation
589                                        return close < 10 && close > -10;
590                                });
591
592                        // TODO try to catch this earlier when scrollTop indicates the last page anyway
593                        if (!result.length) {
594                                result = this.element.children(".ui-menu-item:first");
595                        }
596                        this.activate(event, result);
597                } else {
598                        this.activate(event, this.element.children(".ui-menu-item")
599                                .filter(!this.active || this.first() ? ":last" : ":first"));
600                }
601        },
602
603        hasScroll: function() {
604                return this.element.height() < this.element[ $.fn.prop ? "prop" : "attr" ]("scrollHeight");
605        },
606
607        select: function( event ) {
608                this._trigger("selected", event, { item: this.active });
609        }
610});
611
612}(jQuery));
Note: See TracBrowser for help on using the repository browser.