[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 /*! 2 * jQuery UI Autocomplete 1.9.2 3 * http://jqueryui.com 4 * 5 * Copyright 2012 jQuery Foundation and other contributors 6 * Released under the MIT license. 7 * http://jquery.org/license 8 * 9 * http://api.jqueryui.com/autocomplete/ 10 * 11 * Depends: 12 * jquery.ui.core.js 13 * jquery.ui.widget.js 14 * jquery.ui.position.js 15 * jquery.ui.menu.js 16 */ 17 (function( $, undefined ) { 18 19 // used to prevent race conditions with remote data sources 20 var requestIndex = 0; 21 22 $.widget( "ui.autocomplete", { 23 version: "1.9.2", 24 defaultElement: "<input>", 25 options: { 26 appendTo: "body", 27 autoFocus: false, 28 delay: 300, 29 minLength: 1, 30 position: { 31 my: "left top", 32 at: "left bottom", 33 collision: "none" 34 }, 35 source: null, 36 37 // callbacks 38 change: null, 39 close: null, 40 focus: null, 41 open: null, 42 response: null, 43 search: null, 44 select: null 45 }, 46 47 pending: 0, 48 49 _create: function() { 50 // Some browsers only repeat keydown events, not keypress events, 51 // so we use the suppressKeyPress flag to determine if we've already 52 // handled the keydown event. #7269 53 // Unfortunately the code for & in keypress is the same as the up arrow, 54 // so we use the suppressKeyPressRepeat flag to avoid handling keypress 55 // events when we know the keydown event was used to modify the 56 // search term. #7799 57 var suppressKeyPress, suppressKeyPressRepeat, suppressInput; 58 59 this.isMultiLine = this._isMultiLine(); 60 this.valueMethod = this.element[ this.element.is( "input,textarea" ) ? "val" : "text" ]; 61 this.isNewMenu = true; 62 63 this.element 64 .addClass( "ui-autocomplete-input" ) 65 .attr( "autocomplete", "off" ); 66 67 this._on( this.element, { 68 keydown: function( event ) { 69 if ( this.element.prop( "readOnly" ) ) { 70 suppressKeyPress = true; 71 suppressInput = true; 72 suppressKeyPressRepeat = true; 73 return; 74 } 75 76 suppressKeyPress = false; 77 suppressInput = false; 78 suppressKeyPressRepeat = false; 79 var keyCode = $.ui.keyCode; 80 switch( event.keyCode ) { 81 case keyCode.PAGE_UP: 82 suppressKeyPress = true; 83 this._move( "previousPage", event ); 84 break; 85 case keyCode.PAGE_DOWN: 86 suppressKeyPress = true; 87 this._move( "nextPage", event ); 88 break; 89 case keyCode.UP: 90 suppressKeyPress = true; 91 this._keyEvent( "previous", event ); 92 break; 93 case keyCode.DOWN: 94 suppressKeyPress = true; 95 this._keyEvent( "next", event ); 96 break; 97 case keyCode.ENTER: 98 case keyCode.NUMPAD_ENTER: 99 // when menu is open and has focus 100 if ( this.menu.active ) { 101 // #6055 - Opera still allows the keypress to occur 102 // which causes forms to submit 103 suppressKeyPress = true; 104 event.preventDefault(); 105 this.menu.select( event ); 106 } 107 break; 108 case keyCode.TAB: 109 if ( this.menu.active ) { 110 this.menu.select( event ); 111 } 112 break; 113 case keyCode.ESCAPE: 114 if ( this.menu.element.is( ":visible" ) ) { 115 this._value( this.term ); 116 this.close( event ); 117 // Different browsers have different default behavior for escape 118 // Single press can mean undo or clear 119 // Double press in IE means clear the whole form 120 event.preventDefault(); 121 } 122 break; 123 default: 124 suppressKeyPressRepeat = true; 125 // search timeout should be triggered before the input value is changed 126 this._searchTimeout( event ); 127 break; 128 } 129 }, 130 keypress: function( event ) { 131 if ( suppressKeyPress ) { 132 suppressKeyPress = false; 133 event.preventDefault(); 134 return; 135 } 136 if ( suppressKeyPressRepeat ) { 137 return; 138 } 139 140 // replicate some key handlers to allow them to repeat in Firefox and Opera 141 var keyCode = $.ui.keyCode; 142 switch( event.keyCode ) { 143 case keyCode.PAGE_UP: 144 this._move( "previousPage", event ); 145 break; 146 case keyCode.PAGE_DOWN: 147 this._move( "nextPage", event ); 148 break; 149 case keyCode.UP: 150 this._keyEvent( "previous", event ); 151 break; 152 case keyCode.DOWN: 153 this._keyEvent( "next", event ); 154 break; 155 } 156 }, 157 input: function( event ) { 158 if ( suppressInput ) { 159 suppressInput = false; 160 event.preventDefault(); 161 return; 162 } 163 this._searchTimeout( event ); 164 }, 165 focus: function() { 166 this.selectedItem = null; 167 this.previous = this._value(); 168 }, 169 blur: function( event ) { 170 if ( this.cancelBlur ) { 171 delete this.cancelBlur; 172 return; 173 } 174 175 clearTimeout( this.searching ); 176 this.close( event ); 177 this._change( event ); 178 } 179 }); 180 181 this._initSource(); 182 this.menu = $( "<ul>" ) 183 .addClass( "ui-autocomplete" ) 184 .appendTo( this.document.find( this.options.appendTo || "body" )[ 0 ] ) 185 .menu({ 186 // custom key handling for now 187 input: $(), 188 // disable ARIA support, the live region takes care of that 189 role: null 190 }) 191 .zIndex( this.element.zIndex() + 1 ) 192 .hide() 193 .data( "menu" ); 194 195 this._on( this.menu.element, { 196 mousedown: function( event ) { 197 // prevent moving focus out of the text field 198 event.preventDefault(); 199 200 // IE doesn't prevent moving focus even with event.preventDefault() 201 // so we set a flag to know when we should ignore the blur event 202 this.cancelBlur = true; 203 this._delay(function() { 204 delete this.cancelBlur; 205 }); 206 207 // clicking on the scrollbar causes focus to shift to the body 208 // but we can't detect a mouseup or a click immediately afterward 209 // so we have to track the next mousedown and close the menu if 210 // the user clicks somewhere outside of the autocomplete 211 var menuElement = this.menu.element[ 0 ]; 212 if ( !$( event.target ).closest( ".ui-menu-item" ).length ) { 213 this._delay(function() { 214 var that = this; 215 this.document.one( "mousedown", function( event ) { 216 if ( event.target !== that.element[ 0 ] && 217 event.target !== menuElement && 218 !$.contains( menuElement, event.target ) ) { 219 that.close(); 220 } 221 }); 222 }); 223 } 224 }, 225 menufocus: function( event, ui ) { 226 // #7024 - Prevent accidental activation of menu items in Firefox 227 if ( this.isNewMenu ) { 228 this.isNewMenu = false; 229 if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) { 230 this.menu.blur(); 231 232 this.document.one( "mousemove", function() { 233 $( event.target ).trigger( event.originalEvent ); 234 }); 235 236 return; 237 } 238 } 239 240 // back compat for _renderItem using item.autocomplete, via #7810 241 // TODO remove the fallback, see #8156 242 var item = ui.item.data( "ui-autocomplete-item" ) || ui.item.data( "item.autocomplete" ); 243 if ( false !== this._trigger( "focus", event, { item: item } ) ) { 244 // use value to match what will end up in the input, if it was a key event 245 if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) { 246 this._value( item.value ); 247 } 248 } else { 249 // Normally the input is populated with the item's value as the 250 // menu is navigated, causing screen readers to notice a change and 251 // announce the item. Since the focus event was canceled, this doesn't 252 // happen, so we update the live region so that screen readers can 253 // still notice the change and announce it. 254 this.liveRegion.text( item.value ); 255 } 256 }, 257 menuselect: function( event, ui ) { 258 // back compat for _renderItem using item.autocomplete, via #7810 259 // TODO remove the fallback, see #8156 260 var item = ui.item.data( "ui-autocomplete-item" ) || ui.item.data( "item.autocomplete" ), 261 previous = this.previous; 262 263 // only trigger when focus was lost (click on menu) 264 if ( this.element[0] !== this.document[0].activeElement ) { 265 this.element.focus(); 266 this.previous = previous; 267 // #6109 - IE triggers two focus events and the second 268 // is asynchronous, so we need to reset the previous 269 // term synchronously and asynchronously :-( 270 this._delay(function() { 271 this.previous = previous; 272 this.selectedItem = item; 273 }); 274 } 275 276 if ( false !== this._trigger( "select", event, { item: item } ) ) { 277 this._value( item.value ); 278 } 279 // reset the term after the select event 280 // this allows custom select handling to work properly 281 this.term = this._value(); 282 283 this.close( event ); 284 this.selectedItem = item; 285 } 286 }); 287 288 this.liveRegion = $( "<span>", { 289 role: "status", 290 "aria-live": "polite" 291 }) 292 .addClass( "ui-helper-hidden-accessible" ) 293 .insertAfter( this.element ); 294 295 if ( $.fn.bgiframe ) { 296 this.menu.element.bgiframe(); 297 } 298 299 // turning off autocomplete prevents the browser from remembering the 300 // value when navigating through history, so we re-enable autocomplete 301 // if the page is unloaded before the widget is destroyed. #7790 302 this._on( this.window, { 303 beforeunload: function() { 304 this.element.removeAttr( "autocomplete" ); 305 } 306 }); 307 }, 308 309 _destroy: function() { 310 clearTimeout( this.searching ); 311 this.element 312 .removeClass( "ui-autocomplete-input" ) 313 .removeAttr( "autocomplete" ); 314 this.menu.element.remove(); 315 this.liveRegion.remove(); 316 }, 317 318 _setOption: function( key, value ) { 319 this._super( key, value ); 320 if ( key === "source" ) { 321 this._initSource(); 322 } 323 if ( key === "appendTo" ) { 324 this.menu.element.appendTo( this.document.find( value || "body" )[0] ); 325 } 326 if ( key === "disabled" && value && this.xhr ) { 327 this.xhr.abort(); 328 } 329 }, 330 331 _isMultiLine: function() { 332 // Textareas are always multi-line 333 if ( this.element.is( "textarea" ) ) { 334 return true; 335 } 336 // Inputs are always single-line, even if inside a contentEditable element 337 // IE also treats inputs as contentEditable 338 if ( this.element.is( "input" ) ) { 339 return false; 340 } 341 // All other element types are determined by whether or not they're contentEditable 342 return this.element.prop( "isContentEditable" ); 343 }, 344 345 _initSource: function() { 346 var array, url, 347 that = this; 348 if ( $.isArray(this.options.source) ) { 349 array = this.options.source; 350 this.source = function( request, response ) { 351 response( $.ui.autocomplete.filter( array, request.term ) ); 352 }; 353 } else if ( typeof this.options.source === "string" ) { 354 url = this.options.source; 355 this.source = function( request, response ) { 356 if ( that.xhr ) { 357 that.xhr.abort(); 358 } 359 that.xhr = $.ajax({ 360 url: url, 361 data: request, 362 dataType: "json", 363 success: function( data ) { 364 response( data ); 365 }, 366 error: function() { 367 response( [] ); 368 } 369 }); 370 }; 371 } else { 372 this.source = this.options.source; 373 } 374 }, 375 376 _searchTimeout: function( event ) { 377 clearTimeout( this.searching ); 378 this.searching = this._delay(function() { 379 // only search if the value has changed 380 if ( this.term !== this._value() ) { 381 this.selectedItem = null; 382 this.search( null, event ); 383 } 384 }, this.options.delay ); 385 }, 386 387 search: function( value, event ) { 388 value = value != null ? value : this._value(); 389 390 // always save the actual value, not the one passed as an argument 391 this.term = this._value(); 392 393 if ( value.length < this.options.minLength ) { 394 return this.close( event ); 395 } 396 397 if ( this._trigger( "search", event ) === false ) { 398 return; 399 } 400 401 return this._search( value ); 402 }, 403 404 _search: function( value ) { 405 this.pending++; 406 this.element.addClass( "ui-autocomplete-loading" ); 407 this.cancelSearch = false; 408 409 this.source( { term: value }, this._response() ); 410 }, 411 412 _response: function() { 413 var that = this, 414 index = ++requestIndex; 415 416 return function( content ) { 417 if ( index === requestIndex ) { 418 that.__response( content ); 419 } 420 421 that.pending--; 422 if ( !that.pending ) { 423 that.element.removeClass( "ui-autocomplete-loading" ); 424 } 425 }; 426 }, 427 428 __response: function( content ) { 429 if ( content ) { 430 content = this._normalize( content ); 431 } 432 this._trigger( "response", null, { content: content } ); 433 if ( !this.options.disabled && content && content.length && !this.cancelSearch ) { 434 this._suggest( content ); 435 this._trigger( "open" ); 436 } else { 437 // use ._close() instead of .close() so we don't cancel future searches 438 this._close(); 439 } 440 }, 441 442 close: function( event ) { 443 this.cancelSearch = true; 444 this._close( event ); 445 }, 446 447 _close: function( event ) { 448 if ( this.menu.element.is( ":visible" ) ) { 449 this.menu.element.hide(); 450 this.menu.blur(); 451 this.isNewMenu = true; 452 this._trigger( "close", event ); 453 } 454 }, 455 456 _change: function( event ) { 457 if ( this.previous !== this._value() ) { 458 this._trigger( "change", event, { item: this.selectedItem } ); 459 } 460 }, 461 462 _normalize: function( items ) { 463 // assume all items have the right format when the first item is complete 464 if ( items.length && items[0].label && items[0].value ) { 465 return items; 466 } 467 return $.map( items, function( item ) { 468 if ( typeof item === "string" ) { 469 return { 470 label: item, 471 value: item 472 }; 473 } 474 return $.extend({ 475 label: item.label || item.value, 476 value: item.value || item.label 477 }, item ); 478 }); 479 }, 480 481 _suggest: function( items ) { 482 var ul = this.menu.element 483 .empty() 484 .zIndex( this.element.zIndex() + 1 ); 485 this._renderMenu( ul, items ); 486 this.menu.refresh(); 487 488 // size and position menu 489 ul.show(); 490 this._resizeMenu(); 491 ul.position( $.extend({ 492 of: this.element 493 }, this.options.position )); 494 495 if ( this.options.autoFocus ) { 496 this.menu.next(); 497 } 498 }, 499 500 _resizeMenu: function() { 501 var ul = this.menu.element; 502 ul.outerWidth( Math.max( 503 // Firefox wraps long text (possibly a rounding bug) 504 // so we add 1px to avoid the wrapping (#7513) 505 ul.width( "" ).outerWidth() + 1, 506 this.element.outerWidth() 507 ) ); 508 }, 509 510 _renderMenu: function( ul, items ) { 511 var that = this; 512 $.each( items, function( index, item ) { 513 that._renderItemData( ul, item ); 514 }); 515 }, 516 517 _renderItemData: function( ul, item ) { 518 return this._renderItem( ul, item ).data( "ui-autocomplete-item", item ); 519 }, 520 521 _renderItem: function( ul, item ) { 522 return $( "<li>" ) 523 .append( $( "<a>" ).text( item.label ) ) 524 .appendTo( ul ); 525 }, 526 527 _move: function( direction, event ) { 528 if ( !this.menu.element.is( ":visible" ) ) { 529 this.search( null, event ); 530 return; 531 } 532 if ( this.menu.isFirstItem() && /^previous/.test( direction ) || 533 this.menu.isLastItem() && /^next/.test( direction ) ) { 534 this._value( this.term ); 535 this.menu.blur(); 536 return; 537 } 538 this.menu[ direction ]( event ); 539 }, 540 541 widget: function() { 542 return this.menu.element; 543 }, 544 545 _value: function() { 546 return this.valueMethod.apply( this.element, arguments ); 547 }, 548 549 _keyEvent: function( keyEvent, event ) { 550 if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { 551 this._move( keyEvent, event ); 552 553 // prevents moving cursor to beginning/end of the text field in some browsers 554 event.preventDefault(); 555 } 556 } 557 }); 558 559 $.extend( $.ui.autocomplete, { 560 escapeRegex: function( value ) { 561 return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&"); 562 }, 563 filter: function(array, term) { 564 var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" ); 565 return $.grep( array, function(value) { 566 return matcher.test( value.label || value.value || value ); 567 }); 568 } 569 }); 570 571 572 // live region extension, adding a `messages` option 573 // NOTE: This is an experimental API. We are still investigating 574 // a full solution for string manipulation and internationalization. 575 $.widget( "ui.autocomplete", $.ui.autocomplete, { 576 options: { 577 messages: { 578 noResults: "No search results.", 579 results: function( amount ) { 580 return amount + ( amount > 1 ? " results are" : " result is" ) + 581 " available, use up and down arrow keys to navigate."; 582 } 583 } 584 }, 585 586 __response: function( content ) { 587 var message; 588 this._superApply( arguments ); 589 if ( this.options.disabled || this.cancelSearch ) { 590 return; 591 } 592 if ( content && content.length ) { 593 message = this.options.messages.results( content.length ); 594 } else { 595 message = this.options.messages.noResults; 596 } 597 this.liveRegion.text( message ); 598 } 599 }); 600 601 602 }( jQuery ));
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |