[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/resources/lib/jquery.ui/ -> jquery.ui.menu.js (source)

   1  /*!
   2   * jQuery UI Menu 1.9.2
   3   * http://jqueryui.com
   4   *
   5   * Copyright 2012 jQuery Foundation and other contributors
   6   * Released under the MIT license.
   7   * http://jquery.org/license
   8   *
   9   * http://api.jqueryui.com/menu/
  10   *
  11   * Depends:
  12   *    jquery.ui.core.js
  13   *    jquery.ui.widget.js
  14   *    jquery.ui.position.js
  15   */
  16  (function( $, undefined ) {
  17  
  18  var mouseHandled = false;
  19  
  20  $.widget( "ui.menu", {
  21      version: "1.9.2",
  22      defaultElement: "<ul>",
  23      delay: 300,
  24      options: {
  25          icons: {
  26              submenu: "ui-icon-carat-1-e"
  27          },
  28          menus: "ul",
  29          position: {
  30              my: "left top",
  31              at: "right top"
  32          },
  33          role: "menu",
  34  
  35          // callbacks
  36          blur: null,
  37          focus: null,
  38          select: null
  39      },
  40  
  41      _create: function() {
  42          this.activeMenu = this.element;
  43          this.element
  44              .uniqueId()
  45              .addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" )
  46              .toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length )
  47              .attr({
  48                  role: this.options.role,
  49                  tabIndex: 0
  50              })
  51              // need to catch all clicks on disabled menu
  52              // not possible through _on
  53              .bind( "click" + this.eventNamespace, $.proxy(function( event ) {
  54                  if ( this.options.disabled ) {
  55                      event.preventDefault();
  56                  }
  57              }, this ));
  58  
  59          if ( this.options.disabled ) {
  60              this.element
  61                  .addClass( "ui-state-disabled" )
  62                  .attr( "aria-disabled", "true" );
  63          }
  64  
  65          this._on({
  66              // Prevent focus from sticking to links inside menu after clicking
  67              // them (focus should always stay on UL during navigation).
  68              "mousedown .ui-menu-item > a": function( event ) {
  69                  event.preventDefault();
  70              },
  71              "click .ui-state-disabled > a": function( event ) {
  72                  event.preventDefault();
  73              },
  74              "click .ui-menu-item:has(a)": function( event ) {
  75                  var target = $( event.target ).closest( ".ui-menu-item" );
  76                  if ( !mouseHandled && target.not( ".ui-state-disabled" ).length ) {
  77                      mouseHandled = true;
  78  
  79                      this.select( event );
  80                      // Open submenu on click
  81                      if ( target.has( ".ui-menu" ).length ) {
  82                          this.expand( event );
  83                      } else if ( !this.element.is( ":focus" ) ) {
  84                          // Redirect focus to the menu
  85                          this.element.trigger( "focus", [ true ] );
  86  
  87                          // If the active item is on the top level, let it stay active.
  88                          // Otherwise, blur the active item since it is no longer visible.
  89                          if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) {
  90                              clearTimeout( this.timer );
  91                          }
  92                      }
  93                  }
  94              },
  95              "mouseenter .ui-menu-item": function( event ) {
  96                  var target = $( event.currentTarget );
  97                  // Remove ui-state-active class from siblings of the newly focused menu item
  98                  // to avoid a jump caused by adjacent elements both having a class with a border
  99                  target.siblings().children( ".ui-state-active" ).removeClass( "ui-state-active" );
 100                  this.focus( event, target );
 101              },
 102              mouseleave: "collapseAll",
 103              "mouseleave .ui-menu": "collapseAll",
 104              focus: function( event, keepActiveItem ) {
 105                  // If there's already an active item, keep it active
 106                  // If not, activate the first item
 107                  var item = this.active || this.element.children( ".ui-menu-item" ).eq( 0 );
 108  
 109                  if ( !keepActiveItem ) {
 110                      this.focus( event, item );
 111                  }
 112              },
 113              blur: function( event ) {
 114                  this._delay(function() {
 115                      if ( !$.contains( this.element[0], this.document[0].activeElement ) ) {
 116                          this.collapseAll( event );
 117                      }
 118                  });
 119              },
 120              keydown: "_keydown"
 121          });
 122  
 123          this.refresh();
 124  
 125          // Clicks outside of a menu collapse any open menus
 126          this._on( this.document, {
 127              click: function( event ) {
 128                  if ( !$( event.target ).closest( ".ui-menu" ).length ) {
 129                      this.collapseAll( event );
 130                  }
 131  
 132                  // Reset the mouseHandled flag
 133                  mouseHandled = false;
 134              }
 135          });
 136      },
 137  
 138      _destroy: function() {
 139          // Destroy (sub)menus
 140          this.element
 141              .removeAttr( "aria-activedescendant" )
 142              .find( ".ui-menu" ).andSelf()
 143                  .removeClass( "ui-menu ui-widget ui-widget-content ui-corner-all ui-menu-icons" )
 144                  .removeAttr( "role" )
 145                  .removeAttr( "tabIndex" )
 146                  .removeAttr( "aria-labelledby" )
 147                  .removeAttr( "aria-expanded" )
 148                  .removeAttr( "aria-hidden" )
 149                  .removeAttr( "aria-disabled" )
 150                  .removeUniqueId()
 151                  .show();
 152  
 153          // Destroy menu items
 154          this.element.find( ".ui-menu-item" )
 155              .removeClass( "ui-menu-item" )
 156              .removeAttr( "role" )
 157              .removeAttr( "aria-disabled" )
 158              .children( "a" )
 159                  .removeUniqueId()
 160                  .removeClass( "ui-corner-all ui-state-hover" )
 161                  .removeAttr( "tabIndex" )
 162                  .removeAttr( "role" )
 163                  .removeAttr( "aria-haspopup" )
 164                  .children().each( function() {
 165                      var elem = $( this );
 166                      if ( elem.data( "ui-menu-submenu-carat" ) ) {
 167                          elem.remove();
 168                      }
 169                  });
 170  
 171          // Destroy menu dividers
 172          this.element.find( ".ui-menu-divider" ).removeClass( "ui-menu-divider ui-widget-content" );
 173      },
 174  
 175      _keydown: function( event ) {
 176          var match, prev, character, skip, regex,
 177              preventDefault = true;
 178  
 179  		function escape( value ) {
 180              return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" );
 181          }
 182  
 183          switch ( event.keyCode ) {
 184          case $.ui.keyCode.PAGE_UP:
 185              this.previousPage( event );
 186              break;
 187          case $.ui.keyCode.PAGE_DOWN:
 188              this.nextPage( event );
 189              break;
 190          case $.ui.keyCode.HOME:
 191              this._move( "first", "first", event );
 192              break;
 193          case $.ui.keyCode.END:
 194              this._move( "last", "last", event );
 195              break;
 196          case $.ui.keyCode.UP:
 197              this.previous( event );
 198              break;
 199          case $.ui.keyCode.DOWN:
 200              this.next( event );
 201              break;
 202          case $.ui.keyCode.LEFT:
 203              this.collapse( event );
 204              break;
 205          case $.ui.keyCode.RIGHT:
 206              if ( this.active && !this.active.is( ".ui-state-disabled" ) ) {
 207                  this.expand( event );
 208              }
 209              break;
 210          case $.ui.keyCode.ENTER:
 211          case $.ui.keyCode.SPACE:
 212              this._activate( event );
 213              break;
 214          case $.ui.keyCode.ESCAPE:
 215              this.collapse( event );
 216              break;
 217          default:
 218              preventDefault = false;
 219              prev = this.previousFilter || "";
 220              character = String.fromCharCode( event.keyCode );
 221              skip = false;
 222  
 223              clearTimeout( this.filterTimer );
 224  
 225              if ( character === prev ) {
 226                  skip = true;
 227              } else {
 228                  character = prev + character;
 229              }
 230  
 231              regex = new RegExp( "^" + escape( character ), "i" );
 232              match = this.activeMenu.children( ".ui-menu-item" ).filter(function() {
 233                  return regex.test( $( this ).children( "a" ).text() );
 234              });
 235              match = skip && match.index( this.active.next() ) !== -1 ?
 236                  this.active.nextAll( ".ui-menu-item" ) :
 237                  match;
 238  
 239              // If no matches on the current filter, reset to the last character pressed
 240              // to move down the menu to the first item that starts with that character
 241              if ( !match.length ) {
 242                  character = String.fromCharCode( event.keyCode );
 243                  regex = new RegExp( "^" + escape( character ), "i" );
 244                  match = this.activeMenu.children( ".ui-menu-item" ).filter(function() {
 245                      return regex.test( $( this ).children( "a" ).text() );
 246                  });
 247              }
 248  
 249              if ( match.length ) {
 250                  this.focus( event, match );
 251                  if ( match.length > 1 ) {
 252                      this.previousFilter = character;
 253                      this.filterTimer = this._delay(function() {
 254                          delete this.previousFilter;
 255                      }, 1000 );
 256                  } else {
 257                      delete this.previousFilter;
 258                  }
 259              } else {
 260                  delete this.previousFilter;
 261              }
 262          }
 263  
 264          if ( preventDefault ) {
 265              event.preventDefault();
 266          }
 267      },
 268  
 269      _activate: function( event ) {
 270          if ( !this.active.is( ".ui-state-disabled" ) ) {
 271              if ( this.active.children( "a[aria-haspopup='true']" ).length ) {
 272                  this.expand( event );
 273              } else {
 274                  this.select( event );
 275              }
 276          }
 277      },
 278  
 279      refresh: function() {
 280          var menus,
 281              icon = this.options.icons.submenu,
 282              submenus = this.element.find( this.options.menus );
 283  
 284          // Initialize nested menus
 285          submenus.filter( ":not(.ui-menu)" )
 286              .addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" )
 287              .hide()
 288              .attr({
 289                  role: this.options.role,
 290                  "aria-hidden": "true",
 291                  "aria-expanded": "false"
 292              })
 293              .each(function() {
 294                  var menu = $( this ),
 295                      item = menu.prev( "a" ),
 296                      submenuCarat = $( "<span>" )
 297                          .addClass( "ui-menu-icon ui-icon " + icon )
 298                          .data( "ui-menu-submenu-carat", true );
 299  
 300                  item
 301                      .attr( "aria-haspopup", "true" )
 302                      .prepend( submenuCarat );
 303                  menu.attr( "aria-labelledby", item.attr( "id" ) );
 304              });
 305  
 306          menus = submenus.add( this.element );
 307  
 308          // Don't refresh list items that are already adapted
 309          menus.children( ":not(.ui-menu-item):has(a)" )
 310              .addClass( "ui-menu-item" )
 311              .attr( "role", "presentation" )
 312              .children( "a" )
 313                  .uniqueId()
 314                  .addClass( "ui-corner-all" )
 315                  .attr({
 316                      tabIndex: -1,
 317                      role: this._itemRole()
 318                  });
 319  
 320          // Initialize unlinked menu-items containing spaces and/or dashes only as dividers
 321          menus.children( ":not(.ui-menu-item)" ).each(function() {
 322              var item = $( this );
 323              // hyphen, em dash, en dash
 324              if ( !/[^\-—–\s]/.test( item.text() ) ) {
 325                  item.addClass( "ui-widget-content ui-menu-divider" );
 326              }
 327          });
 328  
 329          // Add aria-disabled attribute to any disabled menu item
 330          menus.children( ".ui-state-disabled" ).attr( "aria-disabled", "true" );
 331  
 332          // If the active item has been removed, blur the menu
 333          if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
 334              this.blur();
 335          }
 336      },
 337  
 338      _itemRole: function() {
 339          return {
 340              menu: "menuitem",
 341              listbox: "option"
 342          }[ this.options.role ];
 343      },
 344  
 345      focus: function( event, item ) {
 346          var nested, focused;
 347          this.blur( event, event && event.type === "focus" );
 348  
 349          this._scrollIntoView( item );
 350  
 351          this.active = item.first();
 352          focused = this.active.children( "a" ).addClass( "ui-state-focus" );
 353          // Only update aria-activedescendant if there's a role
 354          // otherwise we assume focus is managed elsewhere
 355          if ( this.options.role ) {
 356              this.element.attr( "aria-activedescendant", focused.attr( "id" ) );
 357          }
 358  
 359          // Highlight active parent menu item, if any
 360          this.active
 361              .parent()
 362              .closest( ".ui-menu-item" )
 363              .children( "a:first" )
 364              .addClass( "ui-state-active" );
 365  
 366          if ( event && event.type === "keydown" ) {
 367              this._close();
 368          } else {
 369              this.timer = this._delay(function() {
 370                  this._close();
 371              }, this.delay );
 372          }
 373  
 374          nested = item.children( ".ui-menu" );
 375          if ( nested.length && ( /^mouse/.test( event.type ) ) ) {
 376              this._startOpening(nested);
 377          }
 378          this.activeMenu = item.parent();
 379  
 380          this._trigger( "focus", event, { item: item } );
 381      },
 382  
 383      _scrollIntoView: function( item ) {
 384          var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;
 385          if ( this._hasScroll() ) {
 386              borderTop = parseFloat( $.css( this.activeMenu[0], "borderTopWidth" ) ) || 0;
 387              paddingTop = parseFloat( $.css( this.activeMenu[0], "paddingTop" ) ) || 0;
 388              offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop;
 389              scroll = this.activeMenu.scrollTop();
 390              elementHeight = this.activeMenu.height();
 391              itemHeight = item.height();
 392  
 393              if ( offset < 0 ) {
 394                  this.activeMenu.scrollTop( scroll + offset );
 395              } else if ( offset + itemHeight > elementHeight ) {
 396                  this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight );
 397              }
 398          }
 399      },
 400  
 401      blur: function( event, fromFocus ) {
 402          if ( !fromFocus ) {
 403              clearTimeout( this.timer );
 404          }
 405  
 406          if ( !this.active ) {
 407              return;
 408          }
 409  
 410          this.active.children( "a" ).removeClass( "ui-state-focus" );
 411          this.active = null;
 412  
 413          this._trigger( "blur", event, { item: this.active } );
 414      },
 415  
 416      _startOpening: function( submenu ) {
 417          clearTimeout( this.timer );
 418  
 419          // Don't open if already open fixes a Firefox bug that caused a .5 pixel
 420          // shift in the submenu position when mousing over the carat icon
 421          if ( submenu.attr( "aria-hidden" ) !== "true" ) {
 422              return;
 423          }
 424  
 425          this.timer = this._delay(function() {
 426              this._close();
 427              this._open( submenu );
 428          }, this.delay );
 429      },
 430  
 431      _open: function( submenu ) {
 432          var position = $.extend({
 433              of: this.active
 434          }, this.options.position );
 435  
 436          clearTimeout( this.timer );
 437          this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) )
 438              .hide()
 439              .attr( "aria-hidden", "true" );
 440  
 441          submenu
 442              .show()
 443              .removeAttr( "aria-hidden" )
 444              .attr( "aria-expanded", "true" )
 445              .position( position );
 446      },
 447  
 448      collapseAll: function( event, all ) {
 449          clearTimeout( this.timer );
 450          this.timer = this._delay(function() {
 451              // If we were passed an event, look for the submenu that contains the event
 452              var currentMenu = all ? this.element :
 453                  $( event && event.target ).closest( this.element.find( ".ui-menu" ) );
 454  
 455              // If we found no valid submenu ancestor, use the main menu to close all sub menus anyway
 456              if ( !currentMenu.length ) {
 457                  currentMenu = this.element;
 458              }
 459  
 460              this._close( currentMenu );
 461  
 462              this.blur( event );
 463              this.activeMenu = currentMenu;
 464          }, this.delay );
 465      },
 466  
 467      // With no arguments, closes the currently active menu - if nothing is active
 468      // it closes all menus.  If passed an argument, it will search for menus BELOW
 469      _close: function( startMenu ) {
 470          if ( !startMenu ) {
 471              startMenu = this.active ? this.active.parent() : this.element;
 472          }
 473  
 474          startMenu
 475              .find( ".ui-menu" )
 476                  .hide()
 477                  .attr( "aria-hidden", "true" )
 478                  .attr( "aria-expanded", "false" )
 479              .end()
 480              .find( "a.ui-state-active" )
 481                  .removeClass( "ui-state-active" );
 482      },
 483  
 484      collapse: function( event ) {
 485          var newItem = this.active &&
 486              this.active.parent().closest( ".ui-menu-item", this.element );
 487          if ( newItem && newItem.length ) {
 488              this._close();
 489              this.focus( event, newItem );
 490          }
 491      },
 492  
 493      expand: function( event ) {
 494          var newItem = this.active &&
 495              this.active
 496                  .children( ".ui-menu " )
 497                  .children( ".ui-menu-item" )
 498                  .first();
 499  
 500          if ( newItem && newItem.length ) {
 501              this._open( newItem.parent() );
 502  
 503              // Delay so Firefox will not hide activedescendant change in expanding submenu from AT
 504              this._delay(function() {
 505                  this.focus( event, newItem );
 506              });
 507          }
 508      },
 509  
 510      next: function( event ) {
 511          this._move( "next", "first", event );
 512      },
 513  
 514      previous: function( event ) {
 515          this._move( "prev", "last", event );
 516      },
 517  
 518      isFirstItem: function() {
 519          return this.active && !this.active.prevAll( ".ui-menu-item" ).length;
 520      },
 521  
 522      isLastItem: function() {
 523          return this.active && !this.active.nextAll( ".ui-menu-item" ).length;
 524      },
 525  
 526      _move: function( direction, filter, event ) {
 527          var next;
 528          if ( this.active ) {
 529              if ( direction === "first" || direction === "last" ) {
 530                  next = this.active
 531                      [ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" )
 532                      .eq( -1 );
 533              } else {
 534                  next = this.active
 535                      [ direction + "All" ]( ".ui-menu-item" )
 536                      .eq( 0 );
 537              }
 538          }
 539          if ( !next || !next.length || !this.active ) {
 540              next = this.activeMenu.children( ".ui-menu-item" )[ filter ]();
 541          }
 542  
 543          this.focus( event, next );
 544      },
 545  
 546      nextPage: function( event ) {
 547          var item, base, height;
 548  
 549          if ( !this.active ) {
 550              this.next( event );
 551              return;
 552          }
 553          if ( this.isLastItem() ) {
 554              return;
 555          }
 556          if ( this._hasScroll() ) {
 557              base = this.active.offset().top;
 558              height = this.element.height();
 559              this.active.nextAll( ".ui-menu-item" ).each(function() {
 560                  item = $( this );
 561                  return item.offset().top - base - height < 0;
 562              });
 563  
 564              this.focus( event, item );
 565          } else {
 566              this.focus( event, this.activeMenu.children( ".ui-menu-item" )
 567                  [ !this.active ? "first" : "last" ]() );
 568          }
 569      },
 570  
 571      previousPage: function( event ) {
 572          var item, base, height;
 573          if ( !this.active ) {
 574              this.next( event );
 575              return;
 576          }
 577          if ( this.isFirstItem() ) {
 578              return;
 579          }
 580          if ( this._hasScroll() ) {
 581              base = this.active.offset().top;
 582              height = this.element.height();
 583              this.active.prevAll( ".ui-menu-item" ).each(function() {
 584                  item = $( this );
 585                  return item.offset().top - base + height > 0;
 586              });
 587  
 588              this.focus( event, item );
 589          } else {
 590              this.focus( event, this.activeMenu.children( ".ui-menu-item" ).first() );
 591          }
 592      },
 593  
 594      _hasScroll: function() {
 595          return this.element.outerHeight() < this.element.prop( "scrollHeight" );
 596      },
 597  
 598      select: function( event ) {
 599          // TODO: It should never be possible to not have an active item at this
 600          // point, but the tests don't trigger mouseenter before click.
 601          this.active = this.active || $( event.target ).closest( ".ui-menu-item" );
 602          var ui = { item: this.active };
 603          if ( !this.active.has( ".ui-menu" ).length ) {
 604              this.collapseAll( event, true );
 605          }
 606          this._trigger( "select", event, ui );
 607      }
 608  });
 609  
 610  }( jQuery ));


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1