[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

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

   1  /*!
   2   * jQuery UI Selectmenu 1.12.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: Selectmenu
  11  //>>group: Widgets
  12  // jscs:disable maximumLineLength
  13  //>>description: Duplicates and extends the functionality of a native HTML select element, allowing it to be customizable in behavior and appearance far beyond the limitations of a native select.
  14  // jscs:enable maximumLineLength
  15  //>>docs: http://api.jqueryui.com/selectmenu/
  16  //>>demos: http://jqueryui.com/selectmenu/
  17  //>>css.structure: ../../themes/base/core.css
  18  //>>css.structure: ../../themes/base/selectmenu.css, ../../themes/base/button.css
  19  //>>css.theme: ../../themes/base/theme.css
  20  
  21  ( function( factory ) {
  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  
  37  return $.widget( "ui.selectmenu", [ $.ui.formResetMixin, {
  38      version: "1.12.1",
  39      defaultElement: "<select>",
  40      options: {
  41          appendTo: null,
  42          classes: {
  43              "ui-selectmenu-button-open": "ui-corner-top",
  44              "ui-selectmenu-button-closed": "ui-corner-all"
  45          },
  46          disabled: null,
  47          icons: {
  48              button: "ui-icon-triangle-1-s"
  49          },
  50          position: {
  51              my: "left top",
  52              at: "left bottom",
  53              collision: "none"
  54          },
  55          width: false,
  56  
  57          // Callbacks
  58          change: null,
  59          close: null,
  60          focus: null,
  61          open: null,
  62          select: null
  63      },
  64  
  65      _create: function() {
  66          var selectmenuId = this.element.uniqueId().attr( "id" );
  67          this.ids = {
  68              element: selectmenuId,
  69              button: selectmenuId + "-button",
  70              menu: selectmenuId + "-menu"
  71          };
  72  
  73          this._drawButton();
  74          this._drawMenu();
  75          this._bindFormResetHandler();
  76  
  77          this._rendered = false;
  78          this.menuItems = $();
  79      },
  80  
  81      _drawButton: function() {
  82          var icon,
  83              that = this,
  84              item = this._parseOption(
  85                  this.element.find( "option:selected" ),
  86                  this.element[ 0 ].selectedIndex
  87              );
  88  
  89          // Associate existing label with the new button
  90          this.labels = this.element.labels().attr( "for", this.ids.button );
  91          this._on( this.labels, {
  92              click: function( event ) {
  93                  this.button.focus();
  94                  event.preventDefault();
  95              }
  96          } );
  97  
  98          // Hide original select element
  99          this.element.hide();
 100  
 101          // Create button
 102          this.button = $( "<span>", {
 103              tabindex: this.options.disabled ? -1 : 0,
 104              id: this.ids.button,
 105              role: "combobox",
 106              "aria-expanded": "false",
 107              "aria-autocomplete": "list",
 108              "aria-owns": this.ids.menu,
 109              "aria-haspopup": "true",
 110              title: this.element.attr( "title" )
 111          } )
 112              .insertAfter( this.element );
 113  
 114          this._addClass( this.button, "ui-selectmenu-button ui-selectmenu-button-closed",
 115              "ui-button ui-widget" );
 116  
 117          icon = $( "<span>" ).appendTo( this.button );
 118          this._addClass( icon, "ui-selectmenu-icon", "ui-icon " + this.options.icons.button );
 119          this.buttonItem = this._renderButtonItem( item )
 120              .appendTo( this.button );
 121  
 122          if ( this.options.width !== false ) {
 123              this._resizeButton();
 124          }
 125  
 126          this._on( this.button, this._buttonEvents );
 127          this.button.one( "focusin", function() {
 128  
 129              // Delay rendering the menu items until the button receives focus.
 130              // The menu may have already been rendered via a programmatic open.
 131              if ( !that._rendered ) {
 132                  that._refreshMenu();
 133              }
 134          } );
 135      },
 136  
 137      _drawMenu: function() {
 138          var that = this;
 139  
 140          // Create menu
 141          this.menu = $( "<ul>", {
 142              "aria-hidden": "true",
 143              "aria-labelledby": this.ids.button,
 144              id: this.ids.menu
 145          } );
 146  
 147          // Wrap menu
 148          this.menuWrap = $( "<div>" ).append( this.menu );
 149          this._addClass( this.menuWrap, "ui-selectmenu-menu", "ui-front" );
 150          this.menuWrap.appendTo( this._appendTo() );
 151  
 152          // Initialize menu widget
 153          this.menuInstance = this.menu
 154              .menu( {
 155                  classes: {
 156                      "ui-menu": "ui-corner-bottom"
 157                  },
 158                  role: "listbox",
 159                  select: function( event, ui ) {
 160                      event.preventDefault();
 161  
 162                      // Support: IE8
 163                      // If the item was selected via a click, the text selection
 164                      // will be destroyed in IE
 165                      that._setSelection();
 166  
 167                      that._select( ui.item.data( "ui-selectmenu-item" ), event );
 168                  },
 169                  focus: function( event, ui ) {
 170                      var item = ui.item.data( "ui-selectmenu-item" );
 171  
 172                      // Prevent inital focus from firing and check if its a newly focused item
 173                      if ( that.focusIndex != null && item.index !== that.focusIndex ) {
 174                          that._trigger( "focus", event, { item: item } );
 175                          if ( !that.isOpen ) {
 176                              that._select( item, event );
 177                          }
 178                      }
 179                      that.focusIndex = item.index;
 180  
 181                      that.button.attr( "aria-activedescendant",
 182                          that.menuItems.eq( item.index ).attr( "id" ) );
 183                  }
 184              } )
 185              .menu( "instance" );
 186  
 187          // Don't close the menu on mouseleave
 188          this.menuInstance._off( this.menu, "mouseleave" );
 189  
 190          // Cancel the menu's collapseAll on document click
 191          this.menuInstance._closeOnDocumentClick = function() {
 192              return false;
 193          };
 194  
 195          // Selects often contain empty items, but never contain dividers
 196          this.menuInstance._isDivider = function() {
 197              return false;
 198          };
 199      },
 200  
 201      refresh: function() {
 202          this._refreshMenu();
 203          this.buttonItem.replaceWith(
 204              this.buttonItem = this._renderButtonItem(
 205  
 206                  // Fall back to an empty object in case there are no options
 207                  this._getSelectedItem().data( "ui-selectmenu-item" ) || {}
 208              )
 209          );
 210          if ( this.options.width === null ) {
 211              this._resizeButton();
 212          }
 213      },
 214  
 215      _refreshMenu: function() {
 216          var item,
 217              options = this.element.find( "option" );
 218  
 219          this.menu.empty();
 220  
 221          this._parseOptions( options );
 222          this._renderMenu( this.menu, this.items );
 223  
 224          this.menuInstance.refresh();
 225          this.menuItems = this.menu.find( "li" )
 226              .not( ".ui-selectmenu-optgroup" )
 227                  .find( ".ui-menu-item-wrapper" );
 228  
 229          this._rendered = true;
 230  
 231          if ( !options.length ) {
 232              return;
 233          }
 234  
 235          item = this._getSelectedItem();
 236  
 237          // Update the menu to have the correct item focused
 238          this.menuInstance.focus( null, item );
 239          this._setAria( item.data( "ui-selectmenu-item" ) );
 240  
 241          // Set disabled state
 242          this._setOption( "disabled", this.element.prop( "disabled" ) );
 243      },
 244  
 245      open: function( event ) {
 246          if ( this.options.disabled ) {
 247              return;
 248          }
 249  
 250          // If this is the first time the menu is being opened, render the items
 251          if ( !this._rendered ) {
 252              this._refreshMenu();
 253          } else {
 254  
 255              // Menu clears focus on close, reset focus to selected item
 256              this._removeClass( this.menu.find( ".ui-state-active" ), null, "ui-state-active" );
 257              this.menuInstance.focus( null, this._getSelectedItem() );
 258          }
 259  
 260          // If there are no options, don't open the menu
 261          if ( !this.menuItems.length ) {
 262              return;
 263          }
 264  
 265          this.isOpen = true;
 266          this._toggleAttr();
 267          this._resizeMenu();
 268          this._position();
 269  
 270          this._on( this.document, this._documentClick );
 271  
 272          this._trigger( "open", event );
 273      },
 274  
 275      _position: function() {
 276          this.menuWrap.position( $.extend( { of: this.button }, this.options.position ) );
 277      },
 278  
 279      close: function( event ) {
 280          if ( !this.isOpen ) {
 281              return;
 282          }
 283  
 284          this.isOpen = false;
 285          this._toggleAttr();
 286  
 287          this.range = null;
 288          this._off( this.document );
 289  
 290          this._trigger( "close", event );
 291      },
 292  
 293      widget: function() {
 294          return this.button;
 295      },
 296  
 297      menuWidget: function() {
 298          return this.menu;
 299      },
 300  
 301      _renderButtonItem: function( item ) {
 302          var buttonItem = $( "<span>" );
 303  
 304          this._setText( buttonItem, item.label );
 305          this._addClass( buttonItem, "ui-selectmenu-text" );
 306  
 307          return buttonItem;
 308      },
 309  
 310      _renderMenu: function( ul, items ) {
 311          var that = this,
 312              currentOptgroup = "";
 313  
 314          $.each( items, function( index, item ) {
 315              var li;
 316  
 317              if ( item.optgroup !== currentOptgroup ) {
 318                  li = $( "<li>", {
 319                      text: item.optgroup
 320                  } );
 321                  that._addClass( li, "ui-selectmenu-optgroup", "ui-menu-divider" +
 322                      ( item.element.parent( "optgroup" ).prop( "disabled" ) ?
 323                          " ui-state-disabled" :
 324                          "" ) );
 325  
 326                  li.appendTo( ul );
 327  
 328                  currentOptgroup = item.optgroup;
 329              }
 330  
 331              that._renderItemData( ul, item );
 332          } );
 333      },
 334  
 335      _renderItemData: function( ul, item ) {
 336          return this._renderItem( ul, item ).data( "ui-selectmenu-item", item );
 337      },
 338  
 339      _renderItem: function( ul, item ) {
 340          var li = $( "<li>" ),
 341              wrapper = $( "<div>", {
 342                  title: item.element.attr( "title" )
 343              } );
 344  
 345          if ( item.disabled ) {
 346              this._addClass( li, null, "ui-state-disabled" );
 347          }
 348          this._setText( wrapper, item.label );
 349  
 350          return li.append( wrapper ).appendTo( ul );
 351      },
 352  
 353      _setText: function( element, value ) {
 354          if ( value ) {
 355              element.text( value );
 356          } else {
 357              element.html( "&#160;" );
 358          }
 359      },
 360  
 361      _move: function( direction, event ) {
 362          var item, next,
 363              filter = ".ui-menu-item";
 364  
 365          if ( this.isOpen ) {
 366              item = this.menuItems.eq( this.focusIndex ).parent( "li" );
 367          } else {
 368              item = this.menuItems.eq( this.element[ 0 ].selectedIndex ).parent( "li" );
 369              filter += ":not(.ui-state-disabled)";
 370          }
 371  
 372          if ( direction === "first" || direction === "last" ) {
 373              next = item[ direction === "first" ? "prevAll" : "nextAll" ]( filter ).eq( -1 );
 374          } else {
 375              next = item[ direction + "All" ]( filter ).eq( 0 );
 376          }
 377  
 378          if ( next.length ) {
 379              this.menuInstance.focus( event, next );
 380          }
 381      },
 382  
 383      _getSelectedItem: function() {
 384          return this.menuItems.eq( this.element[ 0 ].selectedIndex ).parent( "li" );
 385      },
 386  
 387      _toggle: function( event ) {
 388          this[ this.isOpen ? "close" : "open" ]( event );
 389      },
 390  
 391      _setSelection: function() {
 392          var selection;
 393  
 394          if ( !this.range ) {
 395              return;
 396          }
 397  
 398          if ( window.getSelection ) {
 399              selection = window.getSelection();
 400              selection.removeAllRanges();
 401              selection.addRange( this.range );
 402  
 403          // Support: IE8
 404          } else {
 405              this.range.select();
 406          }
 407  
 408          // Support: IE
 409          // Setting the text selection kills the button focus in IE, but
 410          // restoring the focus doesn't kill the selection.
 411          this.button.focus();
 412      },
 413  
 414      _documentClick: {
 415          mousedown: function( event ) {
 416              if ( !this.isOpen ) {
 417                  return;
 418              }
 419  
 420              if ( !$( event.target ).closest( ".ui-selectmenu-menu, #" +
 421                      $.ui.escapeSelector( this.ids.button ) ).length ) {
 422                  this.close( event );
 423              }
 424          }
 425      },
 426  
 427      _buttonEvents: {
 428  
 429          // Prevent text selection from being reset when interacting with the selectmenu (#10144)
 430          mousedown: function() {
 431              var selection;
 432  
 433              if ( window.getSelection ) {
 434                  selection = window.getSelection();
 435                  if ( selection.rangeCount ) {
 436                      this.range = selection.getRangeAt( 0 );
 437                  }
 438  
 439              // Support: IE8
 440              } else {
 441                  this.range = document.selection.createRange();
 442              }
 443          },
 444  
 445          click: function( event ) {
 446              this._setSelection();
 447              this._toggle( event );
 448          },
 449  
 450          keydown: function( event ) {
 451              var preventDefault = true;
 452              switch ( event.keyCode ) {
 453              case $.ui.keyCode.TAB:
 454              case $.ui.keyCode.ESCAPE:
 455                  this.close( event );
 456                  preventDefault = false;
 457                  break;
 458              case $.ui.keyCode.ENTER:
 459                  if ( this.isOpen ) {
 460                      this._selectFocusedItem( event );
 461                  }
 462                  break;
 463              case $.ui.keyCode.UP:
 464                  if ( event.altKey ) {
 465                      this._toggle( event );
 466                  } else {
 467                      this._move( "prev", event );
 468                  }
 469                  break;
 470              case $.ui.keyCode.DOWN:
 471                  if ( event.altKey ) {
 472                      this._toggle( event );
 473                  } else {
 474                      this._move( "next", event );
 475                  }
 476                  break;
 477              case $.ui.keyCode.SPACE:
 478                  if ( this.isOpen ) {
 479                      this._selectFocusedItem( event );
 480                  } else {
 481                      this._toggle( event );
 482                  }
 483                  break;
 484              case $.ui.keyCode.LEFT:
 485                  this._move( "prev", event );
 486                  break;
 487              case $.ui.keyCode.RIGHT:
 488                  this._move( "next", event );
 489                  break;
 490              case $.ui.keyCode.HOME:
 491              case $.ui.keyCode.PAGE_UP:
 492                  this._move( "first", event );
 493                  break;
 494              case $.ui.keyCode.END:
 495              case $.ui.keyCode.PAGE_DOWN:
 496                  this._move( "last", event );
 497                  break;
 498              default:
 499                  this.menu.trigger( event );
 500                  preventDefault = false;
 501              }
 502  
 503              if ( preventDefault ) {
 504                  event.preventDefault();
 505              }
 506          }
 507      },
 508  
 509      _selectFocusedItem: function( event ) {
 510          var item = this.menuItems.eq( this.focusIndex ).parent( "li" );
 511          if ( !item.hasClass( "ui-state-disabled" ) ) {
 512              this._select( item.data( "ui-selectmenu-item" ), event );
 513          }
 514      },
 515  
 516      _select: function( item, event ) {
 517          var oldIndex = this.element[ 0 ].selectedIndex;
 518  
 519          // Change native select element
 520          this.element[ 0 ].selectedIndex = item.index;
 521          this.buttonItem.replaceWith( this.buttonItem = this._renderButtonItem( item ) );
 522          this._setAria( item );
 523          this._trigger( "select", event, { item: item } );
 524  
 525          if ( item.index !== oldIndex ) {
 526              this._trigger( "change", event, { item: item } );
 527          }
 528  
 529          this.close( event );
 530      },
 531  
 532      _setAria: function( item ) {
 533          var id = this.menuItems.eq( item.index ).attr( "id" );
 534  
 535          this.button.attr( {
 536              "aria-labelledby": id,
 537              "aria-activedescendant": id
 538          } );
 539          this.menu.attr( "aria-activedescendant", id );
 540      },
 541  
 542      _setOption: function( key, value ) {
 543          if ( key === "icons" ) {
 544              var icon = this.button.find( "span.ui-icon" );
 545              this._removeClass( icon, null, this.options.icons.button )
 546                  ._addClass( icon, null, value.button );
 547          }
 548  
 549          this._super( key, value );
 550  
 551          if ( key === "appendTo" ) {
 552              this.menuWrap.appendTo( this._appendTo() );
 553          }
 554  
 555          if ( key === "width" ) {
 556              this._resizeButton();
 557          }
 558      },
 559  
 560      _setOptionDisabled: function( value ) {
 561          this._super( value );
 562  
 563          this.menuInstance.option( "disabled", value );
 564          this.button.attr( "aria-disabled", value );
 565          this._toggleClass( this.button, null, "ui-state-disabled", value );
 566  
 567          this.element.prop( "disabled", value );
 568          if ( value ) {
 569              this.button.attr( "tabindex", -1 );
 570              this.close();
 571          } else {
 572              this.button.attr( "tabindex", 0 );
 573          }
 574      },
 575  
 576      _appendTo: function() {
 577          var element = this.options.appendTo;
 578  
 579          if ( element ) {
 580              element = element.jquery || element.nodeType ?
 581                  $( element ) :
 582                  this.document.find( element ).eq( 0 );
 583          }
 584  
 585          if ( !element || !element[ 0 ] ) {
 586              element = this.element.closest( ".ui-front, dialog" );
 587          }
 588  
 589          if ( !element.length ) {
 590              element = this.document[ 0 ].body;
 591          }
 592  
 593          return element;
 594      },
 595  
 596      _toggleAttr: function() {
 597          this.button.attr( "aria-expanded", this.isOpen );
 598  
 599          // We can't use two _toggleClass() calls here, because we need to make sure
 600          // we always remove classes first and add them second, otherwise if both classes have the
 601          // same theme class, it will be removed after we add it.
 602          this._removeClass( this.button, "ui-selectmenu-button-" +
 603              ( this.isOpen ? "closed" : "open" ) )
 604              ._addClass( this.button, "ui-selectmenu-button-" +
 605                  ( this.isOpen ? "open" : "closed" ) )
 606              ._toggleClass( this.menuWrap, "ui-selectmenu-open", null, this.isOpen );
 607  
 608          this.menu.attr( "aria-hidden", !this.isOpen );
 609      },
 610  
 611      _resizeButton: function() {
 612          var width = this.options.width;
 613  
 614          // For `width: false`, just remove inline style and stop
 615          if ( width === false ) {
 616              this.button.css( "width", "" );
 617              return;
 618          }
 619  
 620          // For `width: null`, match the width of the original element
 621          if ( width === null ) {
 622              width = this.element.show().outerWidth();
 623              this.element.hide();
 624          }
 625  
 626          this.button.outerWidth( width );
 627      },
 628  
 629      _resizeMenu: function() {
 630          this.menu.outerWidth( Math.max(
 631              this.button.outerWidth(),
 632  
 633              // Support: IE10
 634              // IE10 wraps long text (possibly a rounding bug)
 635              // so we add 1px to avoid the wrapping
 636              this.menu.width( "" ).outerWidth() + 1
 637          ) );
 638      },
 639  
 640      _getCreateOptions: function() {
 641          var options = this._super();
 642  
 643          options.disabled = this.element.prop( "disabled" );
 644  
 645          return options;
 646      },
 647  
 648      _parseOptions: function( options ) {
 649          var that = this,
 650              data = [];
 651          options.each( function( index, item ) {
 652              data.push( that._parseOption( $( item ), index ) );
 653          } );
 654          this.items = data;
 655      },
 656  
 657      _parseOption: function( option, index ) {
 658          var optgroup = option.parent( "optgroup" );
 659  
 660          return {
 661              element: option,
 662              index: index,
 663              value: option.val(),
 664              label: option.text(),
 665              optgroup: optgroup.attr( "label" ) || "",
 666              disabled: optgroup.prop( "disabled" ) || option.prop( "disabled" )
 667          };
 668      },
 669  
 670      _destroy: function() {
 671          this._unbindFormResetHandler();
 672          this.menuWrap.remove();
 673          this.button.remove();
 674          this.element.show();
 675          this.element.removeUniqueId();
 676          this.labels.attr( "for", this.ids.element );
 677      }
 678  } ] );
 679  
 680  } ) );


Generated : Wed Dec 2 08:20:02 2020 Cross-referenced by PHPXref