Is it possible to make the user-chosen expanded/collapsed state of the menus persist through page loads? Not the default behavior, but the changes to expanded/collapsed state after the menu has been loaded. I'm using it with Advanced Taxonomy Block and find it troublesome when the menus keep collapsing.

Comments

PixelClever’s picture

That sounds like it would take quite a bit of fancy coding and would probably impact performance. If you think about it, you would have to store and enormous array of link identifiers in either a session variable or a cookie. I personally don't think I could do it without slowing down the module drastically.

I am open to patches from the javascript gods if they smile upon us, but I personally am not up for the task.

vik.nowak’s picture

This is for my own private use, but the general principle might be the same for your case and I thought I should share this.
This keeps all parent menus open, if there's an active child 'a' element somewhere in there.

this goes into jquerymenu_no_animation.js, but you can figure it out for the other file as well :)

// $Id: jquerymenu_no_animation.js,v 1.2 2009/02/04 21:54:54 aaronhawkins Exp $
Drupal.behaviors.jquerymenu = function(context) {
  $('ul.jquerymenu:not(.jquerymenu-processed)', context).addClass('jquerymenu-processed').each(function(){
	/*keep open if active link patch begin*/
	$(this).find("li.parent span.parent").each(function(){
		lolla = $(this).parent();											
		nonna = $(this).parent().find('a.active');
		if($(nonna[0]).hasClass('active')){
			if ($(lolla).hasClass('closed')){
				$(lolla).removeClass('closed').addClass('open');
				$(this).removeClass('closed').addClass('open');
			  }		
		} else{
			$(lolla).removeClass('open').addClass('closed');
        	$(this).removeClass('open').addClass('closed');	
		}
	});
	/*patch code ends*/
	
    $(this).find("li.parent span.parent").click(function(){
      momma = $(this).parent();
      if ($(momma).hasClass('closed')){
        $(momma).removeClass('closed').addClass('open');
        $(this).removeClass('closed').addClass('open');
      }
      else{
        $(momma).removeClass('open').addClass('closed');
        $(this).removeClass('open').addClass('closed');
      }
    });
  });
}

Thank you for this wonderful module.

PixelClever’s picture

Status: Active » Fixed

I think I misunderstood the issue in the beginning. There was a php based solution that was very easy to put in. In the 2.3 release active links have their children open on page load.

LarryS’s picture

Status: Fixed » Active

You didn't misunderstand (I think). I meant keeping the state of open/closed menus regardless of whether the current page was an active item within the menu tree.

If you generated unique id attributes for every HTML list item element (don't have any now, could use the term id) and sent the ids of only 'open' menu items to the server as a cookie, then the module could figure the open state of the menu on generation of the menu. You would only have to record, via Javascript, what ids were currently open or have changed state, and store that in a cookie.

I don't think you have to do it all in JavaScript. This method would 'break' if terms were added/deleted recently. The cookie could be reset on page loads to offset this issue.

Well, at least I think it would work.

PixelClever’s picture

Status: Active » Closed (won't fix)

In that case I understood correctly the first time and comment #2 just confused me. Doing what you are talking about would slow down the module significantly... I'm not adding it unless someone can provide a patch that can be demonstrated to run quickly.

PixelClever’s picture

There's no reason you couldn't add the functionality from an add on module. In this situation I think that would be the way to go.

math_1048’s picture

Thank you very much for module creator,
Dear vik.nowak thank you for your nice code - I was looking for :)
best regards

davidwhthomas’s picture

I'm also interested in this feature, to preserve open state between reloads without reference to the active trail.
I'm working on a patch and will keep this issue posted.
DT

sven.buschbeck’s picture

StatusFileSize
new5.01 KB
new11.89 KB

Hi All,

The website, I am currently working on, uses two additional menus besides primary links (one called for internal documents and the default Drupal navigation menu). As a side administrator, I have to use both menus simultaneously, thus I really could not live with the situation of clicking one menu makes the other collapse all nodes.

To cut a long story short: I modified Jquerymenu to remember states for each menu after reload by using a cookie.
Unfortunately, I had to modify both ends - PHP (adding an ID to each menu item) and Javascript (to store and recover states).

PHP functions modified (PHP file attached!):

  • Passing trough the menu name from jquerymenu_block to theme_menu_creation_by_array and finally to recursive_link_creator
  • Added code to recursive_link_creator for
    • creating IDs (based on a counter and the menu name passed in) - could need some improvement I guess - technique used so far is rather simple
    • keeping track of IDs by adding parameter "lastId"
    • and finally adding the ID to each <li> and the end of the function

Javascript changes (marked using /* sb patch */ and /* /sb patch */ - original Javascript file including some debug stuff attached)):

// $Id: jquerymenu.js,v 1.6 2009/11/16 08:44:48 aaronhawkins Exp $
Drupal.behaviors.jquerymenu = function(context) {
  /* show and hide "edit this menu item buttons */
  jqm_showit = function() {
    $(this).children('.jqm_link_edit').fadeIn();
  }
  jqm_hideit = function() {
    $(this).children('.jqm_link_edit').fadeOut();
  }
  $('ul.jquerymenu li').hover(jqm_showit, jqm_hideit);

  /* sb patch */

  /* This method captures menu changes (i.e. menu items get folded and unfolded).
     All changes are stored in a cookie called "jqmState" (modify by changing variable "cookieName").
     The cookie contains a list of all opened items. 
  */
  jqm_cookieName = 'jqmState';

  jqm_getState = function() {
    var cookie = "";
    if (document.cookie.length>0) {
      var c_start=document.cookie.indexOf(jqm_cookieName + "=");
      if (c_start!=-1) {
	c_start=c_start + jqm_cookieName.length+1 ;
	var c_end=document.cookie.indexOf(";",c_start);
	if (c_end==-1) c_end=document.cookie.length
	cookie = unescape(document.cookie.substring(c_start,c_end));
      }
    }

    /* parse cookie */
    var nodes = new Array();
    if (cookie.length > 0) {
      nodes = cookie.split(',');
    }

    return nodes;
  }

  jqm_state = function(menuItemId, open) {
    /* sanitary code */
    if (typeof(menuItemId) == "string") {
      menuItemId = new Number(menuItemId);
    }
    if (typeof(menuItemId) == "object") {
      menuItemId = $(menuItemId).attr('id');
    }
    //if (typeof(menuItemId) != "number" && isNaN(menuItemId)) {
    if (!menuItemId || typeof(menuItemId) != "string" || menuItemId.length < 1) {
      return;
    }
    
    
    /* init */
    var cookieName = jqm_cookieName; //'jqmState';
    var cookieExpireDate = new Date();
    cookieExpireDate.setDate(cookieExpireDate.getDate() + 1);

    /* load cookie data */
    var nodes = jqm_getState();

    /* is node in already? */
    var nodeIndex = -1;
    for (var x = 0; x < nodes.length && nodeIndex < 0; x++) {
      if (menuItemId == nodes[x]) {
	nodeIndex = x;
      }
    }

    if ((open && (nodeIndex >= 0)) || (!open && (nodeIndex < 0))) {
      /* nothing to do */
      return;
    }

    if (open) {
      /* add node to storage */
      nodes.push(menuItemId);
    }
    else {
      nodes.splice(nodeIndex, 1);
    }

    /* store cookie */
    var cookie = cookieName + "=" + escape(nodes.join(',')) + "; path=/; expires=" + cookieExpireDate.toGMTString();
    document.cookie = cookie;
  }

  /* restore menu setting (i.e. folding of each item) based on the stored cookie */
  jqm_restoreMenu = function() {
    var nodes = jqm_getCookie();
    for (var x = 0; x < nodes.length && nodeIndex < 0; x++) {
      
    }
  }
  /* /sb patch */

  var nodesToBeRestored = jqm_getState();
  $('ul.jquerymenu:not(.jquerymenu-processed)', context).addClass('jquerymenu-processed').each(function(){
    $(this).find("li.parent span.parent")/* sb patch */.each(function() {
      /* restore state */
      momma = $(this).parent();
      if (jQuery.inArray($(momma).attr("id"), nodesToBeRestored) > -1) {
	  $($(this).siblings('ul').children()).show();
	  //$(momma).children('ul').slideDown('700');
	  $(momma).removeClass('closed').addClass('open');
	  $(this).removeClass('closed').addClass('open');
      }
    })/* /sb patch */.click(function(){
      momma = $(this).parent();
      /* sb patch */
      var toBeOpened = $(momma).hasClass('closed');
      jqm_state($(momma).attr("id"), toBeOpened);
      /* /sb patch */
      if (toBeOpened){
        $($(this).siblings('ul').children()).hide().fadeIn('3000');
        $(momma).children('ul').slideDown('700');
        $(momma).removeClass('closed').addClass('open');
        $(this).removeClass('closed').addClass('open');
        jqm_state($(momma), true);
      }
      else{
        $(momma).children('ul').slideUp('700');
        $($(this).siblings('ul').children()).fadeOut('3000');
        $(momma).removeClass('open').addClass('closed');
        $(this).removeClass('open').addClass('closed');
        jqm_state($(momma), false);
      }
    });
  });

}

In the website version, I additionally quoted the first section (everything until the first /* sb patch */) showing and hiding the link editing buttons, as I consider them disturbing only.

Please consider code as a first draft implementation - no big performance tests done - though I did not notice anything negative. As mentioned before: the ID generation is "alpha", the Javascript methods can be improved, too, for sure.

I am really looking forward for your feedback!

Best,
Sven

sven.buschbeck’s picture

Just realized, the Javascript method jqm_state is called twice.
Updated Javascript Code (last part of it only):

...
  var nodesToBeRestored = jqm_getState();
  $('ul.jquerymenu:not(.jquerymenu-processed)', context).addClass('jquerymenu-processed').each(function(){
    $(this).find("li.parent span.parent")/* sb patch */.each(function() {
      momma = $(this).parent();
      if (jQuery.inArray($(momma).attr("id"), nodesToBeRestored) > -1) {
        /* restore state */
        $($(this).siblings('ul').children()).show();
        $(momma).removeClass('closed').addClass('open');
        $(this).removeClass('closed').addClass('open');
      }
    })/* /sb patch */.click(function(){
      momma = $(this).parent();
      if ($(momma).hasClass('closed')){
        $($(this).siblings('ul').children()).hide().fadeIn('3000');
        $(momma).children('ul').slideDown('700');
        $(momma).removeClass('closed').addClass('open');
        $(this).removeClass('closed').addClass('open');
        /* sb patch */
        jqm_state($(momma), true);
        /* /sb patch */
      }
      else{
        $(momma).children('ul').slideUp('700');
        $($(this).siblings('ul').children()).fadeOut('3000');
        $(momma).removeClass('open').addClass('closed');
        $(this).removeClass('open').addClass('closed');
        /* sb patch */
        jqm_state($(momma), false);
        /* /sb patch */
      }
    });
  });

}

Cheers,
Sven