| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 /** 2 * @output wp-admin/js/nav-menu.js 3 */ 4 5 /* global menus, postboxes, columns, isRtl, ajaxurl, wpNavMenu */ 6 7 /** 8 * Handles the WordPress Administration Navigation Menu Interface functionality. 9 * 10 * @version 2.0.0 11 * @package WordPress 12 * 13 * @param {JQueryStatic} $ The jQuery object. 14 */ 15 (function($) { 16 17 var api; 18 19 /** 20 * Contains all the functions to handle WordPress navigation menus administration. 21 * 22 * @namespace wpNavMenu 23 */ 24 api = window.wpNavMenu = { 25 26 options : { 27 menuItemDepthPerLevel : 30, // Do not use directly. Use depthToPx and pxToDepth instead. 28 globalMaxDepth: 11, 29 sortableItems: '> *', 30 targetTolerance: 0 31 }, 32 33 menuList : undefined, // Set in init. 34 targetList : undefined, // Set in init. 35 menusChanged : false, 36 isRTL: !! ( 'undefined' != typeof isRtl && isRtl ), 37 negateIfRTL: ( 'undefined' != typeof isRtl && isRtl ) ? -1 : 1, 38 lastSearch: '', 39 40 // Functions that run on init. 41 init : function() { 42 api.menuList = $('#menu-to-edit'); 43 api.targetList = api.menuList; 44 45 this.jQueryExtensions(); 46 47 this.attachMenuEditListeners(); 48 49 this.attachBulkSelectButtonListeners(); 50 this.attachMenuCheckBoxListeners(); 51 this.attachMenuItemDeleteButton(); 52 this.attachPendingMenuItemsListForDeletion(); 53 54 this.attachQuickSearchListeners(); 55 this.attachThemeLocationsListeners(); 56 this.attachMenuSaveSubmitListeners(); 57 58 this.attachTabsPanelListeners(); 59 60 this.attachUnsavedChangesListener(); 61 62 if ( api.menuList.length ) 63 this.initSortables(); 64 65 if ( menus.oneThemeLocationNoMenus ) 66 $( '#posttype-page' ).addSelectedToMenu( api.addMenuItemToBottom ); 67 68 this.initManageLocations(); 69 70 this.initAccessibility(); 71 72 this.initToggles(); 73 74 this.initPreviewing(); 75 }, 76 77 jQueryExtensions : function() { 78 // jQuery extensions. 79 $.fn.extend({ 80 menuItemDepth : function() { 81 var margin = api.isRTL ? this.eq(0).css('margin-right') : this.eq(0).css('margin-left'); 82 return api.pxToDepth( margin && -1 != margin.indexOf('px') ? margin.slice(0, -2) : 0 ); 83 }, 84 updateDepthClass : function(current, prev) { 85 return this.each(function(){ 86 var t = $(this); 87 prev = prev || t.menuItemDepth(); 88 $(this).removeClass('menu-item-depth-'+ prev ) 89 .addClass('menu-item-depth-'+ current ); 90 }); 91 }, 92 shiftDepthClass : function(change) { 93 return this.each(function(){ 94 var t = $(this), 95 depth = t.menuItemDepth(), 96 newDepth = depth + change; 97 98 t.removeClass( 'menu-item-depth-'+ depth ) 99 .addClass( 'menu-item-depth-'+ ( newDepth ) ); 100 101 if ( 0 === newDepth ) { 102 t.find( '.is-submenu' ).hide(); 103 } 104 }); 105 }, 106 childMenuItems : function() { 107 var result = $(); 108 this.each(function(){ 109 var t = $(this), depth = t.menuItemDepth(), next = t.next( '.menu-item' ); 110 while( next.length && next.menuItemDepth() > depth ) { 111 result = result.add( next ); 112 next = next.next( '.menu-item' ); 113 } 114 }); 115 return result; 116 }, 117 shiftHorizontally : function( dir ) { 118 return this.each(function(){ 119 var t = $(this), 120 depth = t.menuItemDepth(), 121 newDepth = depth + dir; 122 123 // Change .menu-item-depth-n class. 124 t.moveHorizontally( newDepth, depth ); 125 }); 126 }, 127 moveHorizontally : function( newDepth, depth ) { 128 return this.each(function(){ 129 var t = $(this), 130 children = t.childMenuItems(), 131 diff = newDepth - depth, 132 subItemText = t.find('.is-submenu'); 133 134 // Change .menu-item-depth-n class. 135 t.updateDepthClass( newDepth, depth ).updateParentMenuItemDBId(); 136 137 // If it has children, move those too. 138 if ( children ) { 139 children.each(function() { 140 var t = $(this), 141 thisDepth = t.menuItemDepth(), 142 newDepth = thisDepth + diff; 143 t.updateDepthClass(newDepth, thisDepth).updateParentMenuItemDBId(); 144 }); 145 } 146 147 // Show "Sub item" helper text. 148 if (0 === newDepth) 149 subItemText.hide(); 150 else 151 subItemText.show(); 152 }); 153 }, 154 updateParentMenuItemDBId : function() { 155 return this.each(function(){ 156 var item = $(this), 157 input = item.find( '.menu-item-data-parent-id' ), 158 depth = parseInt( item.menuItemDepth(), 10 ), 159 parentDepth = depth - 1, 160 parent = item.prevAll( '.menu-item-depth-' + parentDepth ).first(); 161 162 if ( 0 === depth ) { // Item is on the top level, has no parent. 163 input.val(0); 164 } else { // Find the parent item, and retrieve its object id. 165 input.val( parent.find( '.menu-item-data-db-id' ).val() ); 166 } 167 }); 168 }, 169 hideAdvancedMenuItemFields : function() { 170 return this.each(function(){ 171 var that = $(this); 172 $('.hide-column-tog').not(':checked').each(function(){ 173 that.find('.field-' + $(this).val() ).addClass('hidden-field'); 174 }); 175 }); 176 }, 177 /** 178 * Adds selected menu items to the menu. 179 * 180 * @ignore 181 * 182 * @param {Function} processMethod The method to use for adding the menu items. Defaults to api.addMenuItemToBottom. 183 */ 184 addSelectedToMenu : function(processMethod) { 185 if ( 0 === $('#menu-to-edit').length ) { 186 return false; 187 } 188 189 return this.each(function() { 190 var t = $(this), menuItems = {}, 191 checkboxes = ( menus.oneThemeLocationNoMenus && 0 === t.find( '.tabs-panel-active .categorychecklist li input:checked' ).length ) ? t.find( '#page-all li input[type="checkbox"]' ) : t.find( '.tabs-panel-active .categorychecklist li input:checked' ), 192 re = /menu-item\[([^\]]*)/; 193 194 processMethod = processMethod || api.addMenuItemToBottom; 195 196 // If no items are checked, bail. 197 if ( !checkboxes.length ) 198 return false; 199 200 // Show the Ajax spinner. 201 t.find( '.button-controls .spinner' ).addClass( 'is-active' ); 202 203 // Retrieve menu item data. 204 $(checkboxes).each(function(){ 205 var t = $(this), 206 listItemDBIDMatch = re.exec( t.attr('name') ), 207 listItemDBID = 'undefined' == typeof listItemDBIDMatch[1] ? 0 : parseInt(listItemDBIDMatch[1], 10); 208 209 if ( this.className && -1 != this.className.indexOf('add-to-top') ) 210 processMethod = api.addMenuItemToTop; 211 menuItems[listItemDBID] = t.closest('li').getItemData( 'add-menu-item', listItemDBID ); 212 }); 213 214 // Add the items. 215 api.addItemToMenu(menuItems, processMethod, function(){ 216 // Deselect the items and hide the Ajax spinner. 217 checkboxes.prop( 'checked', false ); 218 t.find( '.button-controls .select-all' ).prop( 'checked', false ); 219 t.find( '.button-controls .spinner' ).removeClass( 'is-active' ); 220 t.updateParentDropdown(); 221 t.updateOrderDropdown(); 222 }); 223 }); 224 }, 225 getItemData : function( itemType, id ) { 226 itemType = itemType || 'menu-item'; 227 228 var itemData = {}, i, 229 fields = [ 230 'menu-item-db-id', 231 'menu-item-object-id', 232 'menu-item-object', 233 'menu-item-parent-id', 234 'menu-item-position', 235 'menu-item-type', 236 'menu-item-title', 237 'menu-item-url', 238 'menu-item-description', 239 'menu-item-attr-title', 240 'menu-item-target', 241 'menu-item-classes', 242 'menu-item-xfn' 243 ]; 244 245 if( !id && itemType == 'menu-item' ) { 246 id = this.find('.menu-item-data-db-id').val(); 247 } 248 249 if( !id ) return itemData; 250 251 this.find('input').each(function() { 252 var field; 253 i = fields.length; 254 while ( i-- ) { 255 if( itemType == 'menu-item' ) 256 field = fields[i] + '[' + id + ']'; 257 else if( itemType == 'add-menu-item' ) 258 field = 'menu-item[' + id + '][' + fields[i] + ']'; 259 260 if ( 261 this.name && 262 field == this.name 263 ) { 264 itemData[fields[i]] = this.value; 265 } 266 } 267 }); 268 269 return itemData; 270 }, 271 setItemData : function( itemData, itemType, id ) { // Can take a type, such as 'menu-item', or an id. 272 itemType = itemType || 'menu-item'; 273 274 if( !id && itemType == 'menu-item' ) { 275 id = $('.menu-item-data-db-id', this).val(); 276 } 277 278 if( !id ) return this; 279 280 this.find('input').each(function() { 281 var t = $(this), field; 282 $.each( itemData, function( attr, val ) { 283 if( itemType == 'menu-item' ) 284 field = attr + '[' + id + ']'; 285 else if( itemType == 'add-menu-item' ) 286 field = 'menu-item[' + id + '][' + attr + ']'; 287 288 if ( field == t.attr('name') ) { 289 t.val( val ); 290 } 291 }); 292 }); 293 return this; 294 }, 295 updateParentDropdown : function() { 296 return this.each(function(){ 297 var menuItems = $( '#menu-to-edit li' ), 298 parentDropdowns = $( '.edit-menu-item-parent' ); 299 300 $.each( parentDropdowns, function() { 301 var parentDropdown = $( this ), 302 currentItemID = parseInt( parentDropdown.closest( 'li.menu-item' ).find( '.menu-item-data-db-id' ).val() ), 303 currentParentID = parseInt( parentDropdown.closest( 'li.menu-item' ).find( '.menu-item-data-parent-id' ).val() ), 304 currentItem = parentDropdown.closest( 'li.menu-item' ), 305 currentMenuItemChild = currentItem.childMenuItems(), 306 excludeMenuItem = /** @type {number[]} */ [ currentItemID ]; 307 308 parentDropdown.empty(); 309 310 if ( currentMenuItemChild.length > 0 ) { 311 $.each( currentMenuItemChild, function(){ 312 var childItem = $(this), 313 childID = parseInt( childItem.find( '.menu-item-data-db-id' ).val() ); 314 315 excludeMenuItem.push( childID ); 316 }); 317 } 318 319 parentDropdown.append( 320 $( '<option>', { 321 value: '0', 322 selected: currentParentID === 0, 323 text: wp.i18n._x( 'No Parent', 'menu item without a parent in navigation menu' ), 324 } ) 325 ); 326 327 $.each( menuItems, function() { 328 var menuItem = $(this), 329 menuID = parseInt( menuItem.find( '.menu-item-data-db-id' ).val() ), 330 menuTitle = menuItem.find( '.edit-menu-item-title' ).val(); 331 332 if ( ! excludeMenuItem.includes( menuID ) ) { 333 parentDropdown.append( 334 $( '<option>', { 335 value: menuID.toString(), 336 selected: currentParentID === menuID, 337 text: menuTitle, 338 } ) 339 ); 340 } 341 }); 342 }); 343 344 }); 345 }, 346 updateOrderDropdown : function() { 347 return this.each( function() { 348 var itemPosition, 349 orderDropdowns = $( '.edit-menu-item-order' ); 350 351 $.each( orderDropdowns, function() { 352 var orderDropdown = $( this ), 353 menuItem = orderDropdown.closest( 'li.menu-item' ).first(), 354 depth = menuItem.menuItemDepth(), 355 isPrimaryMenuItem = ( 0 === depth ); 356 357 orderDropdown.empty(); 358 359 if ( isPrimaryMenuItem ) { 360 var primaryItems = $( '.menu-item-depth-0' ), 361 totalMenuItems = primaryItems.length; 362 363 itemPosition = primaryItems.index( menuItem ) + 1; 364 365 for ( let i = 1; i < totalMenuItems + 1; i++ ) { 366 var itemString = wp.i18n.sprintf( 367 /* translators: 1: The current menu item number, 2: The total number of menu items. */ 368 wp.i18n._x( '%1$s of %2$s', 'part of a total number of menu items' ), 369 i, 370 totalMenuItems 371 ); 372 orderDropdown.append( 373 $( '<option>', { 374 selected: i === itemPosition, 375 value: i.toString(), 376 text: itemString, 377 } ) 378 ); 379 } 380 381 } else { 382 var parentItem = menuItem.prevAll( '.menu-item-depth-' + parseInt( depth - 1, 10 ) ).first(), 383 parentItemId = parentItem.find( '.menu-item-data-db-id' ).val(), 384 subItems = $( '.menu-item .menu-item-data-parent-id[value="' + parentItemId + '"]' ), 385 totalSubMenuItems = subItems.length; 386 387 itemPosition = $( subItems.parents('.menu-item').get().reverse() ).index( menuItem ) + 1; 388 389 for ( let i = 1; i < totalSubMenuItems + 1; i++ ) { 390 var submenuString = wp.i18n.sprintf( 391 /* translators: 1: The current submenu item number, 2: The total number of submenu items. */ 392 wp.i18n._x( '%1$s of %2$s', 'part of a total number of menu items' ), 393 i, 394 totalSubMenuItems 395 ); 396 orderDropdown.append( 397 $( '<option>', { 398 selected: i === itemPosition, 399 value: i.toString(), 400 text: submenuString, 401 } ) 402 ); 403 } 404 405 } 406 }); 407 408 }); 409 } 410 }); 411 }, 412 413 countMenuItems : function( depth ) { 414 return $( '.menu-item-depth-' + depth ).length; 415 }, 416 417 moveMenuItem : function( $this, dir ) { 418 var items, newItemPosition, newDepth, 419 menuItems = $( '#menu-to-edit li' ), 420 menuItemsCount = menuItems.length, 421 thisItem = $this.parents( 'li.menu-item' ), 422 thisItemChildren = thisItem.childMenuItems(), 423 thisItemData = thisItem.getItemData(), 424 thisItemDepth = parseInt( thisItem.menuItemDepth(), 10 ), 425 thisItemPosition = parseInt( thisItem.index(), 10 ), 426 nextItem = thisItem.next(), 427 nextItemChildren = nextItem.childMenuItems(), 428 nextItemDepth = parseInt( nextItem.menuItemDepth(), 10 ) + 1, 429 prevItem = thisItem.prev(), 430 prevItemDepth = parseInt( prevItem.menuItemDepth(), 10 ), 431 prevItemId = prevItem.getItemData()['menu-item-db-id'], 432 a11ySpeech = menus[ 'moved' + dir.charAt(0).toUpperCase() + dir.slice(1) ]; 433 434 switch ( dir ) { 435 case 'up': 436 newItemPosition = thisItemPosition - 1; 437 438 // Already at top. 439 if ( 0 === thisItemPosition ) 440 break; 441 442 // If a sub item is moved to top, shift it to 0 depth. 443 if ( 0 === newItemPosition && 0 !== thisItemDepth ) 444 thisItem.moveHorizontally( 0, thisItemDepth ); 445 446 // If prev item is sub item, shift to match depth. 447 if ( 0 !== prevItemDepth ) 448 thisItem.moveHorizontally( prevItemDepth, thisItemDepth ); 449 450 // Does this item have sub items? 451 if ( thisItemChildren ) { 452 items = thisItem.add( thisItemChildren ); 453 // Move the entire block. 454 items.detach().insertBefore( menuItems.eq( newItemPosition ) ).updateParentMenuItemDBId(); 455 } else { 456 thisItem.detach().insertBefore( menuItems.eq( newItemPosition ) ).updateParentMenuItemDBId(); 457 } 458 break; 459 case 'down': 460 // Does this item have sub items? 461 if ( thisItemChildren ) { 462 items = thisItem.add( thisItemChildren ), 463 nextItem = menuItems.eq( items.length + thisItemPosition ), 464 nextItemChildren = 0 !== nextItem.childMenuItems().length; 465 466 if ( nextItemChildren ) { 467 newDepth = parseInt( nextItem.menuItemDepth(), 10 ) + 1; 468 thisItem.moveHorizontally( newDepth, thisItemDepth ); 469 } 470 471 // Have we reached the bottom? 472 if ( menuItemsCount === thisItemPosition + items.length ) 473 break; 474 475 items.detach().insertAfter( menuItems.eq( thisItemPosition + items.length ) ).updateParentMenuItemDBId(); 476 } else { 477 // If next item has sub items, shift depth. 478 if ( 0 !== nextItemChildren.length ) 479 thisItem.moveHorizontally( nextItemDepth, thisItemDepth ); 480 481 // Have we reached the bottom? 482 if ( menuItemsCount === thisItemPosition + 1 ) 483 break; 484 thisItem.detach().insertAfter( menuItems.eq( thisItemPosition + 1 ) ).updateParentMenuItemDBId(); 485 } 486 break; 487 case 'top': 488 // Already at top. 489 if ( 0 === thisItemPosition ) 490 break; 491 // Does this item have sub items? 492 if ( thisItemChildren ) { 493 items = thisItem.add( thisItemChildren ); 494 // Move the entire block. 495 items.detach().insertBefore( menuItems.eq( 0 ) ).updateParentMenuItemDBId(); 496 } else { 497 thisItem.detach().insertBefore( menuItems.eq( 0 ) ).updateParentMenuItemDBId(); 498 } 499 break; 500 case 'left': 501 // As far left as possible. 502 if ( 0 === thisItemDepth ) 503 break; 504 thisItem.shiftHorizontally( -1 ); 505 break; 506 case 'right': 507 // Can't be sub item at top. 508 if ( 0 === thisItemPosition ) 509 break; 510 // Already sub item of prevItem. 511 if ( thisItemData['menu-item-parent-id'] === prevItemId ) 512 break; 513 thisItem.shiftHorizontally( 1 ); 514 break; 515 } 516 $this.trigger( 'focus' ); 517 api.registerChange(); 518 api.refreshKeyboardAccessibility(); 519 api.refreshAdvancedAccessibility(); 520 thisItem.updateParentDropdown(); 521 thisItem.updateOrderDropdown(); 522 523 if ( a11ySpeech ) { 524 wp.a11y.speak( a11ySpeech ); 525 } 526 }, 527 528 initAccessibility : function() { 529 var menu = $( '#menu-to-edit' ); 530 531 api.refreshKeyboardAccessibility(); 532 api.refreshAdvancedAccessibility(); 533 534 // Refresh the accessibility when the user comes close to the item in any way. 535 menu.on( 'mouseenter.refreshAccessibility focus.refreshAccessibility touchstart.refreshAccessibility' , '.menu-item' , function(){ 536 api.refreshAdvancedAccessibilityOfItem( $( this ).find( 'a.item-edit' ) ); 537 } ); 538 539 // We have to update on click as well because we might hover first, change the item, and then click. 540 menu.on( 'click', 'a.item-edit', function() { 541 api.refreshAdvancedAccessibilityOfItem( $( this ) ); 542 } ); 543 544 // Links for moving items. 545 menu.on( 'click', '.menus-move', function () { 546 var $this = $( this ), 547 dir = $this.data( 'dir' ); 548 549 if ( 'undefined' !== typeof dir ) { 550 api.moveMenuItem( $( this ).parents( 'li.menu-item' ).find( 'a.item-edit' ), dir ); 551 } 552 }); 553 554 // Set menu parents data for all menu items. 555 menu.updateParentDropdown(); 556 557 // Set menu order data for all menu items. 558 menu.updateOrderDropdown(); 559 560 // Update menu item parent when value is changed. 561 menu.on( 'change', '.edit-menu-item-parent', function() { 562 api.changeMenuParent( $( this ) ); 563 }); 564 565 // Update menu item order when value is changed. 566 menu.on( 'change', '.edit-menu-item-order', function() { 567 api.changeMenuOrder( $( this ) ); 568 }); 569 }, 570 571 /** 572 * changeMenuParent( [parentDropdown] ) 573 * 574 * @since 6.7.0 575 * 576 * @param {Object} parentDropdown select field 577 */ 578 changeMenuParent : function( parentDropdown ) { 579 var menuItemNewPosition, 580 menuItems = $( '#menu-to-edit li' ), 581 $this = $( parentDropdown ), 582 newParentID = $this.val(), 583 menuItem = $this.closest( 'li.menu-item' ).first(), 584 menuItemOldDepth = menuItem.menuItemDepth(), 585 menuItemChildren = menuItem.childMenuItems(), 586 menuItemNoChildren = parseInt( menuItem.childMenuItems().length, 10 ), 587 parentItem = $( '#menu-item-' + newParentID ), 588 parentItemDepth = parentItem.menuItemDepth(), 589 menuItemNewDepth = parseInt( parentItemDepth ) + 1; 590 591 if ( newParentID == 0 ) { 592 menuItemNewDepth = 0; 593 } 594 595 menuItem.find( '.menu-item-data-parent-id' ).val( newParentID ); 596 menuItem.moveHorizontally( menuItemNewDepth, menuItemOldDepth ); 597 598 if ( menuItemNoChildren > 0 ) { 599 menuItem = menuItem.add( menuItemChildren ); 600 } 601 menuItem.detach(); 602 603 menuItems = $( '#menu-to-edit li' ); 604 605 var parentItemPosition = parseInt( parentItem.index(), 10 ), 606 parentItemNoChild = parseInt( parentItem.childMenuItems().length, 10 ); 607 608 if ( parentItemNoChild > 0 ){ 609 menuItemNewPosition = parentItemPosition + parentItemNoChild; 610 } else { 611 menuItemNewPosition = parentItemPosition; 612 } 613 614 if ( newParentID == 0 ) { 615 menuItemNewPosition = menuItems.length - 1; 616 } 617 618 menuItem.insertAfter( menuItems.eq( menuItemNewPosition ) ).updateParentMenuItemDBId().updateParentDropdown().updateOrderDropdown(); 619 620 api.registerChange(); 621 api.refreshKeyboardAccessibility(); 622 api.refreshAdvancedAccessibility(); 623 $this.trigger( 'focus' ); 624 wp.a11y.speak( menus.parentUpdated, 'polite' ); 625 }, 626 627 /** 628 * changeMenuOrder( [OrderDropdown] ) 629 * 630 * @since 6.7.0 631 * 632 * @param {Object} orderDropdown select field 633 */ 634 changeMenuOrder : function( orderDropdown ) { 635 var menuItems = $( '#menu-to-edit li' ), 636 $this = $( orderDropdown ), 637 newOrderID = parseInt( $this.val(), 10), 638 menuItem = $this.closest( 'li.menu-item' ).first(), 639 menuItemChildren = menuItem.childMenuItems(), 640 menuItemNoChildren = menuItemChildren.length, 641 menuItemCurrentPosition = parseInt( menuItem.index(), 10 ), 642 parentItemID = menuItem.find( '.menu-item-data-parent-id' ).val(), 643 subItems = $( '.menu-item .menu-item-data-parent-id[value="' + parentItemID + '"]' ), 644 currentItemAtPosition = $(subItems[newOrderID - 1]).closest( 'li.menu-item' ); 645 646 if ( menuItemNoChildren > 0 ) { 647 menuItem = menuItem.add( menuItemChildren ); 648 } 649 650 var currentItemNoChildren = currentItemAtPosition.childMenuItems().length, 651 currentItemPosition = parseInt( currentItemAtPosition.index(), 10 ); 652 653 menuItems = $( '#menu-to-edit li' ); 654 655 var menuItemNewPosition = currentItemPosition; 656 657 if(menuItemCurrentPosition > menuItemNewPosition){ 658 menuItemNewPosition = currentItemPosition; 659 menuItem.detach().insertBefore( menuItems.eq( menuItemNewPosition ) ).updateOrderDropdown(); 660 } else { 661 menuItemNewPosition = menuItemNewPosition + currentItemNoChildren; 662 menuItem.detach().insertAfter( menuItems.eq( menuItemNewPosition ) ).updateOrderDropdown(); 663 } 664 665 api.registerChange(); 666 api.refreshKeyboardAccessibility(); 667 api.refreshAdvancedAccessibility(); 668 $this.trigger( 'focus' ); 669 wp.a11y.speak( menus.orderUpdated, 'polite' ); 670 }, 671 672 /** 673 * refreshAdvancedAccessibilityOfItem( [itemToRefresh] ) 674 * 675 * Refreshes advanced accessibility buttons for one menu item. 676 * Shows or hides buttons based on the location of the menu item. 677 * 678 * @param {Object} itemToRefresh The menu item that might need its advanced accessibility buttons refreshed 679 */ 680 refreshAdvancedAccessibilityOfItem : function( itemToRefresh ) { 681 682 // Only refresh accessibility when necessary. 683 if ( true !== $( itemToRefresh ).data( 'needs_accessibility_refresh' ) ) { 684 return; 685 } 686 687 var thisLink, thisLinkText, primaryItems, itemPosition, title, 688 parentItem, parentItemId, parentItemName, subItems, totalSubItems, 689 $this = $( itemToRefresh ), 690 menuItem = $this.closest( 'li.menu-item' ).first(), 691 depth = menuItem.menuItemDepth(), 692 isPrimaryMenuItem = ( 0 === depth ), 693 itemName = $this.closest( '.menu-item-handle' ).find( '.menu-item-title' ).text(), 694 menuItemType = $this.closest( '.menu-item-handle' ).find( '.item-controls' ).find( '.item-type' ).text(), 695 position = parseInt( menuItem.index(), 10 ), 696 prevItemDepth = ( isPrimaryMenuItem ) ? depth : parseInt( depth - 1, 10 ), 697 prevItemNameLeft = menuItem.prevAll('.menu-item-depth-' + prevItemDepth).first().find( '.menu-item-title' ).text(), 698 prevItemNameRight = menuItem.prevAll('.menu-item-depth-' + depth).first().find( '.menu-item-title' ).text(), 699 totalMenuItems = $('#menu-to-edit li').length, 700 hasSameDepthSibling = menuItem.nextAll( '.menu-item-depth-' + depth ).length; 701 702 menuItem.find( '.field-move' ).toggle( totalMenuItems > 1 ); 703 704 // Where can they move this menu item? 705 if ( 0 !== position ) { 706 thisLink = menuItem.find( '.menus-move-up' ); 707 thisLink.attr( 'aria-label', menus.moveUp ).css( 'display', 'inline' ); 708 } 709 710 if ( 0 !== position && isPrimaryMenuItem ) { 711 thisLink = menuItem.find( '.menus-move-top' ); 712 thisLink.attr( 'aria-label', menus.moveToTop ).css( 'display', 'inline' ); 713 } 714 715 if ( position + 1 !== totalMenuItems && 0 !== position ) { 716 thisLink = menuItem.find( '.menus-move-down' ); 717 thisLink.attr( 'aria-label', menus.moveDown ).css( 'display', 'inline' ); 718 } 719 720 if ( 0 === position && 0 !== hasSameDepthSibling ) { 721 thisLink = menuItem.find( '.menus-move-down' ); 722 thisLink.attr( 'aria-label', menus.moveDown ).css( 'display', 'inline' ); 723 } 724 725 if ( ! isPrimaryMenuItem ) { 726 thisLink = menuItem.find( '.menus-move-left' ), 727 thisLinkText = menus.outFrom.replace( '%s', prevItemNameLeft ); 728 thisLink.attr( 'aria-label', menus.moveOutFrom.replace( '%s', prevItemNameLeft ) ).text( thisLinkText ).css( 'display', 'inline' ); 729 } 730 731 if ( 0 !== position ) { 732 if ( menuItem.find( '.menu-item-data-parent-id' ).val() !== menuItem.prev().find( '.menu-item-data-db-id' ).val() ) { 733 thisLink = menuItem.find( '.menus-move-right' ), 734 thisLinkText = menus.under.replace( '%s', prevItemNameRight ); 735 thisLink.attr( 'aria-label', menus.moveUnder.replace( '%s', prevItemNameRight ) ).text( thisLinkText ).css( 'display', 'inline' ); 736 } 737 } 738 739 if ( isPrimaryMenuItem ) { 740 primaryItems = $( '.menu-item-depth-0' ), 741 itemPosition = primaryItems.index( menuItem ) + 1, 742 totalMenuItems = primaryItems.length, 743 // String together help text for primary menu items. 744 title = menus.menuFocus.replace( '%1$s', itemName ).replace( '%2$s', menuItemType ).replace( '%3$d', itemPosition ).replace( '%4$d', totalMenuItems ); 745 } else { 746 parentItem = menuItem.prevAll( '.menu-item-depth-' + parseInt( depth - 1, 10 ) ).first(), 747 parentItemId = parentItem.find( '.menu-item-data-db-id' ).val(), 748 parentItemName = parentItem.find( '.menu-item-title' ).text(), 749 subItems = $( '.menu-item .menu-item-data-parent-id[value="' + parentItemId + '"]' ), 750 totalSubItems = subItems.length, 751 itemPosition = $( subItems.parents('.menu-item').get().reverse() ).index( menuItem ) + 1; 752 753 // String together help text for sub menu items. 754 if ( depth < 2 ) { 755 title = menus.subMenuFocus.replace( '%1$s', itemName ).replace( '%2$s', menuItemType ).replace( '%3$d', itemPosition ).replace( '%4$d', totalSubItems ).replace( '%5$s', parentItemName ); 756 } else { 757 title = menus.subMenuMoreDepthFocus.replace( '%1$s', itemName ).replace( '%2$s', menuItemType ).replace( '%3$d', itemPosition ).replace( '%4$d', totalSubItems ).replace( '%5$s', parentItemName ).replace( '%6$d', depth ); 758 } 759 } 760 761 $this.attr( 'aria-label', title ); 762 763 // Mark this item's accessibility as refreshed. 764 $this.data( 'needs_accessibility_refresh', false ); 765 }, 766 767 /** 768 * refreshAdvancedAccessibility 769 * 770 * Hides all advanced accessibility buttons and marks them for refreshing. 771 */ 772 refreshAdvancedAccessibility : function() { 773 774 // Hide all the move buttons by default. 775 $( '.menu-item-settings .field-move .menus-move' ).hide(); 776 777 // Mark all menu items as unprocessed. 778 $( 'a.item-edit' ).data( 'needs_accessibility_refresh', true ); 779 780 // All open items have to be refreshed or they will show no links. 781 $( '.menu-item-edit-active a.item-edit' ).each( function() { 782 api.refreshAdvancedAccessibilityOfItem( this ); 783 } ); 784 }, 785 786 refreshKeyboardAccessibility : function() { 787 $( 'a.item-edit' ).off( 'focus' ).on( 'focus', function(){ 788 $(this).off( 'keydown' ).on( 'keydown', function(e){ 789 790 var arrows, 791 $this = $( this ), 792 thisItem = $this.parents( 'li.menu-item' ), 793 thisItemData = thisItem.getItemData(); 794 795 // Bail if it's not an arrow key. 796 if ( 37 != e.which && 38 != e.which && 39 != e.which && 40 != e.which ) 797 return; 798 799 // Avoid multiple keydown events. 800 $this.off('keydown'); 801 802 // Bail if there is only one menu item. 803 if ( 1 === $('#menu-to-edit li').length ) 804 return; 805 806 // If RTL, swap left/right arrows. 807 arrows = { '38': 'up', '40': 'down', '37': 'left', '39': 'right' }; 808 if ( $('body').hasClass('rtl') ) 809 arrows = { '38' : 'up', '40' : 'down', '39' : 'left', '37' : 'right' }; 810 811 switch ( arrows[e.which] ) { 812 case 'up': 813 api.moveMenuItem( $this, 'up' ); 814 break; 815 case 'down': 816 api.moveMenuItem( $this, 'down' ); 817 break; 818 case 'left': 819 api.moveMenuItem( $this, 'left' ); 820 break; 821 case 'right': 822 api.moveMenuItem( $this, 'right' ); 823 break; 824 } 825 // Put focus back on same menu item. 826 $( '#edit-' + thisItemData['menu-item-db-id'] ).trigger( 'focus' ); 827 return false; 828 }); 829 }); 830 }, 831 832 initPreviewing : function() { 833 // Update the item handle title when the navigation label is changed. 834 $( '#menu-to-edit' ).on( 'change input', '.edit-menu-item-title', function(e) { 835 var input = $( e.currentTarget ), title, titleEl; 836 title = input.val(); 837 titleEl = input.closest( '.menu-item' ).find( '.menu-item-title' ); 838 // Don't update to empty title. 839 if ( title ) { 840 titleEl.text( title ).removeClass( 'no-title' ); 841 } else { 842 titleEl.text( wp.i18n._x( '(no label)', 'missing menu item navigation label' ) ).addClass( 'no-title' ); 843 } 844 } ); 845 }, 846 847 initToggles : function() { 848 // Init postboxes. 849 postboxes.add_postbox_toggles('nav-menus'); 850 851 // Adjust columns functions for menus UI. 852 columns.useCheckboxesForHidden(); 853 columns.checked = function(field) { 854 $('.field-' + field).removeClass('hidden-field'); 855 }; 856 columns.unchecked = function(field) { 857 $('.field-' + field).addClass('hidden-field'); 858 }; 859 // Hide fields. 860 api.menuList.hideAdvancedMenuItemFields(); 861 862 $('.hide-postbox-tog').on( 'click', function () { 863 var hidden = $( '.accordion-container li.accordion-section' ).filter(':hidden').map(function() { return this.id; }).get().join(','); 864 $.post(ajaxurl, { 865 action: 'closed-postboxes', 866 hidden: hidden, 867 closedpostboxesnonce: jQuery('#closedpostboxesnonce').val(), 868 page: 'nav-menus' 869 }); 870 }); 871 }, 872 873 initSortables : function() { 874 var currentDepth = 0, originalDepth, minDepth, maxDepth, 875 prev, next, prevBottom, nextThreshold, helperHeight, transport, 876 menuEdge = api.menuList.offset().left, 877 body = $('body'), maxChildDepth, 878 menuMaxDepth = initialMenuMaxDepth(); 879 880 if( 0 !== $( '#menu-to-edit li' ).length ) 881 $( '.drag-instructions' ).show(); 882 883 // Use the right edge if RTL. 884 menuEdge += api.isRTL ? api.menuList.width() : 0; 885 886 api.menuList.sortable({ 887 handle: '.menu-item-handle', 888 placeholder: 'sortable-placeholder', 889 items: api.options.sortableItems, 890 start: function(e, ui) { 891 var height, width, parent, children, tempHolder; 892 893 // Handle placement for RTL orientation. 894 if ( api.isRTL ) 895 ui.item[0].style.right = 'auto'; 896 897 transport = ui.item.children('.menu-item-transport'); 898 899 // Set depths. currentDepth must be set before children are located. 900 originalDepth = ui.item.menuItemDepth(); 901 updateCurrentDepth(ui, originalDepth); 902 903 // Attach child elements to parent. 904 // Skip the placeholder. 905 parent = ( ui.item.next()[0] == ui.placeholder[0] ) ? ui.item.next() : ui.item; 906 children = parent.childMenuItems(); 907 transport.append( children ); 908 909 // Update the height of the placeholder to match the moving item. 910 height = transport.outerHeight(); 911 // If there are children, account for distance between top of children and parent. 912 height += ( height > 0 ) ? (ui.placeholder.css('margin-top').slice(0, -2) * 1) : 0; 913 height += ui.helper.outerHeight(); 914 helperHeight = height; 915 height -= 2; // Subtract 2 for borders. 916 ui.placeholder.height(height); 917 918 // Update the width of the placeholder to match the moving item. 919 maxChildDepth = originalDepth; 920 children.each(function(){ 921 var depth = $(this).menuItemDepth(); 922 maxChildDepth = (depth > maxChildDepth) ? depth : maxChildDepth; 923 }); 924 width = ui.helper.find('.menu-item-handle').outerWidth(); // Get original width. 925 width += api.depthToPx(maxChildDepth - originalDepth); // Account for children. 926 width -= 2; // Subtract 2 for borders. 927 ui.placeholder.width(width); 928 929 // Update the list of menu items. 930 tempHolder = ui.placeholder.next( '.menu-item' ); 931 tempHolder.css( 'margin-top', helperHeight + 'px' ); // Set the margin to absorb the placeholder. 932 ui.placeholder.detach(); // Detach or jQuery UI will think the placeholder is a menu item. 933 $(this).sortable( 'refresh' ); // The children aren't sortable. We should let jQuery UI know. 934 ui.item.after( ui.placeholder ); // Reattach the placeholder. 935 tempHolder.css('margin-top', 0); // Reset the margin. 936 937 // Now that the element is complete, we can update... 938 updateSharedVars(ui); 939 }, 940 stop: function(e, ui) { 941 var children, subMenuTitle, 942 depthChange = currentDepth - originalDepth; 943 944 // Return child elements to the list. 945 children = transport.children().insertAfter(ui.item); 946 947 // Add "sub menu" description. 948 subMenuTitle = ui.item.find( '.item-title .is-submenu' ); 949 if ( 0 < currentDepth ) 950 subMenuTitle.show(); 951 else 952 subMenuTitle.hide(); 953 954 // Update depth classes. 955 if ( 0 !== depthChange ) { 956 ui.item.updateDepthClass( currentDepth ); 957 children.shiftDepthClass( depthChange ); 958 updateMenuMaxDepth( depthChange ); 959 } 960 // Register a change. 961 api.registerChange(); 962 // Update the item data. 963 ui.item.updateParentMenuItemDBId(); 964 965 // Address sortable's incorrectly-calculated top in Opera. 966 ui.item[0].style.top = 0; 967 968 // Handle drop placement for rtl orientation. 969 if ( api.isRTL ) { 970 ui.item[0].style.left = 'auto'; 971 ui.item[0].style.right = 0; 972 } 973 974 api.refreshKeyboardAccessibility(); 975 api.refreshAdvancedAccessibility(); 976 ui.item.updateParentDropdown(); 977 ui.item.updateOrderDropdown(); 978 api.refreshAdvancedAccessibilityOfItem( ui.item.find( 'a.item-edit' ) ); 979 }, 980 change: function(e, ui) { 981 // Make sure the placeholder is inside the menu. 982 // Otherwise fix it, or we're in trouble. 983 if( ! ui.placeholder.parent().hasClass('menu') ) 984 (prev.length) ? prev.after( ui.placeholder ) : api.menuList.prepend( ui.placeholder ); 985 986 updateSharedVars(ui); 987 }, 988 sort: function(e, ui) { 989 var offset = ui.helper.offset(), 990 edge = api.isRTL ? offset.left + ui.helper.width() : offset.left, 991 depth = api.negateIfRTL * api.pxToDepth( edge - menuEdge ); 992 993 /* 994 * Check and correct if depth is not within range. 995 * Also, if the dragged element is dragged upwards over an item, 996 * shift the placeholder to a child position. 997 */ 998 if ( depth > maxDepth || offset.top < ( prevBottom - api.options.targetTolerance ) ) { 999 depth = maxDepth; 1000 } else if ( depth < minDepth ) { 1001 depth = minDepth; 1002 } 1003 1004 if( depth != currentDepth ) 1005 updateCurrentDepth(ui, depth); 1006 1007 // If we overlap the next element, manually shift downwards. 1008 if( nextThreshold && offset.top + helperHeight > nextThreshold ) { 1009 next.after( ui.placeholder ); 1010 updateSharedVars( ui ); 1011 $( this ).sortable( 'refreshPositions' ); 1012 } 1013 } 1014 }); 1015 1016 /** 1017 * Updates the shared variables used to determine the depth of the menu item being moved. 1018 * 1019 * @param {Object} ui The jQuery UI object for the menu item being moved. 1020 */ 1021 function updateSharedVars(ui) { 1022 var depth; 1023 1024 prev = ui.placeholder.prev( '.menu-item' ); 1025 next = ui.placeholder.next( '.menu-item' ); 1026 1027 // Make sure we don't select the moving item. 1028 if( prev[0] == ui.item[0] ) prev = prev.prev( '.menu-item' ); 1029 if( next[0] == ui.item[0] ) next = next.next( '.menu-item' ); 1030 1031 prevBottom = (prev.length) ? prev.offset().top + prev.height() : 0; 1032 nextThreshold = (next.length) ? next.offset().top + next.height() / 3 : 0; 1033 minDepth = (next.length) ? next.menuItemDepth() : 0; 1034 1035 if( prev.length ) 1036 maxDepth = ( (depth = prev.menuItemDepth() + 1) > api.options.globalMaxDepth ) ? api.options.globalMaxDepth : depth; 1037 else 1038 maxDepth = 0; 1039 } 1040 1041 /** 1042 * Updates the current depth of the menu item being moved. 1043 * 1044 * @param {Object} ui The jQuery UI object for the menu item being moved. 1045 * @param {number} depth The new depth of the menu item being moved. 1046 * @return {void} 1047 */ 1048 function updateCurrentDepth(ui, depth) { 1049 ui.placeholder.updateDepthClass( depth, currentDepth ); 1050 currentDepth = depth; 1051 } 1052 1053 /** 1054 * Determines the initial menu max depth class on the body element. 1055 * 1056 * @return {number} The initial menu max depth. 1057 */ 1058 function initialMenuMaxDepth() { 1059 if( ! body[0].className ) return 0; 1060 var match = body[0].className.match(/menu-max-depth-(\d+)/); 1061 return match && match[1] ? parseInt( match[1], 10 ) : 0; 1062 } 1063 1064 /** 1065 * Updates the menu max depth class on the body element. 1066 * 1067 * @param {number} depthChange The change in depth of the menu item being moved. 1068 * @return {void} 1069 */ 1070 function updateMenuMaxDepth( depthChange ) { 1071 var depth, newDepth = menuMaxDepth; 1072 if ( depthChange === 0 ) { 1073 return; 1074 } else if ( depthChange > 0 ) { 1075 depth = maxChildDepth + depthChange; 1076 if( depth > menuMaxDepth ) 1077 newDepth = depth; 1078 } else if ( depthChange < 0 && maxChildDepth == menuMaxDepth ) { 1079 while( ! $('.menu-item-depth-' + newDepth, api.menuList).length && newDepth > 0 ) 1080 newDepth--; 1081 } 1082 // Update the depth class. 1083 body.removeClass( 'menu-max-depth-' + menuMaxDepth ).addClass( 'menu-max-depth-' + newDepth ); 1084 menuMaxDepth = newDepth; 1085 } 1086 }, 1087 1088 initManageLocations : function () { 1089 $('#menu-locations-wrap form').on( 'submit', function(){ 1090 window.onbeforeunload = null; 1091 }); 1092 $('.menu-location-menus select').on('change', function () { 1093 var editLink = $(this).closest('tr').find('.locations-edit-menu-link'); 1094 if ($(this).find('option:selected').data('orig')) 1095 editLink.show(); 1096 else 1097 editLink.hide(); 1098 }); 1099 }, 1100 1101 attachMenuEditListeners : function() { 1102 var that = this; 1103 $('#update-nav-menu').on('click', function(e) { 1104 if ( e.target && e.target.className ) { 1105 if ( -1 != e.target.className.indexOf('item-edit') ) { 1106 return that.eventOnClickEditLink(e.target); 1107 } else if ( -1 != e.target.className.indexOf('menu-save') ) { 1108 return that.eventOnClickMenuSave(e.target); 1109 } else if ( -1 != e.target.className.indexOf('menu-delete') ) { 1110 return that.eventOnClickMenuDelete(e.target); 1111 } else if ( -1 != e.target.className.indexOf('item-delete') ) { 1112 return that.eventOnClickMenuItemDelete(e.target); 1113 } else if ( -1 != e.target.className.indexOf('item-cancel') ) { 1114 return that.eventOnClickCancelLink(e.target); 1115 } 1116 } 1117 }); 1118 1119 $( '#menu-name' ).on( 'input', _.debounce( function () { 1120 var menuName = $( document.getElementById( 'menu-name' ) ), 1121 menuNameVal = menuName.val(); 1122 1123 if ( ! menuNameVal || ! menuNameVal.replace( /\s+/, '' ) ) { 1124 // Add warning for invalid menu name. 1125 menuName.parent().addClass( 'form-invalid' ); 1126 } else { 1127 // Remove warning for valid menu name. 1128 menuName.parent().removeClass( 'form-invalid' ); 1129 } 1130 }, 500 ) ); 1131 1132 $('#add-custom-links input[type="text"]').on( 'keypress', function(e){ 1133 $( '#customlinkdiv' ).removeClass( 'form-invalid' ); 1134 $( '#custom-menu-item-url' ).removeAttr( 'aria-invalid' ).removeAttr( 'aria-describedby' ); 1135 $( '#custom-url-error' ).hide(); 1136 1137 if ( e.keyCode === 13 ) { 1138 e.preventDefault(); 1139 $( '#submit-customlinkdiv' ).trigger( 'click' ); 1140 } 1141 }); 1142 1143 $( '#submit-customlinkdiv' ).on( 'click', function (e) { 1144 var urlInput = $( '#custom-menu-item-url' ), 1145 url = urlInput.val().trim(), 1146 errorMessage = $( '#custom-url-error' ), 1147 urlWrap = $( '#menu-item-url-wrap' ), 1148 urlRegex; 1149 1150 // Hide the error message initially 1151 errorMessage.hide(); 1152 urlWrap.removeClass( 'has-error' ); 1153 1154 /* 1155 * Allow URLs including: 1156 * - http://example.com/ 1157 * - //example.com 1158 * - /directory/ 1159 * - ?query-param 1160 * - #target 1161 * - mailto:foo@example.com 1162 * 1163 * Any further validation will be handled on the server when the setting is attempted to be saved, 1164 * so this pattern does not need to be complete. 1165 */ 1166 urlRegex = /^((\w+:)?\/\/\w.*|\w+:(?!\/\/$)|\/|\?|#)/; 1167 if ( ! urlRegex.test( url ) ) { 1168 e.preventDefault(); 1169 urlInput.addClass( 'form-invalid' ) 1170 .attr( 'aria-invalid', 'true' ) 1171 .attr( 'aria-describedby', 'custom-url-error' ); 1172 1173 errorMessage.show(); 1174 var errorText = errorMessage.text(); 1175 urlWrap.addClass( 'has-error' ); 1176 // Announce error message via screen reader 1177 wp.a11y.speak( errorText, 'assertive' ); 1178 } 1179 }); 1180 }, 1181 1182 /** 1183 * Handle toggling bulk selection checkboxes for menu items. 1184 * 1185 * @since 5.8.0 1186 */ 1187 attachBulkSelectButtonListeners : function() { 1188 var that = this; 1189 1190 $( '.bulk-select-switcher' ).on( 'change', function() { 1191 if ( this.checked ) { 1192 $( '.bulk-select-switcher' ).prop( 'checked', true ); 1193 that.enableBulkSelection(); 1194 } else { 1195 $( '.bulk-select-switcher' ).prop( 'checked', false ); 1196 that.disableBulkSelection(); 1197 } 1198 }); 1199 }, 1200 1201 /** 1202 * Enable bulk selection checkboxes for menu items. 1203 * 1204 * @since 5.8.0 1205 */ 1206 enableBulkSelection : function() { 1207 var checkbox = $( '#menu-to-edit .menu-item-checkbox' ); 1208 1209 $( '#menu-to-edit' ).addClass( 'bulk-selection' ); 1210 $( '#nav-menu-bulk-actions-top' ).addClass( 'bulk-selection' ); 1211 $( '#nav-menu-bulk-actions-bottom' ).addClass( 'bulk-selection' ); 1212 1213 $.each( checkbox, function() { 1214 $(this).prop( 'disabled', false ); 1215 }); 1216 }, 1217 1218 /** 1219 * Disable bulk selection checkboxes for menu items. 1220 * 1221 * @since 5.8.0 1222 */ 1223 disableBulkSelection : function() { 1224 var checkbox = $( '#menu-to-edit .menu-item-checkbox' ); 1225 1226 $( '#menu-to-edit' ).removeClass( 'bulk-selection' ); 1227 $( '#nav-menu-bulk-actions-top' ).removeClass( 'bulk-selection' ); 1228 $( '#nav-menu-bulk-actions-bottom' ).removeClass( 'bulk-selection' ); 1229 1230 if ( $( '.menu-items-delete' ).is( '[aria-describedby="pending-menu-items-to-delete"]' ) ) { 1231 $( '.menu-items-delete' ).removeAttr( 'aria-describedby' ); 1232 } 1233 1234 $.each( checkbox, function() { 1235 $(this).prop( 'disabled', true ).prop( 'checked', false ); 1236 }); 1237 1238 $( '.menu-items-delete' ).addClass( 'disabled' ); 1239 $( '#pending-menu-items-to-delete ul' ).empty(); 1240 }, 1241 1242 /** 1243 * Listen for state changes on bulk action checkboxes. 1244 * 1245 * @since 5.8.0 1246 */ 1247 attachMenuCheckBoxListeners : function() { 1248 var that = this; 1249 1250 $( '#menu-to-edit' ).on( 'change', '.menu-item-checkbox', function() { 1251 that.setRemoveSelectedButtonStatus(); 1252 }); 1253 }, 1254 1255 /** 1256 * Create delete button to remove menu items from collection. 1257 * 1258 * @since 5.8.0 1259 */ 1260 attachMenuItemDeleteButton : function() { 1261 var that = this; 1262 1263 $( document ).on( 'click', '.menu-items-delete', function( e ) { 1264 var itemsPendingDeletion, itemsPendingDeletionList, deletionSpeech; 1265 1266 e.preventDefault(); 1267 1268 if ( ! $(this).hasClass( 'disabled' ) ) { 1269 $.each( $( '.menu-item-checkbox:checked' ), function( index, element ) { 1270 $( element ).parents( 'li' ).find( 'a.item-delete' ).trigger( 'click' ); 1271 }); 1272 1273 $( '.menu-items-delete' ).addClass( 'disabled' ); 1274 $( '.bulk-select-switcher' ).prop( 'checked', false ); 1275 1276 itemsPendingDeletion = ''; 1277 itemsPendingDeletionList = $( '#pending-menu-items-to-delete ul li' ); 1278 1279 $.each( itemsPendingDeletionList, function( index, element ) { 1280 var itemName = $( element ).find( '.pending-menu-item-name' ).text(); 1281 var itemSpeech = menus.menuItemDeletion.replace( '%s', itemName ); 1282 1283 itemsPendingDeletion += itemSpeech; 1284 if ( ( index + 1 ) < itemsPendingDeletionList.length ) { 1285 itemsPendingDeletion += ', '; 1286 } 1287 }); 1288 1289 deletionSpeech = menus.itemsDeleted.replace( '%s', itemsPendingDeletion ); 1290 wp.a11y.speak( deletionSpeech, 'polite' ); 1291 that.disableBulkSelection(); 1292 $( '#menu-to-edit' ).updateParentDropdown(); 1293 $( '#menu-to-edit' ).updateOrderDropdown(); 1294 } 1295 }); 1296 }, 1297 1298 /** 1299 * List menu items awaiting deletion. 1300 * 1301 * @since 5.8.0 1302 */ 1303 attachPendingMenuItemsListForDeletion : function() { 1304 $( '#post-body-content' ).on( 'change', '.menu-item-checkbox', function() { 1305 var menuItemName, menuItemType, menuItemID, listedMenuItem; 1306 1307 if ( ! $( '.menu-items-delete' ).is( '[aria-describedby="pending-menu-items-to-delete"]' ) ) { 1308 $( '.menu-items-delete' ).attr( 'aria-describedby', 'pending-menu-items-to-delete' ); 1309 } 1310 1311 menuItemName = $(this).next().text(); 1312 menuItemType = $(this).parent().next( '.item-controls' ).find( '.item-type' ).text(); 1313 menuItemID = $(this).attr( 'data-menu-item-id' ); 1314 1315 listedMenuItem = $( '#pending-menu-items-to-delete ul' ).find( '[data-menu-item-id=' + menuItemID + ']' ); 1316 if ( listedMenuItem.length > 0 ) { 1317 listedMenuItem.remove(); 1318 } 1319 1320 if ( this.checked === true ) { 1321 const $li = $( '<li>', { 'data-menu-item-id': menuItemID } ); 1322 $li.append( $( '<span>', { 1323 'class': 'pending-menu-item-name', 1324 text: menuItemName 1325 } ) ); 1326 $li.append( ' ' ); 1327 $li.append( $( '<span>', { 1328 'class': 'pending-menu-item-type', 1329 text: '(' + menuItemType + ')', 1330 } ) ); 1331 $li.append( $( '<span>', { 'class': 'separator' } ) ); 1332 $( '#pending-menu-items-to-delete ul' ).append( $li ); 1333 } 1334 1335 $( '#pending-menu-items-to-delete li .separator' ).html( ', ' ); 1336 $( '#pending-menu-items-to-delete li .separator' ).last().html( '.' ); 1337 }); 1338 }, 1339 1340 /** 1341 * Set status of bulk delete checkbox. 1342 * 1343 * @since 5.8.0 1344 */ 1345 setBulkDeleteCheckboxStatus : function() { 1346 var that = this; 1347 var checkbox = $( '#menu-to-edit .menu-item-checkbox' ); 1348 1349 $.each( checkbox, function() { 1350 if ( $(this).prop( 'disabled' ) ) { 1351 $(this).prop( 'disabled', false ); 1352 } else { 1353 $(this).prop( 'disabled', true ); 1354 } 1355 1356 if ( $(this).is( ':checked' ) ) { 1357 $(this).prop( 'checked', false ); 1358 } 1359 }); 1360 1361 that.setRemoveSelectedButtonStatus(); 1362 }, 1363 1364 /** 1365 * Set status of menu items removal button. 1366 * 1367 * @since 5.8.0 1368 */ 1369 setRemoveSelectedButtonStatus : function() { 1370 var button = $( '.menu-items-delete' ); 1371 1372 if ( $( '.menu-item-checkbox:checked' ).length > 0 ) { 1373 button.removeClass( 'disabled' ); 1374 } else { 1375 button.addClass( 'disabled' ); 1376 } 1377 }, 1378 1379 attachMenuSaveSubmitListeners : function() { 1380 /* 1381 * When a navigation menu is saved, store a JSON representation of all form data 1382 * in a single input to avoid PHP `max_input_vars` limitations. See #14134. 1383 */ 1384 $( '#update-nav-menu' ).on( 'submit', function() { 1385 var navMenuData = $( '#update-nav-menu' ).serializeArray(); 1386 $( '[name="nav-menu-data"]' ).val( JSON.stringify( navMenuData ) ); 1387 }); 1388 }, 1389 1390 attachThemeLocationsListeners : function() { 1391 var loc = $('#nav-menu-theme-locations'), params = {}; 1392 params.action = 'menu-locations-save'; 1393 params['menu-settings-column-nonce'] = $('#menu-settings-column-nonce').val(); 1394 loc.find('input[type="submit"]').on( 'click', function() { 1395 loc.find('select').each(function() { 1396 params[this.name] = $(this).val(); 1397 }); 1398 loc.find( '.spinner' ).addClass( 'is-active' ); 1399 $.post( ajaxurl, params, function() { 1400 loc.find( '.spinner' ).removeClass( 'is-active' ); 1401 }); 1402 return false; 1403 }); 1404 }, 1405 1406 attachQuickSearchListeners : function() { 1407 var searchTimer; 1408 1409 // Prevent form submission. 1410 $( '#nav-menu-meta' ).on( 'submit', function( event ) { 1411 event.preventDefault(); 1412 }); 1413 1414 $( '#nav-menu-meta' ).on( 'input', '.quick-search', function() { 1415 var $this = $( this ); 1416 1417 $this.attr( 'autocomplete', 'off' ); 1418 1419 if ( searchTimer ) { 1420 clearTimeout( searchTimer ); 1421 } 1422 1423 searchTimer = setTimeout( function() { 1424 api.updateQuickSearchResults( $this ); 1425 }, 500 ); 1426 }).on( 'blur', '.quick-search', function() { 1427 api.lastSearch = ''; 1428 }); 1429 }, 1430 1431 updateQuickSearchResults : function(input) { 1432 var panel, params, 1433 minSearchLength = 1, 1434 q = input.val(), 1435 pageSearchChecklist = $( '#page-search-checklist' ); 1436 1437 /* 1438 * Avoid a new Ajax search when the pressed key (e.g. arrows) 1439 * doesn't change the searched term. 1440 */ 1441 if ( api.lastSearch == q ) { 1442 return; 1443 } 1444 1445 /* 1446 * Reset results when search is less than or equal to 1447 * minimum characters for searched term. 1448 */ 1449 if ( q.length <= minSearchLength ) { 1450 pageSearchChecklist.empty(); 1451 wp.a11y.speak( wp.i18n.__( 'Search results cleared' ) ); 1452 return; 1453 } 1454 1455 api.lastSearch = q; 1456 1457 panel = input.parents('.tabs-panel'); 1458 params = { 1459 'action': 'menu-quick-search', 1460 'response-format': 'markup', 1461 'menu': $('#menu').val(), 1462 'menu-settings-column-nonce': $('#menu-settings-column-nonce').val(), 1463 'q': q, 1464 'type': input.attr('name') 1465 }; 1466 1467 $( '.spinner', panel ).addClass( 'is-active' ); 1468 1469 $.post( ajaxurl, params, function(menuMarkup) { 1470 api.processQuickSearchQueryResponse(menuMarkup, params, panel); 1471 }); 1472 }, 1473 1474 addCustomLink : function( processMethod ) { 1475 var url = $('#custom-menu-item-url').val().toString(), 1476 label = $('#custom-menu-item-name').val(), 1477 urlRegex; 1478 1479 if ( '' !== url ) { 1480 url = url.trim(); 1481 } 1482 1483 processMethod = processMethod || api.addMenuItemToBottom; 1484 1485 /* 1486 * Allow URLs including: 1487 * - http://example.com/ 1488 * - //example.com 1489 * - /directory/ 1490 * - ?query-param 1491 * - #target 1492 * - mailto:foo@example.com 1493 * 1494 * Any further validation will be handled on the server when the setting is attempted to be saved, 1495 * so this pattern does not need to be complete. 1496 */ 1497 urlRegex = /^((\w+:)?\/\/\w.*|\w+:(?!\/\/$)|\/|\?|#)/; 1498 if ( ! urlRegex.test( url ) ) { 1499 $('#customlinkdiv').addClass('form-invalid'); 1500 return false; 1501 } 1502 1503 // Show the Ajax spinner. 1504 $( '.customlinkdiv .spinner' ).addClass( 'is-active' ); 1505 this.addLinkToMenu( url, label, processMethod, function() { 1506 // Remove the Ajax spinner. 1507 $( '.customlinkdiv .spinner' ).removeClass( 'is-active' ); 1508 // Set custom link form back to defaults. 1509 $('#custom-menu-item-name').val('').trigger( 'blur' ); 1510 $( '#custom-menu-item-url' ).val( '' ).attr( 'placeholder', 'https://' ); 1511 }); 1512 }, 1513 1514 addLinkToMenu : function(url, label, processMethod, callback) { 1515 processMethod = processMethod || api.addMenuItemToBottom; 1516 callback = callback || function(){}; 1517 1518 api.addItemToMenu({ 1519 '-1': { 1520 'menu-item-type': 'custom', 1521 'menu-item-url': url, 1522 'menu-item-title': label 1523 } 1524 }, processMethod, callback); 1525 }, 1526 1527 addItemToMenu : function(menuItem, processMethod, callback) { 1528 var menu = $('#menu').val(), 1529 nonce = $('#menu-settings-column-nonce').val(), 1530 params; 1531 1532 processMethod = processMethod || function(){}; 1533 callback = callback || function(){}; 1534 1535 params = { 1536 'action': 'add-menu-item', 1537 'menu': menu, 1538 'menu-settings-column-nonce': nonce, 1539 'menu-item': menuItem 1540 }; 1541 1542 $.post( ajaxurl, params, function(menuMarkup) { 1543 var ins = $('#menu-instructions'); 1544 1545 menuMarkup = menuMarkup || ''; 1546 menuMarkup = menuMarkup.toString().trim(); // Trim leading whitespaces. 1547 processMethod(menuMarkup, params); 1548 1549 // Make it stand out a bit more visually, by adding a fadeIn. 1550 $( 'li.pending' ).hide().fadeIn('slow'); 1551 $( '.drag-instructions' ).show(); 1552 if( ! ins.hasClass( 'menu-instructions-inactive' ) && ins.siblings().length ) 1553 ins.addClass( 'menu-instructions-inactive' ); 1554 1555 callback(); 1556 }); 1557 }, 1558 1559 /** 1560 * Process the add menu item request response into menu list item. Appends to menu. 1561 * 1562 * @param {string} menuMarkup The text server response of menu item markup. 1563 * 1564 * @fires document#menu-item-added Passes menuMarkup as a jQuery object. 1565 */ 1566 addMenuItemToBottom : function( menuMarkup ) { 1567 var $menuMarkup = $( menuMarkup ); 1568 $menuMarkup.hideAdvancedMenuItemFields().appendTo( api.targetList ); 1569 api.refreshKeyboardAccessibility(); 1570 api.refreshAdvancedAccessibility(); 1571 wp.a11y.speak( menus.itemAdded ); 1572 $( document ).trigger( 'menu-item-added', [ $menuMarkup ] ); 1573 }, 1574 1575 /** 1576 * Process the add menu item request response into menu list item. Prepends to menu. 1577 * 1578 * @param {string} menuMarkup The text server response of menu item markup. 1579 * 1580 * @fires document#menu-item-added Passes menuMarkup as a jQuery object. 1581 */ 1582 addMenuItemToTop : function( menuMarkup ) { 1583 var $menuMarkup = $( menuMarkup ); 1584 $menuMarkup.hideAdvancedMenuItemFields().prependTo( api.targetList ); 1585 api.refreshKeyboardAccessibility(); 1586 api.refreshAdvancedAccessibility(); 1587 wp.a11y.speak( menus.itemAdded ); 1588 $( document ).trigger( 'menu-item-added', [ $menuMarkup ] ); 1589 }, 1590 1591 attachUnsavedChangesListener : function() { 1592 $('#menu-management input, #menu-management select, #menu-management, #menu-management textarea, .menu-location-menus select').on( 'change', function(){ 1593 api.registerChange(); 1594 }); 1595 1596 if ( 0 !== $('#menu-to-edit').length || 0 !== $('.menu-location-menus select').length ) { 1597 window.onbeforeunload = function(){ 1598 if ( api.menusChanged ) 1599 return wp.i18n.__( 'The changes you made will be lost if you navigate away from this page.' ); 1600 }; 1601 } else { 1602 // Make the post boxes read-only, as they can't be used yet. 1603 $( '#menu-settings-column' ).find( 'input,select' ).end().find( 'a' ).attr( 'href', '#' ).off( 'click' ); 1604 } 1605 }, 1606 1607 registerChange : function() { 1608 api.menusChanged = true; 1609 }, 1610 1611 attachTabsPanelListeners : function() { 1612 $('#menu-settings-column').on('click', function(e) { 1613 var selectAreaMatch, selectAll, panelId, wrapper, items, 1614 target = $(e.target); 1615 1616 if ( target.hasClass('nav-tab-link') ) { 1617 1618 panelId = target.data( 'type' ); 1619 1620 wrapper = target.parents('.accordion-section-content').first(); 1621 1622 // Upon changing tabs, we want to uncheck all checkboxes. 1623 $( 'input', wrapper ).prop( 'checked', false ); 1624 1625 $('.tabs-panel-active', wrapper).removeClass('tabs-panel-active').addClass('tabs-panel-inactive'); 1626 $('#' + panelId, wrapper).removeClass('tabs-panel-inactive').addClass('tabs-panel-active'); 1627 1628 $('.tabs', wrapper).removeClass('tabs'); 1629 target.parent().addClass('tabs'); 1630 1631 // Select the search bar. 1632 $('.quick-search', wrapper).trigger( 'focus' ); 1633 1634 // Hide controls in the search tab if no items found. 1635 if ( ! wrapper.find( '.tabs-panel-active .menu-item-title' ).length ) { 1636 wrapper.addClass( 'has-no-menu-item' ); 1637 } else { 1638 wrapper.removeClass( 'has-no-menu-item' ); 1639 } 1640 1641 e.preventDefault(); 1642 } else if ( target.hasClass( 'select-all' ) ) { 1643 selectAreaMatch = target.closest( '.button-controls' ).data( 'items-type' ); 1644 if ( selectAreaMatch ) { 1645 items = $( '#' + selectAreaMatch + ' .tabs-panel-active .menu-item-title input' ); 1646 1647 if ( items.length === items.filter( ':checked' ).length && ! target.is( ':checked' ) ) { 1648 items.prop( 'checked', false ); 1649 } else if ( target.is( ':checked' ) ) { 1650 items.prop( 'checked', true ); 1651 } 1652 } 1653 } else if ( target.hasClass( 'menu-item-checkbox' ) ) { 1654 selectAreaMatch = target.closest( '.tabs-panel-active' ).parent().attr( 'id' ); 1655 if ( selectAreaMatch ) { 1656 items = $( '#' + selectAreaMatch + ' .tabs-panel-active .menu-item-title input' ); 1657 selectAll = $( '.button-controls[data-items-type="' + selectAreaMatch + '"] .select-all' ); 1658 1659 if ( items.length === items.filter( ':checked' ).length && ! selectAll.is( ':checked' ) ) { 1660 selectAll.prop( 'checked', true ); 1661 } else if ( selectAll.is( ':checked' ) ) { 1662 selectAll.prop( 'checked', false ); 1663 } 1664 } 1665 } else if ( target.hasClass('submit-add-to-menu') ) { 1666 api.registerChange(); 1667 1668 if ( e.target.id && 'submit-customlinkdiv' == e.target.id ) 1669 api.addCustomLink( api.addMenuItemToBottom ); 1670 else if ( e.target.id && -1 != e.target.id.indexOf('submit-') ) 1671 $('#' + e.target.id.replace(/submit-/, '')).addSelectedToMenu( api.addMenuItemToBottom ); 1672 return false; 1673 } 1674 }); 1675 1676 /* 1677 * Delegate the `click` event and attach it just to the pagination 1678 * links thus excluding the current page `<span>`. See ticket #35577. 1679 */ 1680 $( '#nav-menu-meta' ).on( 'click', 'a.page-numbers', function() { 1681 var $container = $( this ).closest( '.inside' ); 1682 1683 $.post( ajaxurl, this.href.replace( /.*\?/, '' ).replace( /action=([^&]*)/, '' ) + '&action=menu-get-metabox', 1684 function( resp ) { 1685 var metaBoxData = JSON.parse( resp ), 1686 toReplace; 1687 1688 if ( -1 === resp.indexOf( 'replace-id' ) ) { 1689 return; 1690 } 1691 1692 // Get the post type menu meta box to update. 1693 toReplace = document.getElementById( metaBoxData['replace-id'] ); 1694 1695 if ( ! metaBoxData.markup || ! toReplace ) { 1696 return; 1697 } 1698 1699 // Update the post type menu meta box with new content from the response. 1700 $container.html( metaBoxData.markup ); 1701 } 1702 ); 1703 1704 return false; 1705 }); 1706 }, 1707 1708 eventOnClickEditLink : function(clickedEl) { 1709 var settings, item, 1710 matchedSection = /#(.*)$/.exec(clickedEl.href); 1711 1712 if ( matchedSection && matchedSection[1] ) { 1713 settings = $('#'+matchedSection[1]); 1714 item = settings.parent(); 1715 if( 0 !== item.length ) { 1716 if( item.hasClass('menu-item-edit-inactive') ) { 1717 if( ! settings.data('menu-item-data') ) { 1718 settings.data( 'menu-item-data', settings.getItemData() ); 1719 } 1720 settings.slideDown('fast'); 1721 item.removeClass('menu-item-edit-inactive') 1722 .addClass('menu-item-edit-active'); 1723 } else { 1724 settings.slideUp('fast'); 1725 item.removeClass('menu-item-edit-active') 1726 .addClass('menu-item-edit-inactive'); 1727 } 1728 return false; 1729 } 1730 } 1731 }, 1732 1733 eventOnClickCancelLink : function(clickedEl) { 1734 var settings = $( clickedEl ).closest( '.menu-item-settings' ), 1735 thisMenuItem = $( clickedEl ).closest( '.menu-item' ); 1736 1737 thisMenuItem.removeClass( 'menu-item-edit-active' ).addClass( 'menu-item-edit-inactive' ); 1738 settings.setItemData( settings.data( 'menu-item-data' ) ).hide(); 1739 // Restore the title of the currently active/expanded menu item. 1740 thisMenuItem.find( '.menu-item-title' ).text( settings.data( 'menu-item-data' )['menu-item-title'] ); 1741 1742 return false; 1743 }, 1744 1745 eventOnClickMenuSave : function() { 1746 var menuName = $('#menu-name'), 1747 menuNameVal = menuName.val(); 1748 1749 // Cancel and warn if invalid menu name. 1750 if ( ! menuNameVal || ! menuNameVal.replace( /\s+/, '' ) ) { 1751 menuName.parent().addClass( 'form-invalid' ); 1752 return false; 1753 } 1754 // Copy menu theme locations. 1755 // Note: This appears to be dead code since #nav-menu-theme-locations no longer exists, perhaps removed in r32842. 1756 var $updateNavMenu = $('#update-nav-menu'); 1757 $('#nav-menu-theme-locations select').each(function() { 1758 $updateNavMenu.append( 1759 $( '<input>', { 1760 type: 'hidden', 1761 name: this.name, 1762 value: $( this ).val(), 1763 } ) 1764 ); 1765 }); 1766 // Update menu item position data. 1767 api.menuList.find('.menu-item-data-position').val( function(index) { return index + 1; } ); 1768 window.onbeforeunload = null; 1769 1770 return true; 1771 }, 1772 1773 eventOnClickMenuDelete : function() { 1774 // Delete warning AYS. 1775 if ( window.confirm( wp.i18n.__( 'You are about to permanently delete this menu.\n\'Cancel\' to stop, \'OK\' to delete.' ) ) ) { 1776 window.onbeforeunload = null; 1777 return true; 1778 } 1779 return false; 1780 }, 1781 1782 eventOnClickMenuItemDelete : function(clickedEl) { 1783 var itemID = parseInt(clickedEl.id.replace('delete-', ''), 10); 1784 1785 api.removeMenuItem( $('#menu-item-' + itemID) ); 1786 api.registerChange(); 1787 return false; 1788 }, 1789 1790 /** 1791 * Process the quick search response into a search result 1792 * 1793 * @param {string} resp The server response to the query. 1794 * @param {Object} req The request arguments. 1795 * @param {jQuery} panel The tabs panel we're searching in. 1796 */ 1797 processQuickSearchQueryResponse : function(resp, req, panel) { 1798 var matched, newID, 1799 takenIDs = {}, 1800 form = document.getElementById('nav-menu-meta'), 1801 pattern = /menu-item[(\[^]\]*/, 1802 $items = $('<div>').html(resp).find('li'), 1803 wrapper = panel.closest( '.accordion-section-content' ), 1804 selectAll = wrapper.find( '.button-controls .select-all' ), 1805 $item; 1806 1807 if( ! $items.length ) { 1808 let noResults = wp.i18n.__( 'No results found.' ); 1809 const li = $( '<li>' ); 1810 const p = $( '<p>', { text: noResults } ); 1811 li.append( p ); 1812 $('.categorychecklist', panel).empty().append( li ); 1813 $( '.spinner', panel ).removeClass( 'is-active' ); 1814 wrapper.addClass( 'has-no-menu-item' ); 1815 wp.a11y.speak( noResults, 'assertive' ); 1816 return; 1817 } 1818 1819 $items.each(function(){ 1820 $item = $(this); 1821 1822 // Make a unique DB ID number. 1823 matched = pattern.exec($item.html()); 1824 1825 if ( matched && matched[1] ) { 1826 newID = matched[1]; 1827 while( form.elements['menu-item[' + newID + '][menu-item-type]'] || takenIDs[ newID ] ) { 1828 newID--; 1829 } 1830 1831 takenIDs[newID] = true; 1832 if ( newID != matched[1] ) { 1833 $item.html( $item.html().replace(new RegExp( 1834 'menu-item\\[' + matched[1] + '\\]', 'g'), 1835 'menu-item[' + newID + ']' 1836 ) ); 1837 } 1838 } 1839 }); 1840 1841 $('.categorychecklist', panel).html( $items ); 1842 wp.a11y.speak( wp.i18n.sprintf( wp.i18n.__( '%d Search Results Found' ), $items.length ), 'assertive' ); 1843 $( '.spinner', panel ).removeClass( 'is-active' ); 1844 wrapper.removeClass( 'has-no-menu-item' ); 1845 1846 if ( selectAll.is( ':checked' ) ) { 1847 selectAll.prop( 'checked', false ); 1848 } 1849 }, 1850 1851 /** 1852 * Remove a menu item. 1853 * 1854 * @param {Object} el The element to be removed as a jQuery object. 1855 * 1856 * @fires document#menu-removing-item Passes the element to be removed. 1857 */ 1858 removeMenuItem : function(el) { 1859 var children = el.childMenuItems(); 1860 1861 $( document ).trigger( 'menu-removing-item', [ el ] ); 1862 el.addClass('deleting').animate({ 1863 opacity : 0, 1864 height: 0 1865 }, 350, function() { 1866 var ins = $('#menu-instructions'); 1867 el.remove(); 1868 children.shiftDepthClass( -1 ).updateParentMenuItemDBId(); 1869 if ( 0 === $( '#menu-to-edit li' ).length ) { 1870 $( '.drag-instructions' ).hide(); 1871 ins.removeClass( 'menu-instructions-inactive' ); 1872 } 1873 api.refreshAdvancedAccessibility(); 1874 wp.a11y.speak( menus.itemRemoved ); 1875 $( '#menu-to-edit' ).updateParentDropdown(); 1876 $( '#menu-to-edit' ).updateOrderDropdown(); 1877 }); 1878 }, 1879 1880 depthToPx : function(depth) { 1881 return depth * api.options.menuItemDepthPerLevel; 1882 }, 1883 1884 pxToDepth : function(px) { 1885 return Math.floor(px / api.options.menuItemDepthPerLevel); 1886 } 1887 1888 }; 1889 1890 $( function() { 1891 1892 wpNavMenu.init(); 1893 1894 // Prevent focused element from being hidden by the sticky footer. 1895 $( '.menu-edit a, .menu-edit button, .menu-edit input, .menu-edit textarea, .menu-edit select' ).on('focus', function() { 1896 if ( window.innerWidth >= 783 ) { 1897 var navMenuHeight = $( '#nav-menu-footer' ).height() + 20; 1898 var bottomOffset = $(this).offset().top - ( $(window).scrollTop() + $(window).height() - $(this).height() ); 1899 1900 if ( bottomOffset > 0 ) { 1901 bottomOffset = 0; 1902 } 1903 bottomOffset = bottomOffset * -1; 1904 1905 if( bottomOffset < navMenuHeight ) { 1906 var scrollTop = $(document).scrollTop(); 1907 $(document).scrollTop( scrollTop + ( navMenuHeight - bottomOffset ) ); 1908 } 1909 } 1910 }); 1911 }); 1912 1913 // Show bulk action. 1914 $( document ).on( 'menu-item-added', function() { 1915 if ( ! $( '.bulk-actions' ).is( ':visible' ) ) { 1916 $( '.bulk-actions' ).show(); 1917 } 1918 } ); 1919 1920 // Hide bulk action. 1921 $( document ).on( 'menu-removing-item', function( e, el ) { 1922 var menuElement = $( el ).parents( '#menu-to-edit' ); 1923 if ( menuElement.find( 'li' ).length === 1 && $( '.bulk-actions' ).is( ':visible' ) ) { 1924 $( '.bulk-actions' ).hide(); 1925 } 1926 } ); 1927 1928 })(jQuery);
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Tue Sep 22 08:20:31 2026 | Cross-referenced by PHPXref |