Search code examples
primefacesjsf-2.2bootsfaces

How to build Bootfaces <b:dropmenu> dynamically


My application is based on JSF 2.2 and Primefaces 6.2. I need to create a menubar where submenus would be displayed in a "drop-up", instead of a drop-down. I couldn't achieve this drop-up effect in Primefaces and had to go for Bootsfaces's <b:dropMenu> component which offers a "drop" attribute for this purpose (drop="up" creates a drop-up). More here: https://showcase.bootsfaces.net/layout/navbars.jsf

Using <b:dropMenu>, it gets rendered this way, which looks fine for me: Screenshot. However, I need to build the <b:dropMenu> dynamically via Java code.

Is it possible to use some kind of a model attribute with the <b:dropMenu>, similar to what Primefaces menus have?

In my current code, I have the sub-menus hard-coded this way, which I want to remove and build dynamically instead:

<b:navBar inverse="true" fluid="true" >
    <b:navbarLinks >
        <b:dropMenu value="Vehicle Services" drop="up" >
            <b:navLink value="Repairs" href="#"></b:navLink>
            <b:navLink value="Sales" href="#"></b:navLink>
            <b:navLink value="Financing" href="#"></b:navLink>
            <b:navLink value="Insurance" href="#"></b:navLink>
            <b:navLink value="Leasing" href="#"></b:navLink>
            <b:navLink value="Driving School" href="#"></b:navLink>
            <b:navLink value="Legal" href="#"></b:navLink>
        </b:dropMenu>
        .........
        .........
        .........

-- EDIT --

Following the code sample by @Selaron , trying to build dynamically from <b:NavBarLinks> in the XHTML:

<h:form>
    <b:navBar inverse="true" fluid="true">
        <b:navbarLinks>
             <f:event listener="#{extranetController.initializeNavBarLinksChildren}" type="postAddToView" />
        </b:navbarLinks>
    </b:navBar>
</h:form> 

In the bean:

public void initializeNavBarLinksChildren(ComponentSystemEvent e) {
    UIComponent component = e.getComponent();
    //assert that its a NavBarLinks

    DropMenu dropMenu = new DropMenu();
    dropMenu.setDrop("up");
    dropMenu.setValueExpression("value", Utility.createValueExpression("Vehicle Services", String.class));

    NavLink navLink1 = new NavLink();
    navLink1.setValue("Free Service");
    navLink1.setHref("#");
    dropMenu.getChildren().add(navLink1);

    NavLink navLink2 = new NavLink();
    navLink2.setValue("Paid Service");
    navLink2.setHref("#");
    dropMenu.getChildren().add(navLink2);

    component.getChildren().add(dropMenu);
}

In the Utility.createValueExpression() that I'm using...intention is to just set a string as a valueExpression:

public static ValueExpression createValuExpression(String expression, Class clazz) {
        FacesContext fc = FacesContext.getCurrentInstance();
        ELContext elContext = fc.getELContext();
        ExpressionFactory expFactory = fc.getApplication().getExpressionFactory();
        ValueExpression ret = expFactory.createValueExpression(elContext, expression, clazz);

        return ret;
    }

Sadly, on the UI, only the DropMenu comes up (ie, "Vehicle Services"), but none of the two NavLinks that I defined under it. I'm not very sure what I'm doing wrong. I doubt if it has anything to do with the way I'm setting the valueExpression for the DropMenu (though the value "Vehicle Services" does show up fine along with the up-ward caret icon as well.)


Solution

  • I did not find an attribute allowing to specify a dynamic menu model the PrimeFaces way but here is a work around:

    <h:form>
        <b:navBar inverse="true" fluid="true">
            <b:navbarLinks>
                <b:dropMenu value="Vehicle Services" drop="up">
                    <f:event listener="#{myBean.initializeDropMenuChildren}"
                        type="postAddToView" />
                </b:dropMenu>
            </b:navbarLinks>
        </b:navBar>
    </h:form>
    

    This registers an event listener (f:event) that is triggered once as soon as the b:dropMenu is added to the view. You can then dynamically instantiate and add links to that menu:

    package my.package;
    
    import javax.faces.component.UIComponent;
    import javax.faces.event.ComponentSystemEvent;
    import javax.enterprise.context.RequestScoped;
    import javax.inject.Named;
    
    import net.bootsfaces.component.dropMenu.DropMenu;
    import net.bootsfaces.component.navLink.NavLink;
    
    @Named
    @RequestScoped
    public class MyBean {
    
        public void initializeDropMenuChildren(ComponentSystemEvent e) {
            UIComponent component = e.getComponent();
            assert component instanceof DropMenu;
    
            NavLink navLink = new NavLink();
            navLink.setValue("wow");
            navLink.setHref("#");
            component.getChildren().add(navLink);
    
        }
    
    }
    

    EDIT:

    In response to your edit: It looks like the b:navbarLinks component does not like to be completely empty in the xhtml somehow. Try to add the f:event directly into the b:navBar and add all children from there. Or change your markup to:

    <h:form>
        <b:navBar>
            <b:navbarLinks>
                <b:dropMenu value="dummy" drop="down"/>
                <f:event listener="#{myBean.initializeDropMenuChildren}"
                    type="postAddToView" />
            </b:navbarLinks>
        </b:navBar>
    </h:form>
    

    And before adding your dropMenus, do a component.getChildren().clear(); to remove the dummy dropMenu.