[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/js/jquery/ui/ -> autocomplete.js (source)

   1  /*!
   2   * jQuery UI Autocomplete 1.13.1
   3   * http://jqueryui.com
   4   *
   5   * Copyright jQuery Foundation and other contributors
   6   * Released under the MIT license.
   7   * http://jquery.org/license
   8   */
   9  
  10  //>>label: Autocomplete
  11  //>>group: Widgets
  12  //>>description: Lists suggested words as the user is typing.
  13  //>>docs: http://api.jqueryui.com/autocomplete/
  14  //>>demos: http://jqueryui.com/autocomplete/
  15  //>>css.structure: ../../themes/base/core.css
  16  //>>css.structure: ../../themes/base/autocomplete.css
  17  //>>css.theme: ../../themes/base/theme.css
  18  
  19  ( function( factory ) {
  20      "use strict";
  21  
  22      if ( typeof define === "function" && define.amd ) {
  23  
  24          // AMD. Register as an anonymous module.
  25          define( [
  26              "jquery",
  27              "./menu",
  28              "./core"
  29          ], factory );
  30      } else {
  31  
  32          // Browser globals
  33          factory( jQuery );
  34      }
  35  } )( function( $ ) {
  36  "use strict";
  37  
  38  $.widget( "ui.autocomplete", {
  39      version: "1.13.1",
  40      defaultElement: "<input>",
  41      options: {
  42          appendTo: null,
  43          autoFocus: false,
  44          delay: 300,
  45          minLength: 1,
  46          position: {
  47              my: "left top",
  48              at: "left bottom",
  49              collision: "none"
  50          },
  51          source: null,
  52  
  53          // Callbacks
  54          change: null,
  55          close: null,
  56          focus: null,
  57          open: null,
  58          response: null,
  59          search: null,
  60          select: null
  61      },
  62  
  63      requestIndex: 0,
  64      pending: 0,
  65      liveRegionTimer: null,
  66  
  67      _create: function() {
  68  
  69          // Some browsers only repeat keydown events, not keypress events,
  70          // so we use the suppressKeyPress flag to determine if we've already
  71          // handled the keydown event. #7269
  72          // Unfortunately the code for & in keypress is the same as the up arrow,
  73          // so we use the suppressKeyPressRepeat flag to avoid handling keypress
  74          // events when we know the keydown event was used to modify the
  75          // search term. #7799
  76          var suppressKeyPress, suppressKeyPressRepeat, suppressInput,
  77              nodeName = this.element[ 0 ].nodeName.toLowerCase(),
  78              isTextarea = nodeName === "textarea",
  79              isInput = nodeName === "input";
  80  
  81          // Textareas are always multi-line
  82          // Inputs are always single-line, even if inside a contentEditable element
  83          // IE also treats inputs as contentEditable
  84          // All other element types are determined by whether or not they're contentEditable
  85          this.isMultiLine = isTextarea || !isInput && this._isContentEditable( this.element );
  86  
  87          this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ];
  88          this.isNewMenu = true;
  89  
  90          this._addClass( "ui-autocomplete-input" );
  91          this.element.attr( "autocomplete", "off" );
  92  
  93          this._on( this.element, {
  94              keydown: function( event ) {
  95                  if ( this.element.prop( "readOnly" ) ) {
  96                      suppressKeyPress = true;
  97                      suppressInput = true;
  98                      suppressKeyPressRepeat = true;
  99                      return;
 100                  }
 101  
 102                  suppressKeyPress = false;
 103                  suppressInput = false;
 104                  suppressKeyPressRepeat = false;
 105                  var keyCode = $.ui.keyCode;
 106                  switch ( event.keyCode ) {
 107                      case keyCode.PAGE_UP:
 108                          suppressKeyPress = true;
 109                          this._move( "previousPage", event );
 110                          break;
 111                      case keyCode.PAGE_DOWN:
 112                          suppressKeyPress = true;
 113                          this._move( "nextPage", event );
 114                          break;
 115                      case keyCode.UP:
 116                          suppressKeyPress = true;
 117                          this._keyEvent( "previous", event );
 118                          break;
 119                      case keyCode.DOWN:
 120                          suppressKeyPress = true;
 121                          this._keyEvent( "next", event );
 122                          break;
 123                      case keyCode.ENTER:
 124  
 125                          // when menu is open and has focus
 126                          if ( this.menu.active ) {
 127  
 128                              // #6055 - Opera still allows the keypress to occur
 129                              // which causes forms to submit
 130                              suppressKeyPress = true;
 131                              event.preventDefault();
 132                              this.menu.select( event );
 133                          }
 134                          break;
 135                      case keyCode.TAB:
 136                          if ( this.menu.active ) {
 137                              this.menu.select( event );
 138                          }
 139                          break;
 140                      case keyCode.ESCAPE:
 141                          if ( this.menu.element.is( ":visible" ) ) {
 142                              if ( !this.isMultiLine ) {
 143                                  this._value( this.term );
 144                              }
 145                              this.close( event );
 146  
 147                              // Different browsers have different default behavior for escape
 148                              // Single press can mean undo or clear
 149                              // Double press in IE means clear the whole form
 150                              event.preventDefault();
 151                          }
 152                          break;
 153                      default:
 154                          suppressKeyPressRepeat = true;
 155  
 156                          // search timeout should be triggered before the input value is changed
 157                          this._searchTimeout( event );
 158                          break;
 159                  }
 160              },
 161              keypress: function( event ) {
 162                  if ( suppressKeyPress ) {
 163                      suppressKeyPress = false;
 164                      if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
 165                          event.preventDefault();
 166                      }
 167                      return;
 168                  }
 169                  if ( suppressKeyPressRepeat ) {
 170                      return;
 171                  }
 172  
 173                  // Replicate some key handlers to allow them to repeat in Firefox and Opera
 174                  var keyCode = $.ui.keyCode;
 175                  switch ( event.keyCode ) {
 176                      case keyCode.PAGE_UP:
 177                          this._move( "previousPage", event );
 178                          break;
 179                      case keyCode.PAGE_DOWN:
 180                          this._move( "nextPage", event );
 181                          break;
 182                      case keyCode.UP:
 183                          this._keyEvent( "previous", event );
 184                          break;
 185                      case keyCode.DOWN:
 186                          this._keyEvent( "next", event );
 187                          break;
 188                  }
 189              },
 190              input: function( event ) {
 191                  if ( suppressInput ) {
 192                      suppressInput = false;
 193                      event.preventDefault();
 194                      return;
 195                  }
 196                  this._searchTimeout( event );
 197              },
 198              focus: function() {
 199                  this.selectedItem = null;
 200                  this.previous = this._value();
 201              },
 202              blur: function( event ) {
 203                  clearTimeout( this.searching );
 204                  this.close( event );
 205                  this._change( event );
 206              }
 207          } );
 208  
 209          this._initSource();
 210          this.menu = $( "<ul>" )
 211              .appendTo( this._appendTo() )
 212              .menu( {
 213  
 214                  // disable ARIA support, the live region takes care of that
 215                  role: null
 216              } )
 217              .hide()
 218  
 219              // Support: IE 11 only, Edge <= 14
 220              // For other browsers, we preventDefault() on the mousedown event
 221              // to keep the dropdown from taking focus from the input. This doesn't
 222              // work for IE/Edge, causing problems with selection and scrolling (#9638)
 223              // Happily, IE and Edge support an "unselectable" attribute that
 224              // prevents an element from receiving focus, exactly what we want here.
 225              .attr( {
 226                  "unselectable": "on"
 227              } )
 228              .menu( "instance" );
 229  
 230          this._addClass( this.menu.element, "ui-autocomplete", "ui-front" );
 231          this._on( this.menu.element, {
 232              mousedown: function( event ) {
 233  
 234                  // Prevent moving focus out of the text field
 235                  event.preventDefault();
 236              },
 237              menufocus: function( event, ui ) {
 238                  var label, item;
 239  
 240                  // support: Firefox
 241                  // Prevent accidental activation of menu items in Firefox (#7024 #9118)
 242                  if ( this.isNewMenu ) {
 243                      this.isNewMenu = false;
 244                      if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) {
 245                          this.menu.blur();
 246  
 247                          this.document.one( "mousemove", function() {
 248                              $( event.target ).trigger( event.originalEvent );
 249                          } );
 250  
 251                          return;
 252                      }
 253                  }
 254  
 255                  item = ui.item.data( "ui-autocomplete-item" );
 256                  if ( false !== this._trigger( "focus", event, { item: item } ) ) {
 257  
 258                      // use value to match what will end up in the input, if it was a key event
 259                      if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) {
 260                          this._value( item.value );
 261                      }
 262                  }
 263  
 264                  // Announce the value in the liveRegion
 265                  label = ui.item.attr( "aria-label" ) || item.value;
 266                  if ( label && String.prototype.trim.call( label ).length ) {
 267                      clearTimeout( this.liveRegionTimer );
 268                      this.liveRegionTimer = this._delay( function() {
 269                          this.liveRegion.html( $( "<div>" ).text( label ) );
 270                      }, 100 );
 271                  }
 272              },
 273              menuselect: function( event, ui ) {
 274                  var item = ui.item.data( "ui-autocomplete-item" ),
 275                      previous = this.previous;
 276  
 277                  // Only trigger when focus was lost (click on menu)
 278                  if ( this.element[ 0 ] !== $.ui.safeActiveElement( this.document[ 0 ] ) ) {
 279                      this.element.trigger( "focus" );
 280                      this.previous = previous;
 281  
 282                      // #6109 - IE triggers two focus events and the second
 283                      // is asynchronous, so we need to reset the previous
 284                      // term synchronously and asynchronously :-(
 285                      this._delay( function() {
 286                          this.previous = previous;
 287                          this.selectedItem = item;
 288                      } );
 289                  }
 290  
 291                  if ( false !== this._trigger( "select", event, { item: item } ) ) {
 292                      this._value( item.value );
 293                  }
 294  
 295                  // reset the term after the select event
 296                  // this allows custom select handling to work properly
 297                  this.term = this._value();
 298  
 299                  this.close( event );
 300                  this.selectedItem = item;
 301              }
 302          } );
 303  
 304          this.liveRegion = $( "<div>", {
 305              role: "status",
 306              "aria-live": "assertive",
 307              "aria-relevant": "additions"
 308          } )
 309              .appendTo( this.document[ 0 ].body );
 310  
 311          this._addClass( this.liveRegion, null, "ui-helper-hidden-accessible" );
 312  
 313          // Turning off autocomplete prevents the browser from remembering the
 314          // value when navigating through history, so we re-enable autocomplete
 315          // if the page is unloaded before the widget is destroyed. #7790
 316          this._on( this.window, {
 317              beforeunload: function() {
 318                  this.element.removeAttr( "autocomplete" );
 319              }
 320          } );
 321      },
 322  
 323      _destroy: function() {
 324          clearTimeout( this.searching );
 325          this.element.removeAttr( "autocomplete" );
 326          this.menu.element.remove();
 327          this.liveRegion.remove();
 328      },
 329  
 330      _setOption: function( key, value ) {
 331          this._super( key, value );
 332          if ( key === "source" ) {
 333              this._initSource();
 334          }
 335          if ( key === "appendTo" ) {
 336              this.menu.element.appendTo( this._appendTo() );
 337          }
 338          if ( key === "disabled" && value && this.xhr ) {
 339              this.xhr.abort();
 340          }
 341      },
 342  
 343      _isEventTargetInWidget: function( event ) {
 344          var menuElement = this.menu.element[ 0 ];
 345  
 346          return event.target === this.element[ 0 ] ||
 347              event.target === menuElement ||
 348              $.contains( menuElement, event.target );
 349      },
 350  
 351      _closeOnClickOutside: function( event ) {
 352          if ( !this._isEventTargetInWidget( event ) ) {
 353              this.close();
 354          }
 355      },
 356  
 357      _appendTo: function() {
 358          var element = this.options.appendTo;
 359  
 360          if ( element ) {
 361              element = element.jquery || element.nodeType ?
 362                  $( element ) :
 363                  this.document.find( element ).eq( 0 );
 364          }
 365  
 366          if ( !element || !element[ 0 ] ) {
 367              element = this.element.closest( ".ui-front, dialog" );
 368          }
 369  
 370          if ( !element.length ) {
 371              element = this.document[ 0 ].body;
 372          }
 373  
 374          return element;
 375      },
 376  
 377      _initSource: function() {
 378          var array, url,
 379              that = this;
 380          if ( Array.isArray( this.options.source ) ) {
 381              array = this.options.source;
 382              this.source = function( request, response ) {
 383                  response( $.ui.autocomplete.filter( array, request.term ) );
 384              };
 385          } else if ( typeof this.options.source === "string" ) {
 386              url = this.options.source;
 387              this.source = function( request, response ) {
 388                  if ( that.xhr ) {
 389                      that.xhr.abort();
 390                  }
 391                  that.xhr = $.ajax( {
 392                      url: url,
 393                      data: request,
 394                      dataType: "json",
 395                      success: function( data ) {
 396                          response( data );
 397                      },
 398                      error: function() {
 399                          response( [] );
 400                      }
 401                  } );
 402              };
 403          } else {
 404              this.source = this.options.source;
 405          }
 406      },
 407  
 408      _searchTimeout: function( event ) {
 409          clearTimeout( this.searching );
 410          this.searching = this._delay( function() {
 411  
 412              // Search if the value has changed, or if the user retypes the same value (see #7434)
 413              var equalValues = this.term === this._value(),
 414                  menuVisible = this.menu.element.is( ":visible" ),
 415                  modifierKey = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
 416  
 417              if ( !equalValues || ( equalValues && !menuVisible && !modifierKey ) ) {
 418                  this.selectedItem = null;
 419                  this.search( null, event );
 420              }
 421          }, this.options.delay );
 422      },
 423  
 424      search: function( value, event ) {
 425          value = value != null ? value : this._value();
 426  
 427          // Always save the actual value, not the one passed as an argument
 428          this.term = this._value();
 429  
 430          if ( value.length < this.options.minLength ) {
 431              return this.close( event );
 432          }
 433  
 434          if ( this._trigger( "search", event ) === false ) {
 435              return;
 436          }
 437  
 438          return this._search( value );
 439      },
 440  
 441      _search: function( value ) {
 442          this.pending++;
 443          this._addClass( "ui-autocomplete-loading" );
 444          this.cancelSearch = false;
 445  
 446          this.source( { term: value }, this._response() );
 447      },
 448  
 449      _response: function() {
 450          var index = ++this.requestIndex;
 451  
 452          return function( content ) {
 453              if ( index === this.requestIndex ) {
 454                  this.__response( content );
 455              }
 456  
 457              this.pending--;
 458              if ( !this.pending ) {
 459                  this._removeClass( "ui-autocomplete-loading" );
 460              }
 461          }.bind( this );
 462      },
 463  
 464      __response: function( content ) {
 465          if ( content ) {
 466              content = this._normalize( content );
 467          }
 468          this._trigger( "response", null, { content: content } );
 469          if ( !this.options.disabled && content && content.length && !this.cancelSearch ) {
 470              this._suggest( content );
 471              this._trigger( "open" );
 472          } else {
 473  
 474              // use ._close() instead of .close() so we don't cancel future searches
 475              this._close();
 476          }
 477      },
 478  
 479      close: function( event ) {
 480          this.cancelSearch = true;
 481          this._close( event );
 482      },
 483  
 484      _close: function( event ) {
 485  
 486          // Remove the handler that closes the menu on outside clicks
 487          this._off( this.document, "mousedown" );
 488  
 489          if ( this.menu.element.is( ":visible" ) ) {
 490              this.menu.element.hide();
 491              this.menu.blur();
 492              this.isNewMenu = true;
 493              this._trigger( "close", event );
 494          }
 495      },
 496  
 497      _change: function( event ) {
 498          if ( this.previous !== this._value() ) {
 499              this._trigger( "change", event, { item: this.selectedItem } );
 500          }
 501      },
 502  
 503      _normalize: function( items ) {
 504  
 505          // assume all items have the right format when the first item is complete
 506          if ( items.length && items[ 0 ].label && items[ 0 ].value ) {
 507              return items;
 508          }
 509          return $.map( items, function( item ) {
 510              if ( typeof item === "string" ) {
 511                  return {
 512                      label: item,
 513                      value: item
 514                  };
 515              }
 516              return $.extend( {}, item, {
 517                  label: item.label || item.value,
 518                  value: item.value || item.label
 519              } );
 520          } );
 521      },
 522  
 523      _suggest: function( items ) {
 524          var ul = this.menu.element.empty();
 525          this._renderMenu( ul, items );
 526          this.isNewMenu = true;
 527          this.menu.refresh();
 528  
 529          // Size and position menu
 530          ul.show();
 531          this._resizeMenu();
 532          ul.position( $.extend( {
 533              of: this.element
 534          }, this.options.position ) );
 535  
 536          if ( this.options.autoFocus ) {
 537              this.menu.next();
 538          }
 539  
 540          // Listen for interactions outside of the widget (#6642)
 541          this._on( this.document, {
 542              mousedown: "_closeOnClickOutside"
 543          } );
 544      },
 545  
 546      _resizeMenu: function() {
 547          var ul = this.menu.element;
 548          ul.outerWidth( Math.max(
 549  
 550              // Firefox wraps long text (possibly a rounding bug)
 551              // so we add 1px to avoid the wrapping (#7513)
 552              ul.width( "" ).outerWidth() + 1,
 553              this.element.outerWidth()
 554          ) );
 555      },
 556  
 557      _renderMenu: function( ul, items ) {
 558          var that = this;
 559          $.each( items, function( index, item ) {
 560              that._renderItemData( ul, item );
 561          } );
 562      },
 563  
 564      _renderItemData: function( ul, item ) {
 565          return this._renderItem( ul, item ).data( "ui-autocomplete-item", item );
 566      },
 567  
 568      _renderItem: function( ul, item ) {
 569          return $( "<li>" )
 570              .append( $( "<div>" ).text( item.label ) )
 571              .appendTo( ul );
 572      },
 573  
 574      _move: function( direction, event ) {
 575          if ( !this.menu.element.is( ":visible" ) ) {
 576              this.search( null, event );
 577              return;
 578          }
 579          if ( this.menu.isFirstItem() && /^previous/.test( direction ) ||
 580              this.menu.isLastItem() && /^next/.test( direction ) ) {
 581  
 582              if ( !this.isMultiLine ) {
 583                  this._value( this.term );
 584              }
 585  
 586              this.menu.blur();
 587              return;
 588          }
 589          this.menu[ direction ]( event );
 590      },
 591  
 592      widget: function() {
 593          return this.menu.element;
 594      },
 595  
 596      _value: function() {
 597          return this.valueMethod.apply( this.element, arguments );
 598      },
 599  
 600      _keyEvent: function( keyEvent, event ) {
 601          if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
 602              this._move( keyEvent, event );
 603  
 604              // Prevents moving cursor to beginning/end of the text field in some browsers
 605              event.preventDefault();
 606          }
 607      },
 608  
 609      // Support: Chrome <=50
 610      // We should be able to just use this.element.prop( "isContentEditable" )
 611      // but hidden elements always report false in Chrome.
 612      // https://code.google.com/p/chromium/issues/detail?id=313082
 613      _isContentEditable: function( element ) {
 614          if ( !element.length ) {
 615              return false;
 616          }
 617  
 618          var editable = element.prop( "contentEditable" );
 619  
 620          if ( editable === "inherit" ) {
 621              return this._isContentEditable( element.parent() );
 622          }
 623  
 624          return editable === "true";
 625      }
 626  } );
 627  
 628  $.extend( $.ui.autocomplete, {
 629      escapeRegex: function( value ) {
 630          return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" );
 631      },
 632      filter: function( array, term ) {
 633          var matcher = new RegExp( $.ui.autocomplete.escapeRegex( term ), "i" );
 634          return $.grep( array, function( value ) {
 635              return matcher.test( value.label || value.value || value );
 636          } );
 637      }
 638  } );
 639  
 640  // Live region extension, adding a `messages` option
 641  // NOTE: This is an experimental API. We are still investigating
 642  // a full solution for string manipulation and internationalization.
 643  $.widget( "ui.autocomplete", $.ui.autocomplete, {
 644      options: {
 645          messages: {
 646              noResults: "No search results.",
 647              results: function( amount ) {
 648                  return amount + ( amount > 1 ? " results are" : " result is" ) +
 649                      " available, use up and down arrow keys to navigate.";
 650              }
 651          }
 652      },
 653  
 654      __response: function( content ) {
 655          var message;
 656          this._superApply( arguments );
 657          if ( this.options.disabled || this.cancelSearch ) {
 658              return;
 659          }
 660          if ( content && content.length ) {
 661              message = this.options.messages.results( content.length );
 662          } else {
 663              message = this.options.messages.noResults;
 664          }
 665          clearTimeout( this.liveRegionTimer );
 666          this.liveRegionTimer = this._delay( function() {
 667              this.liveRegion.html( $( "<div>" ).text( message ) );
 668          }, 100 );
 669      }
 670  } );
 671  
 672  return $.ui.autocomplete;
 673  
 674  } );


Generated: Thu Mar 28 01:00:02 2024 Cross-referenced by PHPXref 0.7.1