WordPress.org

Make WordPress Core

Opened 19 months ago

Last modified 19 months ago

#22070 new defect (bug)

Deleting menus with no title / slow menu saving

Reported by: msebel Owned by:
Milestone: Awaiting Review Priority: normal
Severity: normal Version: 3.4.2
Component: Menus Keywords: dev-feedback
Focuses: Cc:

Description

Hello there

I saw that when saving nav menus in wp-admin/nav-menus.php WordPress executes the following lines, if a menu item is saved (no matter if newly added or existing) that doesn't have a title:

if ( empty( $_POST['menu-item-title'][$_key] ) )
  continue;

I changed this to the following to make it work. Problem is, it needs to be removed from $menu_items, because it's handled like a deleted menu else.

if ( empty( $_POST['menu-item-title'][$_key] ) ) {
  if (isset($menu_items[$_POST['menu-item-db-id'][$_key]]))
    unset($menu_items[$_POST['menu-item-db-id'][$_key]]);
    continue;
  }
}

Sidenote:
With that fixed, I was able to solve a major saving bug. Many of our customers have a lot of menu items in out nav_menu (like 100 - 600), which without a timeout would take 10 minutes and more to save.

So I created some hotfix jQuery script that does "dirty-handling". Basically it marks everything that's touched (like changed data, adding a menu item or moving things around) as "dirty". On submit it doesn't nothing more than change every "undirty" menu items title to an empty string so it doesn't get saved.

I think WordPress core developers could integrate something like this with far less code since I wasn't able to use events - I didn't really had a lot of time to fix this. So if it helps anyone, this is the jQuery code I used to create the fix:

jQuery(function($) {
  // the currently displayed menu form
  var $form = $('#update-nav-menu');

  // Listen to new menus, since we have no possibility to hook
  // we have to interval it, because we can't capture an event here
  $('.submit-add-to-menu').click(function() {
    var $item_count = $form.find('.menu-item-handle').length;
    var $interval_id = setInterval(function() {
      var $current_item_count = $form.find('.menu-item-handle').length;
      if ($item_count < $current_item_count) {
        clearInterval($interval_id);
        // Add the dirty flag and set it to dirty immediately
        add_dirty_flags(1);
        // Reassign the mousedown/up events
        assign_mouse_events();
      }
    },200);
  });

  // add a hidden field, telling if the menu is dirty (by default, it's not)
  function add_dirty_flags($flag) {
    $form.find('.menu-item-handle').each(function() {
      if ($(this).find('.dirty-handle').length == 0) {
        var $html = '<input type="hidden" class="dirty-handle" value="' + $flag + '" />';
        $(this).append($html);
        console.log('flag added');
      }
    });
  }

  // (re)assigns mouse events to menu items
  function assign_mouse_events() {
    var $menu_items = $('.menu-item-bar');
    // Unbind previously assigned events
    $menu_items.unbind('mouseup').unbind('mousedown');

    // Dirty Flag handler if a click happens
    $menu_items.mousedown(function() {
      $(this).find('.dirty-handle').val('1');
    });

    // if the parent changes on release the mouse handle, change all items with the new parent to dirty
    $menu_items.mouseup(function() {
      menu_save_mouseup($(this));
    });
  }

  // The call back for mouseup on menu bars
  function menu_save_mouseup($this) {
    var $temp_object = $this.parent();
    // Mark everything with the same parent dirty
    setTimeout(function() {
      var $parent_id = $temp_object.find('.menu-item-data-parent-id').val();
      // Go through all fields, dirtying everything that has the same parent
      $('.menu-item-data-parent-id').each(function() {
        if ($(this).val() == $parent_id)
          $(this).parent().prev().find('.dirty-handle').val('1');
      });
    },200);
  }

  // On submit make every undirty menu an empty title, so it won't get saved
  // We're using a hoax in nav-menus.php, line 335 here..
  $form.submit(function() {
    // Now traverse all handles and make only dirties saveable
    $form.find('.menu-item-handle').each(function() {
      // Find the dirty flag
      var $is_dirty = $(this).find('.dirty-handle').val();
      $(this).find('.dirty-handle').remove();
      if ($is_dirty == 0) {
        // The div containing the menu informations
        var $forms = $(this).parent().next();
        // Remove the title value so it doesn't get saved
        $forms.find('.edit-menu-item-title').val('');
      }
    });
    return true;
  });

  // Assign mouse events on first load
  assign_mouse_events();
  // Add all dirty flags (not dirty by default of course)
  add_dirty_flags(0);
});

Since WordPress now only saves the menu's that changed it doesn't matter how many menu items a customer has, but only how many he want's to save at one time.

Change History (1)

comment:1 SergeyBiryukov19 months ago

Related: #14134, #17031

Last edited 19 months ago by SergeyBiryukov (previous) (diff)
Note: See TracTickets for help on using tickets.