2 * jQuery UI Autocomplete 1.8.10
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
8 * http://docs.jquery.com/UI/Autocomplete
13 * jquery.ui.position.js
15 (function( $, undefined ) {
17 // used to prevent race conditions with remote data sources
20 $.widget( "ui.autocomplete", {
37 doc = this.element[ 0 ].ownerDocument,
41 .addClass( "ui-autocomplete-input" )
42 .attr( "autocomplete", "off" )
43 // TODO verify these actually work as intended
46 "aria-autocomplete": "list",
47 "aria-haspopup": "true"
49 .bind( "keydown.autocomplete", function( event ) {
50 if ( self.options.disabled || self.element.attr( "readonly" ) ) {
54 suppressKeyPress = false;
55 var keyCode = $.ui.keyCode;
56 switch( event.keyCode ) {
58 self._move( "previousPage", event );
60 case keyCode.PAGE_DOWN:
61 self._move( "nextPage", event );
64 self._move( "previous", event );
65 // prevent moving cursor to beginning of text field in some browsers
66 event.preventDefault();
69 self._move( "next", event );
70 // prevent moving cursor to end of text field in some browsers
71 event.preventDefault();
74 case keyCode.NUMPAD_ENTER:
75 // when menu is open and has focus
76 if ( self.menu.active ) {
77 // #6055 - Opera still allows the keypress to occur
78 // which causes forms to submit
79 suppressKeyPress = true;
80 event.preventDefault();
82 //passthrough - ENTER and TAB both select the current element
84 if ( !self.menu.active ) {
87 self.menu.select( event );
90 self.element.val( self.term );
94 // keypress is triggered before the input value is changed
95 clearTimeout( self.searching );
96 self.searching = setTimeout(function() {
97 // only search if the value has changed
98 if ( self.term != self.element.val() ) {
99 self.selectedItem = null;
100 self.search( null, event );
102 }, self.options.delay );
106 .bind( "keypress.autocomplete", function( event ) {
107 if ( suppressKeyPress ) {
108 suppressKeyPress = false;
109 event.preventDefault();
112 .bind( "focus.autocomplete", function() {
113 if ( self.options.disabled ) {
117 self.selectedItem = null;
118 self.previous = self.element.val();
120 .bind( "blur.autocomplete", function( event ) {
121 if ( self.options.disabled ) {
125 clearTimeout( self.searching );
126 // clicks on the menu (or a button to trigger a search) will cause a blur event
127 self.closing = setTimeout(function() {
129 self._change( event );
133 this.response = function() {
134 return self._response.apply( self, arguments );
136 this.menu = $( "<ul></ul>" )
137 .addClass( "ui-autocomplete" )
138 .appendTo( $( this.options.appendTo || "body", doc )[0] )
139 // prevent the close-on-blur in case of a "slow" click on the menu (long mousedown)
140 .mousedown(function( event ) {
141 // clicking on the scrollbar causes focus to shift to the body
142 // but we can't detect a mouseup or a click immediately afterward
143 // so we have to track the next mousedown and close the menu if
144 // the user clicks somewhere outside of the autocomplete
145 var menuElement = self.menu.element[ 0 ];
146 if ( !$( event.target ).closest( ".ui-menu-item" ).length ) {
147 setTimeout(function() {
148 $( document ).one( 'mousedown', function( event ) {
149 if ( event.target !== self.element[ 0 ] &&
150 event.target !== menuElement &&
151 !$.ui.contains( menuElement, event.target ) ) {
158 // use another timeout to make sure the blur-event-handler on the input was already triggered
159 setTimeout(function() {
160 clearTimeout( self.closing );
164 focus: function( event, ui ) {
165 var item = ui.item.data( "item.autocomplete" );
166 if ( false !== self._trigger( "focus", event, { item: item } ) ) {
167 // use value to match what will end up in the input, if it was a key event
168 if ( /^key/.test(event.originalEvent.type) ) {
169 self.element.val( item.value );
173 selected: function( event, ui ) {
174 var item = ui.item.data( "item.autocomplete" ),
175 previous = self.previous;
177 // only trigger when focus was lost (click on menu)
178 if ( self.element[0] !== doc.activeElement ) {
179 self.element.focus();
180 self.previous = previous;
181 // #6109 - IE triggers two focus events and the second
182 // is asynchronous, so we need to reset the previous
183 // term synchronously and asynchronously :-(
184 setTimeout(function() {
185 self.previous = previous;
186 self.selectedItem = item;
190 if ( false !== self._trigger( "select", event, { item: item } ) ) {
191 self.element.val( item.value );
193 // reset the term after the select event
194 // this allows custom select handling to work properly
195 self.term = self.element.val();
198 self.selectedItem = item;
200 blur: function( event, ui ) {
201 // don't set the value of the text field if it's already correct
202 // this prevents moving the cursor unnecessarily
203 if ( self.menu.element.is(":visible") &&
204 ( self.element.val() !== self.term ) ) {
205 self.element.val( self.term );
209 .zIndex( this.element.zIndex() + 1 )
210 // workaround for jQuery bug #5781 http://dev.jquery.com/ticket/5781
211 .css({ top: 0, left: 0 })
214 if ( $.fn.bgiframe ) {
215 this.menu.element.bgiframe();
219 destroy: function() {
221 .removeClass( "ui-autocomplete-input" )
222 .removeAttr( "autocomplete" )
223 .removeAttr( "role" )
224 .removeAttr( "aria-autocomplete" )
225 .removeAttr( "aria-haspopup" );
226 this.menu.element.remove();
227 $.Widget.prototype.destroy.call( this );
230 _setOption: function( key, value ) {
231 $.Widget.prototype._setOption.apply( this, arguments );
232 if ( key === "source" ) {
235 if ( key === "appendTo" ) {
236 this.menu.element.appendTo( $( value || "body", this.element[0].ownerDocument )[0] )
238 if ( key === "disabled" && value && this.xhr ) {
243 _initSource: function() {
247 if ( $.isArray(this.options.source) ) {
248 array = this.options.source;
249 this.source = function( request, response ) {
250 response( $.ui.autocomplete.filter(array, request.term) );
252 } else if ( typeof this.options.source === "string" ) {
253 url = this.options.source;
254 this.source = function( request, response ) {
262 autocompleteRequest: ++requestIndex,
263 success: function( data, status ) {
264 if ( this.autocompleteRequest === requestIndex ) {
269 if ( this.autocompleteRequest === requestIndex ) {
276 this.source = this.options.source;
280 search: function( value, event ) {
281 value = value != null ? value : this.element.val();
283 // always save the actual value, not the one passed as an argument
284 this.term = this.element.val();
286 if ( value.length < this.options.minLength ) {
287 return this.close( event );
290 clearTimeout( this.closing );
291 if ( this._trigger( "search", event ) === false ) {
295 return this._search( value );
298 _search: function( value ) {
300 this.element.addClass( "ui-autocomplete-loading" );
302 this.source( { term: value }, this.response );
305 _response: function( content ) {
306 if ( !this.options.disabled && content && content.length ) {
307 content = this._normalize( content );
308 this._suggest( content );
309 this._trigger( "open" );
314 if ( !this.pending ) {
315 this.element.removeClass( "ui-autocomplete-loading" );
319 close: function( event ) {
320 clearTimeout( this.closing );
321 if ( this.menu.element.is(":visible") ) {
322 this.menu.element.hide();
323 this.menu.deactivate();
324 this._trigger( "close", event );
328 _change: function( event ) {
329 if ( this.previous !== this.element.val() ) {
330 this._trigger( "change", event, { item: this.selectedItem } );
334 _normalize: function( items ) {
335 // assume all items have the right format when the first item is complete
336 if ( items.length && items[0].label && items[0].value ) {
339 return $.map( items, function(item) {
340 if ( typeof item === "string" ) {
347 label: item.label || item.value,
348 value: item.value || item.label
353 _suggest: function( items ) {
354 var ul = this.menu.element
356 .zIndex( this.element.zIndex() + 1 );
357 this._renderMenu( ul, items );
358 // TODO refresh should check if the active item is still in the dom, removing the need for a manual deactivate
359 this.menu.deactivate();
362 // size and position menu
365 ul.position( $.extend({
367 }, this.options.position ));
370 _resizeMenu: function() {
371 var ul = this.menu.element;
372 ul.outerWidth( Math.max(
373 ul.width( "" ).outerWidth(),
374 this.element.outerWidth()
378 _renderMenu: function( ul, items ) {
380 $.each( items, function( index, item ) {
381 self._renderItem( ul, item );
385 _renderItem: function( ul, item) {
386 return $( "<li></li>" )
387 .data( "item.autocomplete", item )
388 .append( $( "<a></a>" ).text( item.label ) )
392 _move: function( direction, event ) {
393 if ( !this.menu.element.is(":visible") ) {
394 this.search( null, event );
397 if ( this.menu.first() && /^previous/.test(direction) ||
398 this.menu.last() && /^next/.test(direction) ) {
399 this.element.val( this.term );
400 this.menu.deactivate();
403 this.menu[ direction ]( event );
407 return this.menu.element;
411 $.extend( $.ui.autocomplete, {
412 escapeRegex: function( value ) {
413 return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
415 filter: function(array, term) {
416 var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
417 return $.grep( array, function(value) {
418 return matcher.test( value.label || value.value || value );
426 * jQuery UI Menu (not officially released)
428 * This widget isn't yet finished and the API is subject to change. We plan to finish
429 * it for the next release. You're welcome to give it a try anyway and give us feedback,
430 * as long as you're okay with migrating your code later on. We can help with that, too.
432 * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
433 * Dual licensed under the MIT or GPL Version 2 licenses.
434 * http://jquery.org/license
436 * http://docs.jquery.com/UI/Menu
440 * jquery.ui.widget.js
444 $.widget("ui.menu", {
445 _create: function() {
448 .addClass("ui-menu ui-widget ui-widget-content ui-corner-all")
451 "aria-activedescendant": "ui-active-menuitem"
453 .click(function( event ) {
454 if ( !$( event.target ).closest( ".ui-menu-item a" ).length ) {
458 event.preventDefault();
459 self.select( event );
464 refresh: function() {
467 // don't refresh list items that are already adapted
468 var items = this.element.children("li:not(.ui-menu-item):has(a)")
469 .addClass("ui-menu-item")
470 .attr("role", "menuitem");
473 .addClass("ui-corner-all")
474 .attr("tabindex", -1)
475 // mouseenter doesn't work with event delegation
476 .mouseenter(function( event ) {
477 self.activate( event, $(this).parent() );
479 .mouseleave(function() {
484 activate: function( event, item ) {
486 if (this.hasScroll()) {
487 var offset = item.offset().top - this.element.offset().top,
488 scroll = this.element.attr("scrollTop"),
489 elementHeight = this.element.height();
491 this.element.attr("scrollTop", scroll + offset);
492 } else if (offset >= elementHeight) {
493 this.element.attr("scrollTop", scroll + offset - elementHeight + item.height());
496 this.active = item.eq(0)
498 .addClass("ui-state-hover")
499 .attr("id", "ui-active-menuitem")
501 this._trigger("focus", event, { item: item });
504 deactivate: function() {
505 if (!this.active) { return; }
507 this.active.children("a")
508 .removeClass("ui-state-hover")
510 this._trigger("blur");
514 next: function(event) {
515 this.move("next", ".ui-menu-item:first", event);
518 previous: function(event) {
519 this.move("prev", ".ui-menu-item:last", event);
523 return this.active && !this.active.prevAll(".ui-menu-item").length;
527 return this.active && !this.active.nextAll(".ui-menu-item").length;
530 move: function(direction, edge, event) {
532 this.activate(event, this.element.children(edge));
535 var next = this.active[direction + "All"](".ui-menu-item").eq(0);
537 this.activate(event, next);
539 this.activate(event, this.element.children(edge));
543 // TODO merge with previousPage
544 nextPage: function(event) {
545 if (this.hasScroll()) {
546 // TODO merge with no-scroll-else
547 if (!this.active || this.last()) {
548 this.activate(event, this.element.children(".ui-menu-item:first"));
551 var base = this.active.offset().top,
552 height = this.element.height(),
553 result = this.element.children(".ui-menu-item").filter(function() {
554 var close = $(this).offset().top - base - height + $(this).height();
555 // TODO improve approximation
556 return close < 10 && close > -10;
559 // TODO try to catch this earlier when scrollTop indicates the last page anyway
560 if (!result.length) {
561 result = this.element.children(".ui-menu-item:last");
563 this.activate(event, result);
565 this.activate(event, this.element.children(".ui-menu-item")
566 .filter(!this.active || this.last() ? ":first" : ":last"));
570 // TODO merge with nextPage
571 previousPage: function(event) {
572 if (this.hasScroll()) {
573 // TODO merge with no-scroll-else
574 if (!this.active || this.first()) {
575 this.activate(event, this.element.children(".ui-menu-item:last"));
579 var base = this.active.offset().top,
580 height = this.element.height();
581 result = this.element.children(".ui-menu-item").filter(function() {
582 var close = $(this).offset().top - base + height - $(this).height();
583 // TODO improve approximation
584 return close < 10 && close > -10;
587 // TODO try to catch this earlier when scrollTop indicates the last page anyway
588 if (!result.length) {
589 result = this.element.children(".ui-menu-item:first");
591 this.activate(event, result);
593 this.activate(event, this.element.children(".ui-menu-item")
594 .filter(!this.active || this.first() ? ":last" : ":first"));
598 hasScroll: function() {
599 return this.element.height() < this.element.attr("scrollHeight");
602 select: function( event ) {
603 this._trigger("selected", event, { item: this.active });