2 * jQuery UI Autocomplete 1.8.18
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", {
38 doc
= this.element
[ 0 ].ownerDocument
,
42 .addClass( "ui-autocomplete-input" )
43 .attr( "autocomplete", "off" )
44 // TODO verify these actually work as intended
47 "aria-autocomplete": "list",
48 "aria-haspopup": "true"
50 .bind( "keydown.autocomplete", function( event
) {
51 if ( self
.options
.disabled
|| self
.element
.propAttr( "readOnly" ) ) {
55 suppressKeyPress
= false;
56 var keyCode
= $.ui
.keyCode
;
57 switch( event
.keyCode
) {
59 self
._move( "previousPage", event
);
61 case keyCode
.PAGE_DOWN
:
62 self
._move( "nextPage", event
);
65 self
._move( "previous", event
);
66 // prevent moving cursor to beginning of text field in some browsers
67 event
.preventDefault();
70 self
._move( "next", event
);
71 // prevent moving cursor to end of text field in some browsers
72 event
.preventDefault();
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();
83 //passthrough - ENTER and TAB both select the current element
85 if ( !self
.menu
.active
) {
88 self
.menu
.select( event
);
91 self
.element
.val( self
.term
);
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
);
103 }, self
.options
.delay
);
107 .bind( "keypress.autocomplete", function( event
) {
108 if ( suppressKeyPress
) {
109 suppressKeyPress
= false;
110 event
.preventDefault();
113 .bind( "focus.autocomplete", function() {
114 if ( self
.options
.disabled
) {
118 self
.selectedItem
= null;
119 self
.previous
= self
.element
.val();
121 .bind( "blur.autocomplete", function( event
) {
122 if ( self
.options
.disabled
) {
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() {
130 self
._change( event
);
134 this.response = function() {
135 return self
._response
.apply( self
, arguments
);
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
) ) {
159 // use another timeout to make sure the blur-event-handler on the input was already triggered
160 setTimeout(function() {
161 clearTimeout( self
.closing
);
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
);
174 selected: function( event
, ui
) {
175 var item
= ui
.item
.data( "item.autocomplete" ),
176 previous
= self
.previous
;
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
;
191 if ( false !== self
._trigger( "select", event
, { item
: item
} ) ) {
192 self
.element
.val( item
.value
);
194 // reset the term after the select event
195 // this allows custom select handling to work properly
196 self
.term
= self
.element
.val();
199 self
.selectedItem
= item
;
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
);
210 .zIndex( this.element
.zIndex() + 1 )
211 // workaround for jQuery bug #5781 http://dev.jquery.com/ticket/5781
212 .css({ top
: 0, left
: 0 })
215 if ( $.fn
.bgiframe
) {
216 this.menu
.element
.bgiframe();
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" );
224 $( window
).bind( "beforeunload", self
.beforeunloadHandler
);
227 destroy: function() {
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 );
239 _setOption: function( key
, value
) {
240 $.Widget
.prototype._setOption
.apply( this, arguments
);
241 if ( key
=== "source" ) {
244 if ( key
=== "appendTo" ) {
245 this.menu
.element
.appendTo( $( value
|| "body", this.element
[0].ownerDocument
)[0] )
247 if ( key
=== "disabled" && value
&& this.xhr
) {
252 _initSource: function() {
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
) );
261 } else if ( typeof this.options
.source
=== "string" ) {
262 url
= this.options
.source
;
263 this.source = function( request
, response
) {
272 autocompleteRequest
: ++requestIndex
274 success: function( data
, status
) {
275 if ( this.autocompleteRequest
=== requestIndex
) {
280 if ( this.autocompleteRequest
=== requestIndex
) {
287 this.source
= this.options
.source
;
291 search: function( value
, event
) {
292 value
= value
!= null ? value
: this.element
.val();
294 // always save the actual value, not the one passed as an argument
295 this.term
= this.element
.val();
297 if ( value
.length
< this.options
.minLength
) {
298 return this.close( event
);
301 clearTimeout( this.closing
);
302 if ( this._trigger( "search", event
) === false ) {
306 return this._search( value
);
309 _search: function( value
) {
311 this.element
.addClass( "ui-autocomplete-loading" );
313 this.source( { term
: value
}, this.response
);
316 _response: function( content
) {
317 if ( !this.options
.disabled
&& content
&& content
.length
) {
318 content
= this._normalize( content
);
319 this._suggest( content
);
320 this._trigger( "open" );
325 if ( !this.pending
) {
326 this.element
.removeClass( "ui-autocomplete-loading" );
330 close: function( event
) {
331 clearTimeout( this.closing
);
332 if ( this.menu
.element
.is(":visible") ) {
333 this.menu
.element
.hide();
334 this.menu
.deactivate();
335 this._trigger( "close", event
);
339 _change: function( event
) {
340 if ( this.previous
!== this.element
.val() ) {
341 this._trigger( "change", event
, { item
: this.selectedItem
} );
345 _normalize: function( items
) {
346 // assume all items have the right format when the first item is complete
347 if ( items
.length
&& items
[0].label
&& items
[0].value
) {
350 return $.map( items
, function(item
) {
351 if ( typeof item
=== "string" ) {
358 label
: item
.label
|| item
.value
,
359 value
: item
.value
|| item
.label
364 _suggest: function( items
) {
365 var ul
= this.menu
.element
367 .zIndex( this.element
.zIndex() + 1 );
368 this._renderMenu( ul
, items
);
369 // TODO refresh should check if the active item is still in the dom, removing the need for a manual deactivate
370 this.menu
.deactivate();
373 // size and position menu
376 ul
.position( $.extend({
378 }, this.options
.position
));
380 if ( this.options
.autoFocus
) {
381 this.menu
.next( new $.Event("mouseover") );
385 _resizeMenu: function() {
386 var ul
= this.menu
.element
;
387 ul
.outerWidth( Math
.max(
388 // Firefox wraps long text (possibly a rounding bug)
389 // so we add 1px to avoid the wrapping (#7513)
390 ul
.width( "" ).outerWidth() + 1,
391 this.element
.outerWidth()
395 _renderMenu: function( ul
, items
) {
397 $.each( items
, function( index
, item
) {
398 self
._renderItem( ul
, item
);
402 _renderItem: function( ul
, item
) {
403 return $( "<li></li>" )
404 .data( "item.autocomplete", item
)
405 .append( $( "<a></a>" ).text( item
.label
) )
409 _move: function( direction
, event
) {
410 if ( !this.menu
.element
.is(":visible") ) {
411 this.search( null, event
);
414 if ( this.menu
.first() && /^previous/.test(direction
) ||
415 this.menu
.last() && /^next/.test(direction
) ) {
416 this.element
.val( this.term
);
417 this.menu
.deactivate();
420 this.menu
[ direction
]( event
);
424 return this.menu
.element
;
428 $.extend( $.ui
.autocomplete
, {
429 escapeRegex: function( value
) {
430 return value
.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
432 filter: function(array
, term
) {
433 var matcher
= new RegExp( $.ui
.autocomplete
.escapeRegex(term
), "i" );
434 return $.grep( array
, function(value
) {
435 return matcher
.test( value
.label
|| value
.value
|| value
);
443 * jQuery UI Menu (not officially released)
445 * This widget isn't yet finished and the API is subject to change. We plan to finish
446 * it for the next release. You're welcome to give it a try anyway and give us feedback,
447 * as long as you're okay with migrating your code later on. We can help with that, too.
449 * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
450 * Dual licensed under the MIT or GPL Version 2 licenses.
451 * http://jquery.org/license
453 * http://docs.jquery.com/UI/Menu
457 * jquery.ui.widget.js
461 $.widget("ui.menu", {
462 _create: function() {
465 .addClass("ui-menu ui-widget ui-widget-content ui-corner-all")
468 "aria-activedescendant": "ui-active-menuitem"
470 .click(function( event
) {
471 if ( !$( event
.target
).closest( ".ui-menu-item a" ).length
) {
475 event
.preventDefault();
476 self
.select( event
);
481 refresh: function() {
484 // don't refresh list items that are already adapted
485 var items
= this.element
.children("li:not(.ui-menu-item):has(a)")
486 .addClass("ui-menu-item")
487 .attr("role", "menuitem");
490 .addClass("ui-corner-all")
491 .attr("tabindex", -1)
492 // mouseenter doesn't work with event delegation
493 .mouseenter(function( event
) {
494 self
.activate( event
, $(this).parent() );
496 .mouseleave(function() {
501 activate: function( event
, item
) {
503 if (this.hasScroll()) {
504 var offset
= item
.offset().top
- this.element
.offset().top
,
505 scroll
= this.element
.scrollTop(),
506 elementHeight
= this.element
.height();
508 this.element
.scrollTop( scroll
+ offset
);
509 } else if (offset
>= elementHeight
) {
510 this.element
.scrollTop( scroll
+ offset
- elementHeight
+ item
.height());
513 this.active
= item
.eq(0)
515 .addClass("ui-state-hover")
516 .attr("id", "ui-active-menuitem")
518 this._trigger("focus", event
, { item
: item
});
521 deactivate: function() {
522 if (!this.active
) { return; }
524 this.active
.children("a")
525 .removeClass("ui-state-hover")
527 this._trigger("blur");
531 next: function(event
) {
532 this.move("next", ".ui-menu-item:first", event
);
535 previous: function(event
) {
536 this.move("prev", ".ui-menu-item:last", event
);
540 return this.active
&& !this.active
.prevAll(".ui-menu-item").length
;
544 return this.active
&& !this.active
.nextAll(".ui-menu-item").length
;
547 move: function(direction
, edge
, event
) {
549 this.activate(event
, this.element
.children(edge
));
552 var next
= this.active
[direction
+ "All"](".ui-menu-item").eq(0);
554 this.activate(event
, next
);
556 this.activate(event
, this.element
.children(edge
));
560 // TODO merge with previousPage
561 nextPage: function(event
) {
562 if (this.hasScroll()) {
563 // TODO merge with no-scroll-else
564 if (!this.active
|| this.last()) {
565 this.activate(event
, this.element
.children(".ui-menu-item:first"));
568 var base
= this.active
.offset().top
,
569 height
= this.element
.height(),
570 result
= this.element
.children(".ui-menu-item").filter(function() {
571 var close
= $(this).offset().top
- base
- height
+ $(this).height();
572 // TODO improve approximation
573 return close
< 10 && close
> -10;
576 // TODO try to catch this earlier when scrollTop indicates the last page anyway
577 if (!result
.length
) {
578 result
= this.element
.children(".ui-menu-item:last");
580 this.activate(event
, result
);
582 this.activate(event
, this.element
.children(".ui-menu-item")
583 .filter(!this.active
|| this.last() ? ":first" : ":last"));
587 // TODO merge with nextPage
588 previousPage: function(event
) {
589 if (this.hasScroll()) {
590 // TODO merge with no-scroll-else
591 if (!this.active
|| this.first()) {
592 this.activate(event
, this.element
.children(".ui-menu-item:last"));
596 var base
= this.active
.offset().top
,
597 height
= this.element
.height();
598 result
= this.element
.children(".ui-menu-item").filter(function() {
599 var close
= $(this).offset().top
- base
+ height
- $(this).height();
600 // TODO improve approximation
601 return close
< 10 && close
> -10;
604 // TODO try to catch this earlier when scrollTop indicates the last page anyway
605 if (!result
.length
) {
606 result
= this.element
.children(".ui-menu-item:first");
608 this.activate(event
, result
);
610 this.activate(event
, this.element
.children(".ui-menu-item")
611 .filter(!this.active
|| this.first() ? ":last" : ":first"));
615 hasScroll: function() {
616 return this.element
.height() < this.element
[ $.fn
.prop
? "prop" : "attr" ]("scrollHeight");
619 select: function( event
) {
620 this._trigger("selected", event
, { item
: this.active
});