2 * jQuery UI Autocomplete 1.8.16
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();
220 destroy: function() {
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 );
231 _setOption: function( key
, value
) {
232 $.Widget
.prototype._setOption
.apply( this, arguments
);
233 if ( key
=== "source" ) {
236 if ( key
=== "appendTo" ) {
237 this.menu
.element
.appendTo( $( value
|| "body", this.element
[0].ownerDocument
)[0] )
239 if ( key
=== "disabled" && value
&& this.xhr
) {
244 _initSource: function() {
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
) );
253 } else if ( typeof this.options
.source
=== "string" ) {
254 url
= this.options
.source
;
255 this.source = function( request
, response
) {
263 autocompleteRequest
: ++requestIndex
,
264 success: function( data
, status
) {
265 if ( this.autocompleteRequest
=== requestIndex
) {
270 if ( this.autocompleteRequest
=== requestIndex
) {
277 this.source
= this.options
.source
;
281 search: function( value
, event
) {
282 value
= value
!= null ? value
: this.element
.val();
284 // always save the actual value, not the one passed as an argument
285 this.term
= this.element
.val();
287 if ( value
.length
< this.options
.minLength
) {
288 return this.close( event
);
291 clearTimeout( this.closing
);
292 if ( this._trigger( "search", event
) === false ) {
296 return this._search( value
);
299 _search: function( value
) {
301 this.element
.addClass( "ui-autocomplete-loading" );
303 this.source( { term
: value
}, this.response
);
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" );
315 if ( !this.pending
) {
316 this.element
.removeClass( "ui-autocomplete-loading" );
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
);
329 _change: function( event
) {
330 if ( this.previous
!== this.element
.val() ) {
331 this._trigger( "change", event
, { item
: this.selectedItem
} );
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
) {
340 return $.map( items
, function(item
) {
341 if ( typeof item
=== "string" ) {
348 label
: item
.label
|| item
.value
,
349 value
: item
.value
|| item
.label
354 _suggest: function( items
) {
355 var ul
= this.menu
.element
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();
363 // size and position menu
366 ul
.position( $.extend({
368 }, this.options
.position
));
370 if ( this.options
.autoFocus
) {
371 this.menu
.next( new $.Event("mouseover") );
375 _resizeMenu: function() {
376 var ul
= this.menu
.element
;
377 ul
.outerWidth( Math
.max(
378 ul
.width( "" ).outerWidth(),
379 this.element
.outerWidth()
383 _renderMenu: function( ul
, items
) {
385 $.each( items
, function( index
, item
) {
386 self
._renderItem( ul
, item
);
390 _renderItem: function( ul
, item
) {
391 return $( "<li></li>" )
392 .data( "item.autocomplete", item
)
393 .append( $( "<a></a>" ).text( item
.label
) )
397 _move: function( direction
, event
) {
398 if ( !this.menu
.element
.is(":visible") ) {
399 this.search( null, event
);
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();
408 this.menu
[ direction
]( event
);
412 return this.menu
.element
;
416 $.extend( $.ui
.autocomplete
, {
417 escapeRegex: function( value
) {
418 return value
.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
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
);
431 * jQuery UI Menu (not officially released)
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.
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
441 * http://docs.jquery.com/UI/Menu
445 * jquery.ui.widget.js
449 $.widget("ui.menu", {
450 _create: function() {
453 .addClass("ui-menu ui-widget ui-widget-content ui-corner-all")
456 "aria-activedescendant": "ui-active-menuitem"
458 .click(function( event
) {
459 if ( !$( event
.target
).closest( ".ui-menu-item a" ).length
) {
463 event
.preventDefault();
464 self
.select( event
);
469 refresh: function() {
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");
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() );
484 .mouseleave(function() {
489 activate: function( event
, item
) {
491 if (this.hasScroll()) {
492 var offset
= item
.offset().top
- this.element
.offset().top
,
493 scroll
= this.element
.scrollTop(),
494 elementHeight
= this.element
.height();
496 this.element
.scrollTop( scroll
+ offset
);
497 } else if (offset
>= elementHeight
) {
498 this.element
.scrollTop( scroll
+ offset
- elementHeight
+ item
.height());
501 this.active
= item
.eq(0)
503 .addClass("ui-state-hover")
504 .attr("id", "ui-active-menuitem")
506 this._trigger("focus", event
, { item
: item
});
509 deactivate: function() {
510 if (!this.active
) { return; }
512 this.active
.children("a")
513 .removeClass("ui-state-hover")
515 this._trigger("blur");
519 next: function(event
) {
520 this.move("next", ".ui-menu-item:first", event
);
523 previous: function(event
) {
524 this.move("prev", ".ui-menu-item:last", event
);
528 return this.active
&& !this.active
.prevAll(".ui-menu-item").length
;
532 return this.active
&& !this.active
.nextAll(".ui-menu-item").length
;
535 move: function(direction
, edge
, event
) {
537 this.activate(event
, this.element
.children(edge
));
540 var next
= this.active
[direction
+ "All"](".ui-menu-item").eq(0);
542 this.activate(event
, next
);
544 this.activate(event
, this.element
.children(edge
));
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"));
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;
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");
568 this.activate(event
, result
);
570 this.activate(event
, this.element
.children(".ui-menu-item")
571 .filter(!this.active
|| this.last() ? ":first" : ":last"));
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"));
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;
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");
596 this.activate(event
, result
);
598 this.activate(event
, this.element
.children(".ui-menu-item")
599 .filter(!this.active
|| this.first() ? ":last" : ":first"));
603 hasScroll: function() {
604 return this.element
.height() < this.element
[ $.fn
.prop
? "prop" : "attr" ]("scrollHeight");
607 select: function( event
) {
608 this._trigger("selected", event
, { item
: this.active
});