source: Dev/branches/jQueryUI/js/jquery/ui/jquery.ui.autocomplete.js @ 244

Last change on this file since 244 was 244, checked in by hendrikvanantwerpen, 13 years ago

Inlogscherm werkt met jQuery UI! Woot!

File size: 16.7 KB
Line 
1/*
2 * jQuery UI Autocomplete 1.8.17
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                // turning off autocomplete prevents the browser from remembering the
219                // value when navigating through history, so we re-enable autocomplete
220                // if the page is unloaded before the widget is destroyed. #7790
221                self.beforeunloadHandler = function() {
222                        self.element.removeAttr( "autocomplete" );
223                };
224                $( window ).bind( "beforeunload", self.beforeunloadHandler );
225        },
226
227        destroy: function() {
228                this.element
229                        .removeClass( "ui-autocomplete-input" )
230                        .removeAttr( "autocomplete" )
231                        .removeAttr( "role" )
232                        .removeAttr( "aria-autocomplete" )
233                        .removeAttr( "aria-haspopup" );
234                this.menu.element.remove();
235                $( window ).unbind( "beforeunload", this.beforeunloadHandler );
236                $.Widget.prototype.destroy.call( this );
237        },
238
239        _setOption: function( key, value ) {
240                $.Widget.prototype._setOption.apply( this, arguments );
241                if ( key === "source" ) {
242                        this._initSource();
243                }
244                if ( key === "appendTo" ) {
245                        this.menu.element.appendTo( $( value || "body", this.element[0].ownerDocument )[0] )
246                }
247                if ( key === "disabled" && value && this.xhr ) {
248                        this.xhr.abort();
249                }
250        },
251
252        _initSource: function() {
253                var self = this,
254                        array,
255                        url;
256                if ( $.isArray(this.options.source) ) {
257                        array = this.options.source;
258                        this.source = function( request, response ) {
259                                response( $.ui.autocomplete.filter(array, request.term) );
260                        };
261                } else if ( typeof this.options.source === "string" ) {
262                        url = this.options.source;
263                        this.source = function( request, response ) {
264                                if ( self.xhr ) {
265                                        self.xhr.abort();
266                                }
267                                self.xhr = $.ajax({
268                                        url: url,
269                                        data: request,
270                                        dataType: "json",
271                                        autocompleteRequest: ++requestIndex,
272                                        success: function( data, status ) {
273                                                if ( this.autocompleteRequest === requestIndex ) {
274                                                        response( data );
275                                                }
276                                        },
277                                        error: function() {
278                                                if ( this.autocompleteRequest === requestIndex ) {
279                                                        response( [] );
280                                                }
281                                        }
282                                });
283                        };
284                } else {
285                        this.source = this.options.source;
286                }
287        },
288
289        search: function( value, event ) {
290                value = value != null ? value : this.element.val();
291
292                // always save the actual value, not the one passed as an argument
293                this.term = this.element.val();
294
295                if ( value.length < this.options.minLength ) {
296                        return this.close( event );
297                }
298
299                clearTimeout( this.closing );
300                if ( this._trigger( "search", event ) === false ) {
301                        return;
302                }
303
304                return this._search( value );
305        },
306
307        _search: function( value ) {
308                this.pending++;
309                this.element.addClass( "ui-autocomplete-loading" );
310
311                this.source( { term: value }, this.response );
312        },
313
314        _response: function( content ) {
315                if ( !this.options.disabled && content && content.length ) {
316                        content = this._normalize( content );
317                        this._suggest( content );
318                        this._trigger( "open" );
319                } else {
320                        this.close();
321                }
322                this.pending--;
323                if ( !this.pending ) {
324                        this.element.removeClass( "ui-autocomplete-loading" );
325                }
326        },
327
328        close: function( event ) {
329                clearTimeout( this.closing );
330                if ( this.menu.element.is(":visible") ) {
331                        this.menu.element.hide();
332                        this.menu.deactivate();
333                        this._trigger( "close", event );
334                }
335        },
336       
337        _change: function( event ) {
338                if ( this.previous !== this.element.val() ) {
339                        this._trigger( "change", event, { item: this.selectedItem } );
340                }
341        },
342
343        _normalize: function( items ) {
344                // assume all items have the right format when the first item is complete
345                if ( items.length && items[0].label && items[0].value ) {
346                        return items;
347                }
348                return $.map( items, function(item) {
349                        if ( typeof item === "string" ) {
350                                return {
351                                        label: item,
352                                        value: item
353                                };
354                        }
355                        return $.extend({
356                                label: item.label || item.value,
357                                value: item.value || item.label
358                        }, item );
359                });
360        },
361
362        _suggest: function( items ) {
363                var ul = this.menu.element
364                        .empty()
365                        .zIndex( this.element.zIndex() + 1 );
366                this._renderMenu( ul, items );
367                // TODO refresh should check if the active item is still in the dom, removing the need for a manual deactivate
368                this.menu.deactivate();
369                this.menu.refresh();
370
371                // size and position menu
372                ul.show();
373                this._resizeMenu();
374                ul.position( $.extend({
375                        of: this.element
376                }, this.options.position ));
377
378                if ( this.options.autoFocus ) {
379                        this.menu.next( new $.Event("mouseover") );
380                }
381        },
382
383        _resizeMenu: function() {
384                var ul = this.menu.element;
385                ul.outerWidth( Math.max(
386                        // Firefox wraps long text (possibly a rounding bug)
387                        // so we add 1px to avoid the wrapping (#7513)
388                        ul.width( "" ).outerWidth() + 1,
389                        this.element.outerWidth()
390                ) );
391        },
392
393        _renderMenu: function( ul, items ) {
394                var self = this;
395                $.each( items, function( index, item ) {
396                        self._renderItem( ul, item );
397                });
398        },
399
400        _renderItem: function( ul, item) {
401                return $( "<li></li>" )
402                        .data( "item.autocomplete", item )
403                        .append( $( "<a></a>" ).text( item.label ) )
404                        .appendTo( ul );
405        },
406
407        _move: function( direction, event ) {
408                if ( !this.menu.element.is(":visible") ) {
409                        this.search( null, event );
410                        return;
411                }
412                if ( this.menu.first() && /^previous/.test(direction) ||
413                                this.menu.last() && /^next/.test(direction) ) {
414                        this.element.val( this.term );
415                        this.menu.deactivate();
416                        return;
417                }
418                this.menu[ direction ]( event );
419        },
420
421        widget: function() {
422                return this.menu.element;
423        }
424});
425
426$.extend( $.ui.autocomplete, {
427        escapeRegex: function( value ) {
428                return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
429        },
430        filter: function(array, term) {
431                var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
432                return $.grep( array, function(value) {
433                        return matcher.test( value.label || value.value || value );
434                });
435        }
436});
437
438}( jQuery ));
439
440/*
441 * jQuery UI Menu (not officially released)
442 *
443 * This widget isn't yet finished and the API is subject to change. We plan to finish
444 * it for the next release. You're welcome to give it a try anyway and give us feedback,
445 * as long as you're okay with migrating your code later on. We can help with that, too.
446 *
447 * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
448 * Dual licensed under the MIT or GPL Version 2 licenses.
449 * http://jquery.org/license
450 *
451 * http://docs.jquery.com/UI/Menu
452 *
453 * Depends:
454 *      jquery.ui.core.js
455 *  jquery.ui.widget.js
456 */
457(function($) {
458
459$.widget("ui.menu", {
460        _create: function() {
461                var self = this;
462                this.element
463                        .addClass("ui-menu ui-widget ui-widget-content ui-corner-all")
464                        .attr({
465                                role: "listbox",
466                                "aria-activedescendant": "ui-active-menuitem"
467                        })
468                        .click(function( event ) {
469                                if ( !$( event.target ).closest( ".ui-menu-item a" ).length ) {
470                                        return;
471                                }
472                                // temporary
473                                event.preventDefault();
474                                self.select( event );
475                        });
476                this.refresh();
477        },
478       
479        refresh: function() {
480                var self = this;
481
482                // don't refresh list items that are already adapted
483                var items = this.element.children("li:not(.ui-menu-item):has(a)")
484                        .addClass("ui-menu-item")
485                        .attr("role", "menuitem");
486               
487                items.children("a")
488                        .addClass("ui-corner-all")
489                        .attr("tabindex", -1)
490                        // mouseenter doesn't work with event delegation
491                        .mouseenter(function( event ) {
492                                self.activate( event, $(this).parent() );
493                        })
494                        .mouseleave(function() {
495                                self.deactivate();
496                        });
497        },
498
499        activate: function( event, item ) {
500                this.deactivate();
501                if (this.hasScroll()) {
502                        var offset = item.offset().top - this.element.offset().top,
503                                scroll = this.element.scrollTop(),
504                                elementHeight = this.element.height();
505                        if (offset < 0) {
506                                this.element.scrollTop( scroll + offset);
507                        } else if (offset >= elementHeight) {
508                                this.element.scrollTop( scroll + offset - elementHeight + item.height());
509                        }
510                }
511                this.active = item.eq(0)
512                        .children("a")
513                                .addClass("ui-state-hover")
514                                .attr("id", "ui-active-menuitem")
515                        .end();
516                this._trigger("focus", event, { item: item });
517        },
518
519        deactivate: function() {
520                if (!this.active) { return; }
521
522                this.active.children("a")
523                        .removeClass("ui-state-hover")
524                        .removeAttr("id");
525                this._trigger("blur");
526                this.active = null;
527        },
528
529        next: function(event) {
530                this.move("next", ".ui-menu-item:first", event);
531        },
532
533        previous: function(event) {
534                this.move("prev", ".ui-menu-item:last", event);
535        },
536
537        first: function() {
538                return this.active && !this.active.prevAll(".ui-menu-item").length;
539        },
540
541        last: function() {
542                return this.active && !this.active.nextAll(".ui-menu-item").length;
543        },
544
545        move: function(direction, edge, event) {
546                if (!this.active) {
547                        this.activate(event, this.element.children(edge));
548                        return;
549                }
550                var next = this.active[direction + "All"](".ui-menu-item").eq(0);
551                if (next.length) {
552                        this.activate(event, next);
553                } else {
554                        this.activate(event, this.element.children(edge));
555                }
556        },
557
558        // TODO merge with previousPage
559        nextPage: function(event) {
560                if (this.hasScroll()) {
561                        // TODO merge with no-scroll-else
562                        if (!this.active || this.last()) {
563                                this.activate(event, this.element.children(".ui-menu-item:first"));
564                                return;
565                        }
566                        var base = this.active.offset().top,
567                                height = this.element.height(),
568                                result = this.element.children(".ui-menu-item").filter(function() {
569                                        var close = $(this).offset().top - base - height + $(this).height();
570                                        // TODO improve approximation
571                                        return close < 10 && close > -10;
572                                });
573
574                        // TODO try to catch this earlier when scrollTop indicates the last page anyway
575                        if (!result.length) {
576                                result = this.element.children(".ui-menu-item:last");
577                        }
578                        this.activate(event, result);
579                } else {
580                        this.activate(event, this.element.children(".ui-menu-item")
581                                .filter(!this.active || this.last() ? ":first" : ":last"));
582                }
583        },
584
585        // TODO merge with nextPage
586        previousPage: function(event) {
587                if (this.hasScroll()) {
588                        // TODO merge with no-scroll-else
589                        if (!this.active || this.first()) {
590                                this.activate(event, this.element.children(".ui-menu-item:last"));
591                                return;
592                        }
593
594                        var base = this.active.offset().top,
595                                height = this.element.height();
596                                result = this.element.children(".ui-menu-item").filter(function() {
597                                        var close = $(this).offset().top - base + height - $(this).height();
598                                        // TODO improve approximation
599                                        return close < 10 && close > -10;
600                                });
601
602                        // TODO try to catch this earlier when scrollTop indicates the last page anyway
603                        if (!result.length) {
604                                result = this.element.children(".ui-menu-item:first");
605                        }
606                        this.activate(event, result);
607                } else {
608                        this.activate(event, this.element.children(".ui-menu-item")
609                                .filter(!this.active || this.first() ? ":last" : ":first"));
610                }
611        },
612
613        hasScroll: function() {
614                return this.element.height() < this.element[ $.fn.prop ? "prop" : "attr" ]("scrollHeight");
615        },
616
617        select: function( event ) {
618                this._trigger("selected", event, { item: this.active });
619        }
620});
621
622}(jQuery));
Note: See TracBrowser for help on using the repository browser.