[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
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 ));
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |