1/* 2 * jQuery UI Autocomplete 1.8 3 * 4 * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about) 5 * Dual licensed under the MIT (MIT-LICENSE.txt) 6 * and GPL (GPL-LICENSE.txt) licenses. 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( $ ) { 16 17$.widget( "ui.autocomplete", { 18 options: { 19 minLength: 1, 20 delay: 300 21 }, 22 _create: function() { 23 var self = this, 24 doc = this.element[ 0 ].ownerDocument; 25 this.element 26 .addClass( "ui-autocomplete-input" ) 27 .attr( "autocomplete", "off" ) 28 // TODO verify these actually work as intended 29 .attr({ 30 role: "textbox", 31 "aria-autocomplete": "list", 32 "aria-haspopup": "true" 33 }) 34 .bind( "keydown.autocomplete", function( event ) { 35 var keyCode = $.ui.keyCode; 36 switch( event.keyCode ) { 37 case keyCode.PAGE_UP: 38 self._move( "previousPage", event ); 39 break; 40 case keyCode.PAGE_DOWN: 41 self._move( "nextPage", event ); 42 break; 43 case keyCode.UP: 44 self._move( "previous", event ); 45 // prevent moving cursor to beginning of text field in some browsers 46 event.preventDefault(); 47 break; 48 case keyCode.DOWN: 49 self._move( "next", event ); 50 // prevent moving cursor to end of text field in some browsers 51 event.preventDefault(); 52 break; 53 case keyCode.ENTER: 54 // when menu is open or has focus 55 if ( self.menu.active ) { 56 event.preventDefault(); 57 } 58 //passthrough - ENTER and TAB both select the current element 59 case keyCode.TAB: 60 if ( !self.menu.active ) { 61 return; 62 } 63 self.menu.select(); 64 break; 65 case keyCode.ESCAPE: 66 self.element.val( self.term ); 67 self.close( event ); 68 break; 69 case keyCode.SHIFT: 70 case keyCode.CONTROL: 71 case 18: 72 // ignore metakeys (shift, ctrl, alt) 73 break; 74 default: 75 // keypress is triggered before the input value is changed 76 clearTimeout( self.searching ); 77 self.searching = setTimeout(function() { 78 self.search( null, event ); 79 }, self.options.delay ); 80 break; 81 } 82 }) 83 .bind( "focus.autocomplete", function() { 84 self.previous = self.element.val(); 85 }) 86 .bind( "blur.autocomplete", function( event ) { 87 clearTimeout( self.searching ); 88 // clicks on the menu (or a button to trigger a search) will cause a blur event 89 // TODO try to implement this without a timeout, see clearTimeout in search() 90 self.closing = setTimeout(function() { 91 self.close( event ); 92 }, 150 ); 93 }); 94 this._initSource(); 95 this.response = function() { 96 return self._response.apply( self, arguments ); 97 }; 98 this.menu = $( "<ul></ul>" ) 99 .addClass( "ui-autocomplete" ) 100 .appendTo( "body", doc ) 101 .menu({ 102 focus: function( event, ui ) { 103 var item = ui.item.data( "item.autocomplete" ); 104 if ( false !== self._trigger( "focus", null, { item: item } ) ) { 105 // use value to match what will end up in the input 106 self.element.val( item.value ); 107 } 108 }, 109 selected: function( event, ui ) { 110 var item = ui.item.data( "item.autocomplete" ); 111 if ( false !== self._trigger( "select", event, { item: item } ) ) { 112 self.element.val( item.value ); 113 } 114 self.close( event ); 115 self.previous = self.element.val(); 116 // only trigger when focus was lost (click on menu) 117 if ( self.element[0] !== doc.activeElement ) { 118 self.element.focus(); 119 } 120 }, 121 blur: function( event, ui ) { 122 if ( self.menu.element.is(":visible") ) { 123 self.element.val( self.term ); 124 } 125 } 126 }) 127 .zIndex( this.element.zIndex() + 1 ) 128 // workaround for jQuery bug #5781 http://dev.jquery.com/ticket/5781 129 .css({ top: 0, left: 0 }) 130 .hide() 131 .data( "menu" ); 132 if ( $.fn.bgiframe ) { 133 this.menu.element.bgiframe(); 134 } 135 }, 136 137 destroy: function() { 138 this.element 139 .removeClass( "ui-autocomplete-input ui-widget ui-widget-content" ) 140 .removeAttr( "autocomplete" ) 141 .removeAttr( "role" ) 142 .removeAttr( "aria-autocomplete" ) 143 .removeAttr( "aria-haspopup" ); 144 this.menu.element.remove(); 145 $.Widget.prototype.destroy.call( this ); 146 }, 147 148 _setOption: function( key ) { 149 $.Widget.prototype._setOption.apply( this, arguments ); 150 if ( key === "source" ) { 151 this._initSource(); 152 } 153 }, 154 155 _initSource: function() { 156 var array, 157 url; 158 if ( $.isArray(this.options.source) ) { 159 array = this.options.source; 160 this.source = function( request, response ) { 161 // escape regex characters 162 var matcher = new RegExp( $.ui.autocomplete.escapeRegex(request.term), "i" ); 163 response( $.grep( array, function(value) { 164 return matcher.test( value.label || value.value || value ); 165 }) ); 166 }; 167 } else if ( typeof this.options.source === "string" ) { 168 url = this.options.source; 169 this.source = function( request, response ) { 170 $.getJSON( url, request, response ); 171 }; 172 } else { 173 this.source = this.options.source; 174 } 175 }, 176 177 search: function( value, event ) { 178 value = value != null ? value : this.element.val(); 179 if ( value.length < this.options.minLength ) { 180 return this.close( event ); 181 } 182 183 clearTimeout( this.closing ); 184 if ( this._trigger("search") === false ) { 185 return; 186 } 187 188 return this._search( value ); 189 }, 190 191 _search: function( value ) { 192 this.term = this.element 193 .addClass( "ui-autocomplete-loading" ) 194 // always save the actual value, not the one passed as an argument 195 .val(); 196 197 this.source( { term: value }, this.response ); 198 }, 199 200 _response: function( content ) { 201 if ( content.length ) { 202 content = this._normalize( content ); 203 this._suggest( content ); 204 this._trigger( "open" ); 205 } else { 206 this.close(); 207 } 208 this.element.removeClass( "ui-autocomplete-loading" ); 209 }, 210 211 close: function( event ) { 212 clearTimeout( this.closing ); 213 if ( this.menu.element.is(":visible") ) { 214 this._trigger( "close", event ); 215 this.menu.element.hide(); 216 this.menu.deactivate(); 217 } 218 if ( this.previous !== this.element.val() ) { 219 this._trigger( "change", event ); 220 } 221 }, 222 223 _normalize: function( items ) { 224 // assume all items have the right format when the first item is complete 225 if ( items.length && items[0].label && items[0].value ) { 226 return items; 227 } 228 return $.map( items, function(item) { 229 if ( typeof item === "string" ) { 230 return { 231 label: item, 232 value: item 233 }; 234 } 235 return $.extend({ 236 label: item.label || item.value, 237 value: item.value || item.label 238 }, item ); 239 }); 240 }, 241 242 _suggest: function( items ) { 243 var ul = this.menu.element 244 .empty() 245 .zIndex( this.element.zIndex() + 1 ), 246 menuWidth, 247 textWidth; 248 this._renderMenu( ul, items ); 249 // TODO refresh should check if the active item is still in the dom, removing the need for a manual deactivate 250 this.menu.deactivate(); 251 this.menu.refresh(); 252 this.menu.element.show().position({ 253 my: "left top", 254 at: "left bottom", 255 of: this.element, 256 collision: "none" 257 }); 258 259 menuWidth = ul.width( "" ).width(); 260 textWidth = this.element.width(); 261 ul.width( Math.max( menuWidth, textWidth ) ); 262 }, 263 264 _renderMenu: function( ul, items ) { 265 var self = this; 266 $.each( items, function( index, item ) { 267 self._renderItem( ul, item ); 268 }); 269 }, 270 271 _renderItem: function( ul, item) { 272 return $( "<li></li>" ) 273 .data( "item.autocomplete", item ) 274 .append( "<a>" + item.label + "</a>" ) 275 .appendTo( ul ); 276 }, 277 278 _move: function( direction, event ) { 279 if ( !this.menu.element.is(":visible") ) { 280 this.search( null, event ); 281 return; 282 } 283 if ( this.menu.first() && /^previous/.test(direction) || 284 this.menu.last() && /^next/.test(direction) ) { 285 this.element.val( this.term ); 286 this.menu.deactivate(); 287 return; 288 } 289 this.menu[ direction ](); 290 }, 291 292 widget: function() { 293 return this.menu.element; 294 } 295}); 296 297$.extend( $.ui.autocomplete, { 298 escapeRegex: function( value ) { 299 return value.replace( /([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1" ); 300 } 301}); 302 303}( jQuery )); 304 305/* 306 * jQuery UI Menu (not officially released) 307 * 308 * This widget isn't yet finished and the API is subject to change. We plan to finish 309 * it for the next release. You're welcome to give it a try anyway and give us feedback,
310 * as long as you're okay with migrating your code later on. We can help with that, too. 311 * 312 * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about) 313 * Dual licensed under the MIT (MIT-LICENSE.txt) 314 * and GPL (GPL-LICENSE.txt) licenses. 315 * 316 * http://docs.jquery.com/UI/Menu 317 * 318 * Depends: 319 * jquery.ui.core.js 320 * jquery.ui.widget.js 321 */ 322(function($) { 323 324$.widget("ui.menu", { 325 _create: function() { 326 var self = this; 327 this.element 328 .addClass("ui-menu ui-widget ui-widget-content ui-corner-all") 329 .attr({ 330 role: "listbox", 331 "aria-activedescendant": "ui-active-menuitem" 332 }) 333 .click(function(e) { 334 // temporary 335 e.preventDefault(); 336 self.select(); 337 }); 338 this.refresh(); 339 }, 340 341 refresh: function() { 342 var self = this; 343 344 // don't refresh list items that are already adapted 345 var items = this.element.children("li:not(.ui-menu-item):has(a)") 346 .addClass("ui-menu-item") 347 .attr("role", "menuitem"); 348 349 items.children("a") 350 .addClass("ui-corner-all") 351 .attr("tabindex", -1) 352 // mouseenter doesn't work with event delegation 353 .mouseenter(function() { 354 self.activate($(this).parent()); 355 }) 356 .mouseleave(function() { 357 self.deactivate(); 358 }); 359 }, 360 361 activate: function(item) { 362 this.deactivate(); 363 if (this.hasScroll()) { 364 var offset = item.offset().top - this.element.offset().top, 365 scroll = this.element.attr("scrollTop"), 366 elementHeight = this.element.height(); 367 if (offset < 0) { 368 this.element.attr("scrollTop", scroll + offset); 369 } else if (offset > elementHeight) { 370 this.element.attr("scrollTop", scroll + offset - elementHeight + item.height()); 371 } 372 } 373 this.active = item.eq(0) 374 .children("a") 375 .addClass("ui-state-hover") 376 .attr("id", "ui-active-menuitem") 377 .end(); 378 this._trigger("focus", null, { item: item }); 379 }, 380 381 deactivate: function() { 382 if (!this.active) { return; } 383 384 this.active.children("a") 385 .removeClass("ui-state-hover") 386 .removeAttr("id"); 387 this._trigger("blur"); 388 this.active = null; 389 }, 390 391 next: function() { 392 this.move("next", "li:first"); 393 }, 394 395 previous: function() { 396 this.move("prev", "li:last"); 397 }, 398 399 first: function() { 400 return this.active && !this.active.prev().length; 401 }, 402 403 last: function() { 404 return this.active && !this.active.next().length; 405 }, 406 407 move: function(direction, edge) { 408 if (!this.active) { 409 this.activate(this.element.children(edge)); 410 return; 411 } 412 var next = this.active[direction](); 413 if (next.length) { 414 this.activate(next); 415 } else { 416 this.activate(this.element.children(edge)); 417 } 418 }, 419 420 // TODO merge with previousPage 421 nextPage: function() { 422 if (this.hasScroll()) { 423 // TODO merge with no-scroll-else 424 if (!this.active || this.last()) { 425 this.activate(this.element.children(":first")); 426 return; 427 } 428 var base = this.active.offset().top, 429 height = this.element.height(), 430 result = this.element.children("li").filter(function() { 431 var close = $(this).offset().top - base - height + $(this).height(); 432 // TODO improve approximation 433 return close < 10 && close > -10; 434 }); 435 436 // TODO try to catch this earlier when scrollTop indicates the last page anyway 437 if (!result.length) { 438 result = this.element.children(":last"); 439 } 440 this.activate(result); 441 } else { 442 this.activate(this.element.children(!this.active || this.last() ? ":first" : ":last")); 443 } 444 }, 445 446 // TODO merge with nextPage 447 previousPage: function() { 448 if (this.hasScroll()) { 449 // TODO merge with no-scroll-else 450 if (!this.active || this.first()) { 451 this.activate(this.element.children(":last")); 452 return; 453 } 454 455 var base = this.active.offset().top, 456 height = this.element.height(); 457 result = this.element.children("li").filter(function() { 458 var close = $(this).offset().top - base + height - $(this).height(); 459 // TODO improve approximation 460 return close < 10 && close > -10; 461 }); 462 463 // TODO try to catch this earlier when scrollTop indicates the last page anyway 464 if (!result.length) { 465 result = this.element.children(":first"); 466 } 467 this.activate(result); 468 } else { 469 this.activate(this.element.children(!this.active || this.first() ? ":last" : ":first")); 470 } 471 }, 472 473 hasScroll: function() { 474 return this.element.height() < this.element.attr("scrollHeight"); 475 }, 476 477 select: function() { 478 this._trigger("selected", null, { item: this.active }); 479 } 480}); 481 482}(jQuery));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.