[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

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

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


Generated: Sat Sep 18 01:00:04 2021 Cross-referenced by PHPXref 0.7.1