From 333f10ba1c3ce4a9abba1352c957c6b2b5c47d28 Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Tue, 25 May 2021 17:41:11 +0000 Subject: [PATCH] Menus: Add bulk delete for menu items. Adds an option to bulk delete menu items from the core Navigation Menus screen, offering a considerable usability benefit when making significant changes to navigation menus. Uses the bulk select pattern found in post and media lists for consistency with other core management screens, rather than the rapid delete pattern found in menus in the customizer. Props wphound, welcher, melchoyce, maxpertici, audrasjb Fixes #21603. git-svn-id: https://develop.svn.wordpress.org/trunk@51006 602fd350-edb4-49c9-b593-d223f7449a82 --- src/js/_enqueues/lib/nav-menu.js | 184 +++++++++++++++++- src/wp-admin/css/nav-menus.css | 108 ++++++++++ .../includes/class-walker-nav-menu-edit.php | 2 +- src/wp-admin/nav-menus.php | 23 ++- 4 files changed, 313 insertions(+), 4 deletions(-) diff --git a/src/js/_enqueues/lib/nav-menu.js b/src/js/_enqueues/lib/nav-menu.js index d3b9663487..b8dbd40ad6 100644 --- a/src/js/_enqueues/lib/nav-menu.js +++ b/src/js/_enqueues/lib/nav-menu.js @@ -45,6 +45,11 @@ this.attachMenuEditListeners(); + this.attachBulkSelectButtonListeners(); + this.attachMenuCheckBoxListeners(); + this.attachMenuItemDeleteButton(); + this.attachPendingMenuItemsListForDeletion(); + this.attachQuickSearchListeners(); this.attachThemeLocationsListeners(); this.attachMenuSaveSubmitListeners(); @@ -862,6 +867,183 @@ }); }, + /** + * Handle toggling bulk selection checkboxes for menu items. + * + * @since 5.8.0 + */ + attachBulkSelectButtonListeners : function() { + var that = this; + + $('.bulk-select-switcher').on( 'change', function(){ + if( this.checked ){ + $('.bulk-select-switcher').prop('checked',true); + that.enableBulkSelection(); + }else{ + $('.bulk-select-switcher').prop('checked',false); + that.disableBulkSelection(); + } + }); + }, + + /** + * Enable bulk selection checkboxes for menu items. + * + * @since 5.8.0 + */ + enableBulkSelection : function() { + $('#menu-to-edit').addClass('bulk-selection'); + $('#nav-menu-bulk-actions-top').addClass('bulk-selection'); + $('#nav-menu-bulk-actions-bottom').addClass('bulk-selection'); + + var checkbox = $('#menu-to-edit .menu-item-checkbox'); + $.each( checkbox, function(){ + $(this).prop('disabled',false); + }); + }, + + /** + * Disable bulk selection checkboxes for menu items. + * + * @since 5.8.0 + */ + disableBulkSelection : function() { + $('#menu-to-edit').removeClass('bulk-selection'); + $('#nav-menu-bulk-actions-top').removeClass('bulk-selection'); + $('#nav-menu-bulk-actions-bottom').removeClass('bulk-selection'); + + if ( $('.menu-items-delete').is('[aria-describedby="pending-menu-items-to-delete"]') ) { + $('.menu-items-delete').removeAttr( 'aria-describedby' ); + } + + var checkbox = $('#menu-to-edit .menu-item-checkbox'); + $.each( checkbox, function(){ + $(this).prop('disabled',true).prop('checked',false); + }); + $('.menu-items-delete').addClass('disabled'); + $('#pending-menu-items-to-delete ul').empty(); + }, + + /** + * Listen for state changes on bulk action checkboxes. + * + * @since 5.8.0 + */ + attachMenuCheckBoxListeners : function() { + var that = this; + + $( '#menu-to-edit' ).on( 'change', '.menu-item-checkbox', function(){ + that.setRemoveSelectedButtonStatus(); + }); + }, + + /** + * Create delete button to remove menu items from collection. + * + * @since 5.8.0 + */ + attachMenuItemDeleteButton : function() { + var that = this; + + $( document ).on('click', '.menu-items-delete', function(e){ + e.preventDefault(); + + if( ! $(this).hasClass('disabled') ){ + $.each( $('.menu-item-checkbox:checked'), function( index, element ) { + $( element ).parents( 'li' ).find( 'a.item-delete' ).trigger( 'click' ); + }); + + $('.menu-items-delete').addClass('disabled'); + $('.bulk-select-switcher').prop('checked',false); + + var itemsPendingDeletion = ''; + var itemsPendingDeletionList = $('#pending-menu-items-to-delete ul li'); + + $.each( itemsPendingDeletionList, function( index, element ) { + var itemName = $( element ).find( '.pending-menu-item-name' ).text(); + var itemSpeech = menus.menuItemDeletion.replace( '%s', itemName ); + itemsPendingDeletion += itemSpeech ; + if( ( index + 1 ) < itemsPendingDeletionList.length ){ + itemsPendingDeletion += ', ' ; + } + }); + + var deletionSpeech = menus.itemsDeleted.replace( '%s', itemsPendingDeletion ); + wp.a11y.speak( deletionSpeech, 'polite' ); + that.disableBulkSelection(); + } + }); + }, + + /** + * List menu items awaiting deletion. + * + * @since 5.8.0 + */ + attachPendingMenuItemsListForDeletion : function(){ + $('#post-body-content').on( 'change', '.menu-item-checkbox', function(){ + + if( ! $('.menu-items-delete').is('[aria-describedby="pending-menu-items-to-delete"]') ){ $('.menu-items-delete').attr( 'aria-describedby', 'pending-menu-items-to-delete' ); } + + var menuItemName = $(this).next().text(); + var menuItemType = $(this).parent().next('.item-controls').find('.item-type').text(); + var menuItemID = $(this).attr('data-menu-item-id') ; + + var ListedMenuItem = $('#pending-menu-items-to-delete ul').find('[data-menu-item-id=' + menuItemID + ']') ; + if( ListedMenuItem.length > 0 ){ + ListedMenuItem.remove(); + } + if( this.checked === true ){ + $('#pending-menu-items-to-delete ul').append( '
  • ' + menuItemName + ' (' + menuItemType + ')
  • ' ); + } + + $('#pending-menu-items-to-delete li .separator').html(', '); + $('#pending-menu-items-to-delete li .separator').last().html('.'); + }); + }, + + /** + * Set status of bulk delete checkbox. + * + * @since 5.8.0 + */ + setBulkDeleteCheckboxStatus : function(){ + + var that = this; + var checkbox = $('#menu-to-edit .menu-item-checkbox'); + + $.each( checkbox, function(){ + + if( $(this).prop('disabled') == true ){ + $(this).prop( 'disabled', false ); + }else{ + $(this).prop( 'disabled', true ); + } + + if( $(this).is(':checked')) { + $(this).prop('checked',false); + } + }); + + that.setRemoveSelectedButtonStatus(); + }, + + /** + * Set status of menu items removal button. + * + * @since 5.8.0 + */ + setRemoveSelectedButtonStatus : function(){ + + var button = $('.menu-items-delete'); + + if( $('.menu-item-checkbox:checked').length > 0 ) { + button.removeClass('disabled'); + } else { + button.addClass('disabled'); + } + }, + attachMenuSaveSubmitListeners : function() { /* * When a navigation menu is saved, store a JSON representation of all form data @@ -908,7 +1090,7 @@ searchTimer = setTimeout( function() { api.updateQuickSearchResults( $this ); - }, 500 ); + }, 500 ); }).on( 'blur', '.quick-search', function() { api.lastSearch = ''; }); diff --git a/src/wp-admin/css/nav-menus.css b/src/wp-admin/css/nav-menus.css index ed97188ea5..a6804bccaf 100644 --- a/src/wp-admin/css/nav-menus.css +++ b/src/wp-admin/css/nav-menus.css @@ -97,6 +97,105 @@ ul.add-menu-item-tabs li { margin: 1em 0 10px; } +#nav-menu-bulk-actions-top { + margin: 1em 0; +} + +#nav-menu-bulk-actions-bottom { + margin: 1em 0; + margin: calc( 1em + 9px ) 0 ; +} + +.bulk-actions input.button { + margin-right: 12px; +} + +.bulk-select-button { + position: relative; + display: inline-block; + padding: 0 10px; + font-size: 13px; + line-height: 2.15384615; + height: auto; + min-height: 30px; + background: #f6f7f7; + vertical-align: top; + border: 1px solid #dcdcde; + margin: 0; + cursor: pointer; + border-radius: 3px; + white-space: nowrap; + box-sizing: border-box; +} + +.bulk-selection .bulk-select-button { + color: #2271b1; + border-color: #2271b1; + background: #f6f7f7; + vertical-align: top; +} + +#pending-menu-items-to-delete { + display: none; +} + +.bulk-selection #pending-menu-items-to-delete { + display: block; + margin-top: 1em; +} + +#pending-menu-items-to-delete p { + margin-bottom: 0; +} + +#pending-menu-items-to-delete ul { + margin-top: 0; + list-style: none; +} + +#pending-menu-items-to-delete ul li { + display: inline; +} + +input.bulk-select-switcher + .bulk-select-button-label { + vertical-align: inherit; +} + +label.bulk-select-button:hover, +label.bulk-select-button:active, +label.bulk-select-button:focus-within { + background: #f0f0f1; + border-color: #0a4b78; + color: #0a4b78; +} + +input.bulk-select-switcher:focus + .bulk-select-button-label{ + color: #0a4b78; +} + +.bulk-actions input.menu-items-delete { + -webkit-appearance: none; + appearance: none; + font-size: inherit; + border: 0; + line-height: 2.1em; + background: none; + cursor: pointer; + text-decoration: underline; + color: #b32d2e; +} + +.bulk-actions input.menu-items-delete:hover { + color: #b32d2e; + border: none; +} + +.bulk-actions input.menu-items-delete.disabled { + cursor: default; + color: #a7aaad; + box-shadow: none; +} + .menu-settings { border-top: 1px solid #f0f0f1; margin-top: 2em; @@ -481,6 +580,15 @@ ul.add-menu-item-tabs li { margin-right: 13em; } +.menu-item-handle .menu-item-checkbox { + display: none; +} + +.bulk-selection .menu-item-handle .menu-item-checkbox { + display: inline-block; + margin-right: 6px; +} + .menu-item-handle .menu-item-title.no-title { color: #646970; } diff --git a/src/wp-admin/includes/class-walker-nav-menu-edit.php b/src/wp-admin/includes/class-walker-nav-menu-edit.php index fb9fceb7df..0249149eb3 100644 --- a/src/wp-admin/includes/class-walker-nav-menu-edit.php +++ b/src/wp-admin/includes/class-walker-nav-menu-edit.php @@ -118,7 +118,7 @@ class Walker_Nav_Menu_Edit extends Walker_Nav_Menu {