Primefaces PanelMenu accordion behavior

UI Components for JSF
asterD
Posts: 6
Joined: 18 Jun 2012, 11:03

21 Dec 2012, 12:49

Hello everyone!
First of all, i would like to offer my sincere congratulations to the makers of this fantastic library!
Then i would like to share a small piece of code that i made related to PanelMenu. It 'a very simple thing but it can be useful to someone.

At now, the PanelMenu does not have the tiered option to behave like the accordion pane (if i open a pane, the others are hidden).
To simulate just do like this:

Code: Select all

<ui:define name="menu">
		<p:panelMenu id="hbMenu" model="#{menu.model}" widgetVar="hbPanelMenu" />
		<h:outputScript>
			hbPanelMenu.headers.click(function(e) {
				var currHeader = $(this);
				hbPanelMenu.headers.each(function() {
					var header = $(this);
					if(header.text() != currHeader.text()) {
						hbPanelMenu.collapseRootSubmenu(header);
						header.removeClass('ui-state-hover');
					}
				});
			});
		</h:outputScript>
	</ui:define>
Another aspect is the following: the panelMenu keeps the selection with a cookie, but if the navigation is done with methods such as "handleNavigation" which provide a complete refresh of the page, the cookie is lost.

To solve this problem you can do this:

Code: Select all

// XHMTL PART

		<h:form id="hbMenuForm">
			<p:panelMenu model="#{menuPanel.model}" widgetVar="hbPanelMenu" />
			<p:inputText binding="#{lookup.components.hbActiveTab}" id="hbActiveTab" value="#{menuPanel.activeTab}" style="display:none;"/>  
			<h:outputScript>
				var elId = document.getElementById('#{lookup.clientIds.hbActiveTab}').value;
				if(elId) {
					hbPanelMenu.headers.each(function() {
						var header = $(this);
						if(header.text() == elId) {
							hbPanelMenu.expandRootSubmenu(header, false);
						}
					});
				}
			
				hbPanelMenu.headers.click(function(e) {
					var currHeader = $(this);
					hbPanelMenu.headers.each(function() {
						var header = $(this);
						if(header.text() != currHeader.text()) {
							hbPanelMenu.collapseRootSubmenu(header);
							header.removeClass('ui-state-hover');
						}
					});

					document.getElementById('#{lookup.clientIds.hbActiveTab}').value = currHeader.text();
				});
			</h:outputScript>
		</h:form>
	</ui:define>
and for the backing bean that manage the binding you can use:

Code: Select all

import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;

@ManagedBean(name="lookup")
@RequestScoped
public class ComponentLookup {
    private Map<String, Map<String, UIComponent>> componentMap;

    private Map<String, Map<String, UIComponent>> getPageMaps() {
        if (componentMap == null) {
            componentMap = new HashMap<String, Map<String, UIComponent>>();
        }
        return componentMap;
    }

    private Map<String, UIComponent> getViewMap(String viewId) {
        Map<String, Map<String, UIComponent>> pageMaps = getPageMaps();
        Map<String, UIComponent> viewMap = pageMaps.get(viewId);
        if (viewMap == null) {
            viewMap = new HashMap<String, UIComponent>();
            pageMaps.put(viewId, viewMap);
        }
        return viewMap;
    }

    /** Returns map for components */
    public Map<String, UIComponent> getComponents() {
        FacesContext context = FacesContext.getCurrentInstance();
        UIViewRoot view = context.getViewRoot();
        String viewId = view.getViewId();
        return getViewMap(viewId);
    }

    private Map<String, String> clientIdMap;

    /** Returns map for getting clientIds */
    public Map<String, String> getClientIds() {
        if (clientIdMap == null) {
            clientIdMap = new AbstractMap<String, String>() {
                @Override
                public String get(Object key) {
                    return getClientId(key.toString());
                }
                @Override
                public Set<java.util.Map.Entry<String, String>> entrySet() {
                    throw new UnsupportedOperationException();
                }
            };
        }
        return clientIdMap;
    }

    private String getClientId(String id) {
        FacesContext context = FacesContext.getCurrentInstance();
        Map<String, UIComponent> components = getComponents();
        UIComponent component = components.get(id);
        return component.getClientId(context);
    }
}
I know that it is a home-made solution, but it works!
I hope this code can help someone!

Happy coding!
Primefaces 3.4.2
JSF 2.1
Websphere Portal 8.0.0.5
Websphere Application Server 8.0.0.3
Window Server 2008 - 2003, Win7

sakkie6yster
Posts: 80
Joined: 23 Nov 2011, 08:46

23 Apr 2013, 08:03

Hi asterD I would love to implement this code of your for our project too, but struggling to get it to work. Could you give me some pointers perhaps?

Did you test this code as is? I tried to implement it exactly as is, but it doesn't seem to work;
For example if I use your ui:define tag my menu is not even visible and I get a compilation exception on the way you use h:outputscript - "required attribute name missing" - name is suppose to point to js right. So I created a js file with your javascript, but this didn't help either.

Is there something wrong with the javascript as well?
Primefaces 5.3, Extensions 4.0.0
Glassfish 3.1.2.2, Wildfly-10.0.0.Final
JSF 2.2

sakkie6yster
Posts: 80
Joined: 23 Nov 2011, 08:46

23 Apr 2013, 08:14

Ok... I got the JS working (as usual problem lied with me) :D

I had the h:outputScript at the top of the body tag, moved it inside my h:form tags:

Code: Select all

  <h:form id="menuForm" >
        <p:panelMenu model="#{bean.model}" widgetVar="hbPanelMenu"  />
        <h:outputScript library="js" name="handle-navigation.js" />
  </h:form>
I also made two small "tweaks" to your js: removed the function parameter 'e' and changed '!=' to '!==':

Code: Select all

hbPanelMenu.headers.click(function() {
    var currHeader = $(this);
    hbPanelMenu.headers.each(function() {
        var header = $(this);
        if (header.text() !== currHeader.text()) {
            hbPanelMenu.collapseRootSubmenu(header);
            header.removeClass('ui-state-hover');
        }
    });
});
Thank you! Brilliant piece of code btw :)
I am going to look at the rest of your example next! Hopefull will be little easier to imitate! ;)
Primefaces 5.3, Extensions 4.0.0
Glassfish 3.1.2.2, Wildfly-10.0.0.Final
JSF 2.2

elprimo
Posts: 1
Joined: 14 May 2013, 19:08

14 May 2013, 19:13

just one word : wonderful

dvd1984
Posts: 2
Joined: 07 Jun 2013, 15:26

07 Jun 2013, 15:43

Hi all, i'm new on this forum.

I've resolved in other way, but doesn't work with pf 3.5.
I've tried with your solution (both solutions works fine with 3.4.2) but i've same problem on latest (free) version.

With firebug i've seen that menu object (and many other DOM object, generated by pf) wasn't present in DOM, and javascript problem is encurred ($ is not defined, hbPanelMenu is undefined)

library version:
  • primefaces 3.5
    jsf-api/impl 2.1.2
AS: jboss eap 5.1

any suggestion?
--
primefaces 3.4.2
jsf-api/impl 2.1.2 (mojarra)
AS: jboss eap 5.1
--
dvd

sakkie6yster
Posts: 80
Joined: 23 Nov 2011, 08:46

31 Jul 2014, 21:41

Try using:

Code: Select all

PF('hbPanelMenu').headers.click(function() {
    var currHeader = $(this);
    PF('hbPanelMenu').headers.each(function() {
        var header = $(this);
        if (header.text() !== currHeader.text()) {
            PF('hbPanelMenu').collapseRootSubmenu(header);
            header.removeClass('ui-state-hover');
        }
    });
});
Primefaces 5.3, Extensions 4.0.0
Glassfish 3.1.2.2, Wildfly-10.0.0.Final
JSF 2.2

mireczatko
Posts: 3
Joined: 02 Dec 2014, 04:21

09 Jan 2015, 23:07

Hi there can anybody explain me how this code works? It is pretty clear how works 'accordion part' of it however state saving part is pretty "mysterious" for me... :)
And, unfortunately, exactly this "voodoo" part doesn't work...

Code: Select all

#{menuPanel.activeTab}
setter is never invoked, just getter(means returning null always)

Code: Select all

var elId = document.getElementById('#{lookup.clientIds.hbActiveTab}').value
returns empty string each time...

* I am using Primefaces5.1, JSF2.2, Wildfly8.1.0

mireczatko
Posts: 3
Joined: 02 Dec 2014, 04:21

10 Jan 2015, 02:43

Finally I changed this solution little bit and now it is working using cookies without need of BackingBean, only "prerequisite" is to have unique p:submenu labels
setCookie,getCookie functions are from http://www.w3schools.com/js/js_cookies.asp
it works with "one-level" submenu, maybe it can help someone

Code: Select all

// XHTML
 <p:panelMenu widgetVar="myPanelMenu">
...
 </p:panelMenu>


// JAVASCRIPT
jQuery(document).ready(function()
{
  var remembered = getCookie("myPanelMenu_remembered");
  PF('myPanelMenu').headers.each(function()
  {
    var header = jQuery(this);
    if(header.text() === remembered)
    {
      PF('myPanelMenu').expandRootSubmenu(header, false);
    }
    else
    {
      PF('myPanelMenu').collapseRootSubmenu(header);
      header.removeClass('ui-state-hover');
    }
  });
  PF('myPanelMenu').menuitemLinks.click(function()
  {
    var expanded = getCookie("myPanelMenu_expanded");
    setCookie("myPanelMenu_remembered", expanded);
  });
  PF('myPanelMenu').headers.click(function()
  {
    var currHeader = jQuery(this);
    PF('myPanelMenu').headers.each(function()
    {
      var header = jQuery(this);
      if(header.text() !== currHeader.text())
      {
        PF('myPanelMenu').collapseRootSubmenu(header);
        header.removeClass('ui-state-hover');
      }
    });
    setCookie("myPanelMenu_expanded", currHeader.text());
  })
});
function setCookie(cname, cvalue, exdays) {
  exdays = typeof exdays !== 'undefined' ? exdays : 1;
  var d = new Date();
  d.setTime(d.getTime() + (exdays*24*60*60*1000));
  var expires = "expires="+d.toUTCString();
  document.cookie = cname + "=" + cvalue + "; " + expires+ "; path=/";
}

function getCookie(cname) {
  var name = cname + "=";
  var ca = document.cookie.split(';');
  for(var i=0; i<ca.length; i++) {
      var c = ca[i];
      while (c.charAt(0)==' ') c = c.substring(1);
      if (c.indexOf(name) == 0) return c.substring(name.length, c.length);
  }
  return "";
}

Shreyas Hulyalkar
Posts: 7
Joined: 16 May 2016, 14:08

24 May 2016, 15:55

Even this block of code also holds good.

Code: Select all

PF('myPanelMenu').headers.each(
				function(){
	          		var header = jQuery(this);	          		   
	          		      PF('myPanelMenu').collapseRootSubmenu(header);
	          		      header.removeClass('ui-state-hover');	          		    
	          	}
			);
Thanks & Regards
Shreyas

tlog
Posts: 2
Joined: 05 Oct 2015, 14:28

03 Aug 2016, 09:07

In the current primefaces-6.0, the code provided by mireczatko will break.

I opened a ticket on the issue tracker, but got closed as using internal apis.
For reference: https://github.com/primefaces/primefaces/issues/1658
If you ad a setTimeout before the function starts, it will be working. My timeout value is 500.

I somehow think, it could be related to the changed behavior of the SearchExpressionFacade as mentioned in the migration guide.
https://github.com/primefaces/primeface ... tion-Guide

Actually, this thread was created back in the end of 2012 and this behavior still doesn't made it into any release. If you fire up google searching for the panelmenu, this is the thing that gets the most attention. Even the primefaces showcase itself uses this behavior. :(
Running on Java 1.8, JSF 2.2, Web-App 3.1
- Primefaces 6.0
- MyFaces 2.2.10
- Tomcat 8.0.36

Post Reply

Return to “PrimeFaces”

  • Information
  • Who is online

    Users browsing this forum: No registered users and 22 guests