Reputation: 375
I want to programmatically create p:menu with MenuItems (it works) and each MenuItem should has p:contextMenu (it doesn't work).
ManagedBean:
@ManagedBean(name="leftMenuView")
@SessionScoped
public class LeftMenuView {
private MenuModel model;
@PostConstruct
public void init() {
model = new DefaultMenuModel();
DefaultMenuItem item = new DefaultMenuItem("Redirect");
item.setId("redirectMenuItem");
model.addElement(item);
ContextMenu ctxMenu = new ContextMenu();
ctxMenu.setFor("redirectMenuItem");
MenuModel ctxModel = new DefaultMenuModel();
MenuItem ctxItem = new DefaultMenuItem("Remove from favorities");
ctxModel.addElement(ctxItem);
ctxMenu.setModel(ctxModel);
}
(...)
}
view:
<h:form id="leftForm">
<p:menu id="leftMenu" model="#{leftMenuView.model}" />
</h:form>
Menu appears, but ContextMenu were not shown when I do right-click on MenuItem with "Redirect" label.
What I am doing wrong?
UPDATE - When I add:
uiComponent = (UIComponent) rootView
.findComponent("leftForm");
uiComponent.getChildren().add(ctxMenu);
It displays context menu on whole Menu bar. This ist unimportant that I have clicked MenuItem or just Menu component.
When I change it to:
uiComponent = (UIComponent) rootView
.findComponent(":leftForm:leftMenu:redirectMenuItem");
uiComponent.getChildren().add(ctxMenu);
I get "java.lang.IllegalArgumentException: leftMenu"
In other words - I want to get this behaviour:
<h:form>
<p:menu id="menu_id">
<p:menuitem id="gmail_id" value="Gmail"/>
<p:menuitem id="hotmail_id" value="Hotmail" />
</p:menu>
<p:contextMenu for="gmail_id">
<p:menuitem value="Save" />
<p:menuitem value="Delete"/>
</p:contextMenu>
</h:form>
but programmatically (from source code).
Upvotes: 0
Views: 5679
Reputation: 68
The Id you set in the Backend gets overwritten again in PrimeFaces Class BaseMenuRenderer, encodeEnd() with generateUniqueIds(..)
I opened an Issue for that https://github.com/primefaces/primefaces/issues/1039 .
Upvote it if u like to. Seems to be a PrimeFaces bug. It still exists in PrimeFaces 5.3.5
So the answer is, that it is not possible to set ID's for MenuItems at the moment.
This is my workaround to set IDs at the moment:
put this in your faces-config.xml to user your own MenuRenderer
<render-kit>
<renderer>
<component-family>org.primefaces.component</component-family>
<renderer-type>org.primefaces.component.MenuRenderer</renderer-type>
<renderer-class>de.yourPackage.CustomMenuRenderer</renderer-class>
</renderer>
</render-kit>
and then make a CustomMenuRenderer class like this which is only changed a little not to override the IDS:
public class CustomMenuRenderer extends CustomBaseMenuRenderer {
protected void encodeScript(FacesContext context, AbstractMenu abstractMenu) throws IOException {
Menu menu = (Menu) abstractMenu;
String clientId = menu.getClientId(context);
WidgetBuilder wb = getWidgetBuilder(context);
wb.initWithDomReady("PlainMenu", menu.resolveWidgetVar(), clientId)
.attr("toggleable", menu.isToggleable(), false);
if (menu.isOverlay()) {
encodeOverlayConfig(context, menu, wb);
}
wb.finish();
}
protected void encodeMarkup(FacesContext context, AbstractMenu abstractMenu) throws IOException {
ResponseWriter writer = context.getResponseWriter();
Menu menu = (Menu) abstractMenu;
String clientId = menu.getClientId(context);
String style = menu.getStyle();
String styleClass = menu.getStyleClass();
String defaultStyleClass = menu.isOverlay() ? Menu.DYNAMIC_CONTAINER_CLASS : Menu.STATIC_CONTAINER_CLASS;
if (menu.isToggleable()) {
defaultStyleClass = defaultStyleClass + " " + Menu.TOGGLEABLE_MENU_CLASS;
}
styleClass = styleClass == null ? defaultStyleClass : defaultStyleClass + " " + styleClass;
writer.startElement("div", menu);
writer.writeAttribute("id", clientId, "id");
writer.writeAttribute("class", styleClass, "styleClass");
if (style != null) {
writer.writeAttribute("style", style, "style");
}
writer.writeAttribute("role", "menu", null);
encodeKeyboardTarget(context, menu);
if (menu.getElementsCount() > 0) {
writer.startElement("ul", null);
writer.writeAttribute("class", Menu.LIST_CLASS, null);
encodeElements(context, menu, menu.getElements());
writer.endElement("ul");
}
writer.endElement("div");
}
protected void encodeElements(FacesContext context, Menu menu, List<MenuElement> elements) throws IOException {
ResponseWriter writer = context.getResponseWriter();
boolean toggleable = menu.isToggleable();
for (MenuElement element : elements) {
if (element.isRendered()) {
if (element instanceof MenuItem) {
MenuItem menuItem = (MenuItem) element;
String containerStyle = menuItem.getContainerStyle();
String containerStyleClass = menuItem.getContainerStyleClass();
containerStyleClass = (containerStyleClass == null) ? Menu.MENUITEM_CLASS : Menu.MENUITEM_CLASS + " " + containerStyleClass;
if (toggleable) {
UIComponent parent = ((UIComponent) menuItem).getParent();
containerStyleClass = (parent instanceof Submenu) ? containerStyleClass + " " + Menu.SUBMENU_CHILD_CLASS : containerStyleClass;
}
writer.startElement("li", null);
writer.writeAttribute("class", containerStyleClass, null);
writer.writeAttribute("role", "menuitem", null);
if (containerStyle != null) {
writer.writeAttribute("style", containerStyle, null);
}
if (menuItem.getId() != null) {
writer.writeAttribute("id", menuItem.getId(), null);
}
encodeMenuItem(context, menu, menuItem);
writer.endElement("li");
}
else if (element instanceof Submenu) {
encodeSubmenu(context, menu, (Submenu) element);
}
else if (element instanceof Separator) {
encodeSeparator(context, (Separator) element);
}
}
}
}
@SuppressWarnings("unchecked")
protected void encodeSubmenu(FacesContext context, Menu menu, Submenu submenu) throws IOException {
ResponseWriter writer = context.getResponseWriter();
String label = submenu.getLabel();
String icon = submenu.getIcon();
String style = submenu.getStyle();
String styleClass = submenu.getStyleClass();
styleClass = styleClass == null ? Menu.SUBMENU_TITLE_CLASS : Menu.SUBMENU_TITLE_CLASS + " " + styleClass;
boolean toggleable = menu.isToggleable();
//title
writer.startElement("li", null);
if (toggleable) {
writer.writeAttribute("id", submenu.getClientId(), null);
}
writer.writeAttribute("class", styleClass, null);
if (style != null) {
writer.writeAttribute("style", style, null);
}
if (menu.getId() != null) {
writer.writeAttribute("id", menu.getId(), null);
}
writer.startElement("h3", null);
if (menu.isToggleable()) {
encodeIcon(context, label, Menu.EXPANDED_SUBMENU_HEADER_ICON_CLASS);
}
if (icon != null) {
encodeIcon(context, label, "ui-submenu-icon ui-icon " + icon);
}
if (label != null) {
writer.writeText(label, "value");
}
writer.endElement("h3");
writer.endElement("li");
encodeElements(context, menu, submenu.getElements());
}
protected void encodeIcon(FacesContext context, String label, String styleClass) throws IOException {
ResponseWriter writer = context.getResponseWriter();
writer.startElement("span", null);
writer.writeAttribute("class", styleClass, null);
writer.endElement("span");
}
}
finally you also need your own Class for a CustomBaseMenuRenderer:
public class CustomBaseMenuRenderer extends BaseMenuRenderer {
@Override
protected void encodeMenuItem(FacesContext context, AbstractMenu menu, MenuItem menuitem) throws IOException {
ResponseWriter writer = context.getResponseWriter();
String title = menuitem.getTitle();
String style = menuitem.getStyle();
boolean disabled = menuitem.isDisabled();
writer.startElement("a", null);
writer.writeAttribute("tabindex", "-1", null);
if (shouldRenderId(menuitem)) {
writer.writeAttribute("id", menuitem.getClientId(), null);
}
if (title != null) {
writer.writeAttribute("title", title, null);
}
String styleClass = this.getLinkStyleClass(menuitem);
if (disabled) {
styleClass = styleClass + " ui-state-disabled";
}
writer.writeAttribute("class", styleClass, null);
if (style != null) {
writer.writeAttribute("style", style, null);
}
if (disabled) {
writer.writeAttribute("href", "#", null);
writer.writeAttribute("onclick", "return false;", null);
}
else {
setConfirmationScript(context, menuitem);
String onclick = menuitem.getOnclick();
//GET
if (menuitem.getUrl() != null || menuitem.getOutcome() != null) {
String targetURL = getTargetURL(context, (UIOutcomeTarget) menuitem);
writer.writeAttribute("href", targetURL, null);
if (menuitem.getTarget() != null) {
writer.writeAttribute("target", menuitem.getTarget(), null);
}
}
//POST
else {
writer.writeAttribute("href", "#", null);
UIComponent form = ComponentTraversalUtils.closestForm(context, menu);
if (form == null) {
throw new FacesException("MenuItem must be inside a form element");
}
String command;
if (menuitem.isDynamic()) {
String menuClientId = menu.getClientId(context);
Map<String, List<String>> params = menuitem.getParams();
if (params == null) {
params = new LinkedHashMap<String, List<String>>();
}
List<String> idParams = new ArrayList<String>();
idParams.add(menuitem.getId());
params.put(menuClientId + "_menuid", idParams);
command = menuitem.isAjax() ? buildAjaxRequest(context, menu, (AjaxSource) menuitem, form, params) : buildNonAjaxRequest(context, menu, form, menuClientId, params, true);
}
else {
command = menuitem.isAjax() ? buildAjaxRequest(context, (AjaxSource) menuitem, form)
: buildNonAjaxRequest(context, ((UIComponent) menuitem), form, ((UIComponent) menuitem).getClientId(context), true);
}
onclick = (onclick == null) ? command : onclick + ";" + command;
}
if (onclick != null) {
if (menuitem.requiresConfirmation()) {
writer.writeAttribute("data-pfconfirmcommand", onclick, null);
writer.writeAttribute("onclick", menuitem.getConfirmationScript(), "onclick");
}
else {
writer.writeAttribute("onclick", onclick, null);
}
}
}
encodeMenuItemContent(context, menu, menuitem);
writer.endElement("a");
}
@Override
protected boolean shouldRenderId(MenuElement element) {
if (element instanceof UIComponent) {
return shouldWriteId((UIComponent) element);
}
return false;
}
@Override
protected void encodeMarkup(FacesContext context, AbstractMenu abstractMenu) throws IOException {
// TODO Auto-generated method stub
}
@Override
protected void encodeScript(FacesContext context, AbstractMenu abstractMenu) throws IOException {
// TODO Auto-generated method stub
}
}
I'm using this since 2 years now and it works like a charm. But sad, they didn't fix it yet.
Upvotes: 1
Reputation: 980
I had the same exact problem and this is how i fixed it.
I tried with DynamicMenuModel
as @Kukeltje suggested and i added my own custom ids to the menu items. But when i added the contextmenu
for attribute for those ids i kept getting
Exception in page: Cannot find component for expression "leftmenu:sm_leftmenu_KEYSTORE_MANAGEMENT" referenced from "leftmenu:j_idt240:0:j_idt226".
So i reverted back to DefaultMenuModel
and added a contextMenu for everyMenuItem like:
for (AuthTransactions subMenus : subTransactions){
String subMenuName = getI18NString(subMenus.getLabelId(),locale);
DefaultMenuItem item = new DefaultMenuItem(
subMenuName == null ? subMenus.getTransactionCode()
: subMenuName);
item.setCommand("#{" + subMenus.getViewClassName()
+ defaultMethodName + "}");
item.setParam("menuTransactionCode",
subMenus.getTransactionCode());
item.setOnclick("PF('statusDialog').show()");
item.setTitle(subMenus.getTransactionCode());
item.setIcon(subMenus.getIcon());
userMenuModel.addElement(item);
ContextMenu ctxMenu = new ContextMenu();
ctxMenu.setFor("leftmenu:"+subMenus.getTransactionCode());
DynamicMenuModel ctxModel = new DynamicMenuModel();
DefaultMenuItem ctxItem = new DefaultMenuItem("Add/Remove"
+subMenus.getTransactionCode() + " to favorites","ui-icon-star");
ctxItem.setCommand("#{currentUserManager.toggleFavorite}");
ctxItem.setParam("menuTransactionCode", subMenus.getTransactionCode());
ctxItem.setAjax(true);
ctxItem.setOnclick("PF('statusDialog').show()");
ctxItem.setUpdate(":topBar");
ctxModel.addElement(ctxItem);
ctxMenu.setModel(ctxModel);
contextMenuList.add(ctxMenu);
}
But instead of using
<ps:menu id="sm_leftmenu" model="#{userMenuModel}" stateful="false" />
i created the menu with plain old html and <h:commandLink>
<c:if test="#{userMenuModel != null and userMenuModel.elements !=null and !empty userMenuModel.elements}">
<ul id="leftmenu:sm_leftmenu" class="layout-menubar-container">
<c:forEach var="item" items="#{userMenuModel.elements}">
<li id="#{item.id}" role="menuitem">
<h:commandLink value="#{item.value}" action="#{item.command}" title="#{item.title}" onclick="PF('statusDialog').show();" id="#{item.title}">
<f:param name="menuTransactionCode" value="#{item.title}" />
<i class="#{item.icon} yellow" style="float:left;padding-right:5px;"></i>
</h:commandLink>
</li>
</c:forEach>
</ul>
</c:if>
and added the conext menu below that like
<h:panelGroup rendered="#{currentUserManager.contextMenuList != null and !empty currentUserManager.contextMenuList}">
<h:dataTable value="#{currentUserManager.contextMenuList}" var="contextMenu">
<h:column>
<p:contextMenu binding="#{contextMenu}" model="#{contextMenu.model}" for="#{contextMenu.for}" styleClass="favCtx"/>
</h:column>
</h:dataTable>
</h:panelGroup>
Now the contextMenu will point to a <h:commandLink>
which is a jsf component as required by PrimeFaces documentation for contextMenu and the contextMenu for every Menu Item appeared.
and
Upvotes: 2
Reputation: 1
Please, have a look at the page:
PrimeFaces - New MenuModel.
Simple example:
MenuModel model = new DefaultMenuModel();
DefaultSubMenu subMenu = new DefaultSubMenu("some submenu");
DefaultMenuItem menuItem = new DefaultMenuItem("some item");
subMenu.addElement(menuItem);
model.addElement(subMenu);
model.generateUniqueIds();
Method generateUniqueIds() will spare you from creating and setting id strings yourself.
Upvotes: -1
Reputation: 12335
This is caused by the fact that when using the DefaultMenuModel
, the effective id's are not what you expect them to be. They get overridden (can be seen when looking at the generated code using a browser developer tool).
Instead of using a DefaultMenuModel
, use a DynamicMenuModel
. Contrary to the former, the latter allows you to set and keep the id's you assign. But you have to set id's on everything then, menuitem, submenu, groups etc...
The differences can be seen in the implementations of each.
Upvotes: 1