[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

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

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


Generated : Thu Nov 21 08:20:01 2024 Cross-referenced by PHPXref