Primefaces 4, dynamic menu setCommand method - jsf

I'm trying primefaces 4 but there are no documentation around for the new MenuModel. Here, Optimus Prime wrote about the new menu system with a little example.
http://blog.primefaces.org/?p=2594
At this point, he wrote about a setCommand method:
This point to a save method (found in the pf4 showcase: http://www.primefaces.org/showcase/ui/menu/menu.xhtml):
After this introduction, here's the question/problem. I'm creating a dynamic menu from a bean but I don't understand how know the menu clicked by the user and do the right operation.
public void init() {
if (spBean == null) {
System.out.println("spBean is NULL!");
return;
}
for (ServiceProvider sp: spBean.getListaSP()) {
DefaultMenuItem item = new DefaultMenuItem(sp.getNome());
//item.setUrl("#");
item.setIcon("images/sps/" + sp.getImageId() + ".png");
item.setCommand("#{dockMenuBackingBean.setNewMenu}");
//
model.addElement(item);
System.out.println(sp.getNome());
}
}
public void setNewMenu() {
System.out.println("A menu was clicked BUT witch menu? Arghh!!");
//
}
What I want to do, is to change the spSelected in ServiceProviderBackingBean, like I've done in PF3.5:
<p:dock>
<c:forEach items="#{serviceProvidersBean.sps}" var="sp">
<p:menuitem
value="#{sp.spInstanceName}"
icon="/images/sps/#{sp.spInstanceId}.png"
update=":form:spDetail" >
<f:setPropertyActionListener
value="#{sp}"
target="#{serviceProvidersBean.spSelected}" />
</p:menuitem>
</c:forEach>
</p:dock>
Any help?
EDIT:
Actually I'm doing this, but I'm looking for a better and cleaner way to achieve this.
public void init() {
if (spBean == null) {
System.out.println("spBean is NULL!");
return;
}
for (ServiceProvider sp: spBean.getListaSP()) {
DefaultMenuItem item = new DefaultMenuItem(sp.getNome());
//item.setUrl("#");
item.setIcon("images/sps/" + sp.getImageId() + ".png");
String command = String.format("#{dockMenuBackingBean.setNewMenu('%d')}", spBean.getListaSP().indexOf(sp));
item.setCommand(command);
//
model.addElement(item);
System.out.println(sp.getNome());
}
}
public void setNewMenu(Object x) {
Integer selectedId = Integer.parseInt((String)x);
System.out.println("Menu changed " + Integer.toString(selectedId));
//
}

Setting command parameters by setParam(key,value) can be done like that:
In your menu generating bean:
DefaultMenuItem item = new DefaultMenuItem("display list");
item.setId("listMenuItem");
item.setCommand("#{myBean.displayList}");
item.setParam("listId", 1l);
In your managed bean containing the action:
public String displayList(ActionEvent event) {
MenuItem menuItem = ((MenuActionEvent) event).getMenuItem();
Long id = Long.parseLong(menuItem.getParams().get("listId").get(0));
findListBy(id);
}
Reading parameters seems to be a bit complicated. But ActionListeners aren't supported by Primefaces 4 MenuItems (because they aren't derived from UICommand any more) so params seem to be new new way.

Optimus here, use setParam(key,value). You need to update to trunk code though for this.

Related

How to make a dynamically generated URL redirect to another tab?

I'm using PrimeFaces 6.2
Hi everyone. As mentionned in the title, I need to open a new tab when a user clicks on a link (which is dynamically generated). I tried 2 solutions for now, and none of them works entirely :
1st solution : attributes url and target in PrimeFaces component
Facelet :
<p:contextMenu id="menuMesure" for="treeVArboParents" nodeType="3">
<p:menuitem value="OPL" url="#{arboParObjView.sessionService.lienUrl()}" target="_blank"/>
</p:contextMenu>
View :
#Named(value="arboParObjView")
#ViewScoped
public class ArboParObjView implements Serializable
{
#Inject
SessionService sessionService;
private TreeNode selectedNode //changes everytime a node is selected - both right and left clicks work
...some code here...
public void genererLienBirt() //called everytime the selectedNode value is changed
{
String libelle="";
if (selectedNode != null)
{
//code to find the id of the associated to the selected node.
//I need the id because I want to pass it as a parameter of the link
//And this part of code works well
sessionService.setIdMesure(idMesure);
}
}
}
Session Service :
#Named(value="sessionService")
#SessionScoped
public class SessionService implements Serializable
{
private LienURL lienUrl = new LienURL();
public String lienUrl()
{
String lien = "";
if (idMesure != null)
{
lien = lienUrl.getUrl();
lien += idMesure.toString();
return lien;
}
return "";
}
}
Bean :
public class LienURL
{
private String url;
public LienURL()
{
this.url = "myLink&BirtParameter="; //The base link with a Birt parameter waiting for the idMesure to be passed.
}
}
This solution doesn't work. When the user click on the menu item of the context menu component, it's opening a new tab but the opened page is the same as the one the user just leaved. I think that's because the PF's attribute url loads the url once (and the first time, my url is null because the idMesure isn't filled yet), and it just ignores the good link I try to pass after idMesure is filled.
2nd solution : use the redirect of the FacesContext
Facelet :
<p:contextMenu id="menuMesure" for="treeVArboParents" nodeType="3">
<p:menuitem value="OPL" actionListener="#{arboParObjView.sessionService.lienUrl()}" />
</p:contextMenu>
Service :
#Named(value="sessionService")
#SessionScoped
public class SessionService implements Serializable
{
private LienURL lienUrl = new LienURL();
public void lienUrl() throws IOException
{
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
String url = lienUrl.getUrl()+idMesure.toString();
ec.redirect(url);
}
}
The bean and the view don't change. It's the same as in the 1st solution.
The second solution works better than the first one. It is opening the good page with the good url, but in the same tab as the page where the user was. Is there a way to use the FacesContext redirect, but in another tab, as the target="_blank" do (the target only works with the url attribute) ? Or is there a way to make the url attribute read other urls than the first passed (which is null) ?
Thanks, and excuse my english.
Please use target="_blank" in p:menuitem only in second solution and it should work.
Below is updated code
<p:contextMenu id="menuMesure" for="treeVArboParents" nodeType="3">
<p:menuitem value="OPL" actionListener="#{arboParObjView.sessionService.lienUrl()}" target="_blank" />
</p:contextMenu>
and
public void lienUrl() throws IOException
{
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
String url = lienUrl.getUrl()+idMesure.toString();
ec.redirect(url);
}
Thanks to all the contributors for their help. Solution below :
View :
#Named(value="arboParObjView")
#ViewScoped
public class ArboParObjView implements Serializable
{
#Inject
private TreePodeService treePodeService;
private TreeNode selectedNode;
private Integer idMesure;
private String lienOplBirt;
...
//redirect to the generated link (called by the UI)
public void redirectOpl()
{
try {
FacesContext.getCurrentInstance().getExternalContext.redirect(lienOplBirt);
} catch (IOException e) {
e.printStackTrace();
}
}
//generate the Birt Link
public void genererLienBirt()
{
String libelle = "";
if (selectedNode != null)
{
libelle = selectedNode.getData().toString();
VArboParObjectifsParents mesureSelected = treePodeService.getPodeArboObjParentDao().findByLibelle(libelle);
idMesure = mesureSelected.getIdRoot();
}
lienOplBirt = "https://theLinkToPass"+"&RP_idMesure="+this.idMesure;
}
...
//Execute the genererLienBirt() method everytime selectedNode's value changes
public void setSelectedTreeNode(TreeNode selectedNode) {
if (selectedNode != this.selectedNode)
{
this.selectedNode = selectedNode;
genererLienBirt();
}
this.selectedNode = selectedNode;
}
}
Facelet (UI)
<p:menuitem value="OPL" includeViewParams="true" action="#{arboParObjView.redirectOpl()}" ajax="false" />

p:steps but enable click on all steps

I have primefaces steps using tag <p:steps> like below :
<p:steps activeIndex="3" styleClass="custom" readonly="false" style="padding: 20px;">
<p:menuitem value="step 1." actionListener="#{masterController.menuSales(preferencesController)}" update="mainPanel"/>
<p:menuitem value="step 2." actionListener="#{masterController.menuCustomer(preferencesController)}" update="mainPanel"/>
<p:menuitem value="step 3." actionListener="#{masterController.menuItem(preferencesController)}" update="mainPanel"/>
<p:menuitem value="step 4"/>
</p:steps>
And the result is like this :
I can click step 1 but not step 3 and 4. How can I enable click for all steps?
Wow, that's a nice question!
I've tried many things with the current API to accomplish it, but seems like it's not possible with our current options.
To solve this I wrote a custom renderer for the Steps component:
Most of the code below is the same from the PrimeFaces's GitHub. I just changed a few things to solve this specific problem.
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import org.primefaces.component.api.AjaxSource;
import org.primefaces.component.api.UIOutcomeTarget;
import org.primefaces.component.steps.Steps;
import org.primefaces.component.steps.StepsRenderer;
import org.primefaces.model.menu.MenuItem;
import org.primefaces.util.ComponentTraversalUtils;
public class CustomStepsRenderer extends StepsRenderer {
#Override
protected void encodeItem(FacesContext context, Steps steps, MenuItem item, int activeIndex, int index) throws IOException {
ResponseWriter writer = context.getResponseWriter();
String itemClass;
if (steps.isReadonly()) {
itemClass = (index == activeIndex) ? Steps.ACTIVE_ITEM_CLASS : Steps.INACTIVE_ITEM_CLASS;
} else {
if (index == activeIndex) {
itemClass = Steps.ACTIVE_ITEM_CLASS;
}
else {
itemClass = Steps.VISITED_ITEM_CLASS;
}
}
String containerStyle = item.getContainerStyle();
String containerStyleClass = item.getContainerStyleClass();
if (containerStyleClass != null) {
itemClass = itemClass + " " + containerStyleClass;
}
//header container
writer.startElement("li", null);
writer.writeAttribute("class", itemClass, null);
writer.writeAttribute("role", "tab", null);
if (containerStyle != null) {
writer.writeAttribute("style", containerStyle, null);
}
encodeMenuItem(context, steps, item, activeIndex, index);
writer.endElement("li");
}
#Override
protected void encodeMenuItem(FacesContext context, Steps steps, MenuItem menuitem, int activeIndex, int index) throws IOException {
ResponseWriter writer = context.getResponseWriter();
String title = menuitem.getTitle();
String style = menuitem.getStyle();
String styleClass = this.getLinkStyleClass(menuitem);
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);
}
writer.writeAttribute("class", styleClass, null);
if (style != null) {
writer.writeAttribute("style", style, null);
}
if (steps.isReadonly() || menuitem.isDisabled()) {
writer.writeAttribute("href", "#", null);
writer.writeAttribute("onclick", "return false;", null);
} else {
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, steps);
if (form == null) {
throw new FacesException("MenuItem must be inside a form element");
}
String command;
if (menuitem.isDynamic()) {
String menuClientId = steps.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, steps, (AjaxSource) menuitem, form, params)
: buildNonAjaxRequest(context, steps, 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) {
writer.writeAttribute("onclick", onclick, null);
}
}
writer.startElement("span", steps);
writer.writeAttribute("class", Steps.STEP_NUMBER_CLASS, null);
writer.writeText((index + 1), null);
writer.endElement("span");
Object value = menuitem.getValue();
if (value != null) {
writer.startElement("span", steps);
writer.writeAttribute("class", Steps.STEP_TITLE_CLASS, null);
writer.writeText(value, null);
writer.endElement("span");
}
writer.endElement("a");
}
Then, register this new renderer in your faces-config.xml file:
<render-kit>
<renderer>
<component-family>org.primefaces.component</component-family>
<renderer-type>org.primefaces.component.StepsRenderer</renderer-type>
<renderer-class>YOUR_PACKAGE.CustomStepsRenderer</renderer-class>
</renderer>
</render-kit>
Don't forget to change YOUR_PACKAGE to your CustomStepsRenderer package location.
After that, just build/re-deploy your application and everything should work fine:
Well, p:steps and p:wizard are the components in PrimeFaces component suite that represent or indicate the step(s) in a workflow to manage multiple steps of single form (step by step) for process simplication and can be used interchangably if you understand the usage properly (depending on the requirement).
For using p:steps component, you should ensure that the next step(s) will only be displayed when the current step is completely processed and required data is gathered.
Assume the process of online shopping, where payment processing is the last step and that will appear if and only if, you have any item in your cart and have provided the other information (if any).
The above scenario can also be implemented using p:wizard component. Where only current step is processed partially and next step is displayed if current step passes validations. However, p:wizard component has feasibility to override it's default behavior by controlling the wizard flow, rendering of custom previous & next buttons with custom action handlers and skipping of validation to view next steps.
menuform:I may answer your question a bit late, but I will post it so if other persona have the same problem, may it work for them.
I use JavaScript for the solution, so may it not the solution that you need:
// That is your code. I added ids to capture them with the DOM.
<p:steps activeIndex="3" styleClass="custom" readonly="false" style="padding: 20px;">
<p:menuitem value="step 1." actionListener="#{masterController.menuSales(preferencesController)}" update="mainPanel" id="step1"/>
<p:menuitem value="step 2." actionListener="#{masterController.menuCustomer(preferencesController)}" update="mainPanel" id="step2"/>
<p:menuitem value="step 3." actionListener="#{masterController.menuItem(preferencesController)}" update="mainPanel" id="step3"/>
<p:menuitem value="step 4" id="step4"/>
</p:steps>
// Now we can make the script
<script>
// First of all, we will capture all the steps with the DOM (you can also work with jQuery, but I will post the solution with DOM in case you do not have your code prepared to jQuery)
var step1 = document.getElementById("menuform:step1");
var step2 = document.getElementById("menuform:step2");
var step3 = document.getElementById("menuform:step3");
var step4 = document.getElementById("menuform:step4");
// Then, we are going to set the attributes href and onclick, and give them some style to make the elements look like proper links
step1.setAttribute("href", "[url]");
step1.setAttribute("onclick", true);
step1.style.cursor = "pointer";
step2.setAttribute("href", "[url]");
step2.setAttribute("onclick", true);
step2.style.cursor = "pointer";
step3.setAttribute("href", "[url]");
step3.setAttribute("onclick", true);
step4.style.cursor = "pointer";
step4.setAttribute("href", "[url]");
step4.setAttribute("onclick", true);
step4.style.cursor = "pointer";
</script>
Is important to change href and onclick (click event), because the element 'steps' change both of them, thats like them looks like when you inspect the code with the console:
- href="#"
- onclick="return false;"

Getter called multiple times - selected item not retained in JSP

I am working with JSF 1.2 and could use a little advice. I have an FMB (PlanFMB.java) that contains an array list of select items and a single selectedItem.
When the page loads, the getProjects() method is correctly called and displays the Projects as expected. Strange thing is though, the getSelectedItem() method is called three times (once of each project). Not sure if this is typical behavior:
SystemOut O getSelectedItem = null
SystemOut O getSelectedItem = null
SystemOut O getSelectedItem = null
Also, I have a command link in my JSP, that when clicked does the same thing, calls the getSelectedItem() method three times:
<h:commandLink action="#{planDocBean.classAction}" id="classActionENLink">
PlanFMB.java
String selectedItem = null;
private List<SelectItem> selectItems = null;
public String getSelectedItem() {
System.out.println("getSelectedItem = " + selectedItem);
return selectedItem;
}
public void setSelectedItem(String selectedItem) {
this.selectedItem = selectedItem;
System.out.println("setSelectedItem = " + selectedItem);
}
public List<SelectItem> getProjects() {
if (selectItems == null) {
selectItems = new ArrayList<SelectItem>();
selectItems.add(new SelectItem("Project1", "Project1"));
selectItems.add(new SelectItem("Project2", "Project2"));
selectItems.add(new SelectItem("Project3", "Project3"));
}
return selectItems;
}
<h:selectOneMenu id="items" value="#{planDocBean.selectedItem}">
<f:selectItems value="#{planDocBean.projects}" />
</h:selectOneMenu>
One last thing, the actual selected item is not retained in the select menu when the page reloads.
Any feedback much appreciated. Thanks!

How to show total number of errors within a page in primefaces message?

I'm using jsf 2.1 and Primefaces 3.3. I want to show the total number of errors within a page at the top of the page using <p:message>.
Please give me advice for how to do it and it will be helpful if you can explain with an example. Thank you in advance.
If you just want to count specific messages (e.g. only errors). you can do something like this:
#ManagedBean
#RequestScoped
public class MessageCount {
public int getFatal() {
return countMessages(FacesMessage.SEVERITY_FATAL);
}
public int getError() {
return countMessages(FacesMessage.SEVERITY_ERROR);
}
public int getWarn() {
return countMessages(FacesMessage.SEVERITY_WARN);
}
public int getInfo() {
return countMessages(FacesMessage.SEVERITY_INFO);
}
private int countMessages(FacesMessage.Severity severity) {
Iterator<FacesMessage> iterator = FacesContext.getCurrentInstance().getMessages();
int count = 0;
while (iterator.hasNext()) {
FacesMessage msg = iterator.next();
if (severity.compareTo(msg.getSeverity()) == 0) {
count++;
}
}
return count;
}
}
And in your jsf page:
<h:outputText value="#{messageCount.error}"/>
Why use <p:message/> ?
Try this
<h:outputText value="#{fn:length(facesContext.messageList)}"/>
There is no built in functionality for this. You can for example add inputHidden element to form, and add p:message for that input hidden field:
<p:messages for="justForErrorCount"/>
<h:inputHidden id="justForErrorCount"/>
In your backing bean you can do some check and add message:
if (FacesContext.getCurrentInstance().getMessageList() != null &&
!FacesContext.getCurrentInstance().getMessageList().isEmpty()) {
String message = "You have " +
FacesContext.getCurrentInstance().getMessageList().size() + " errors";
FacesContext.getCurrentInstance().addMessage("justForErrorCount",
new FacesMessage(FacesMessage.SEVERITY_ERROR, message);
}

How to bind data with checkbox list in JSF?

I've the following code in my edit user screen
<h:selectManyCheckbox id="selectedGroups" value="#{usersController.selectedGroups}">
<f:selectItems value="#{usersController.groupsList}" var="item" itemLabel="#{item.groupname}" itemValue="#{item.groupid}" />
</h:selectManyCheckbox>
I've user groups list with all the groups in it, and I've selectedGroups list with the groups that are enabled for the user. But, on the edit screen, they are not showing selected by default. What am I missing? Is this not the right way to bind selected many checkboxes?
An item value of the groupsList will be preselected only when equals() method has returned true for at least one item in the selectedGroups.
Assuming that groupid is a Long, then the selectedGroups should return a List<Long> containing the values to be preselected.
Following code will work perfectly for request scope bean...
xhml code....
<h:selectManyCheckbox binding="#{Page1.chk_1}">
<f:selectItem binding="#{Page1.chk_1_options}"/>
</h:selectManyCheckbox>
Java code....
HtmlSelectManyCheckbox chk_1 = new HtmlSelectManyCheckbox();
UISelectItems chk_1_options = new UISelectItems();
public HtmlSelectManyCheckbox getChk_1() {
return chk_1;
}
public void setChk_1(HtmlSelectManyCheckbox chk_1) {
this.chk_1 = chk_1;
}
public UISelectItems getChk_1_options() {
if (chk_1_options.getValue() == null) {
List<SelectItem> lst_chk_options = new ArrayList<SelectItem>();
lst_chk_options.add(new SelectItem(1, "Label1"));
lst_chk_options.add(new SelectItem(2, "Label2"));
chk_1_options.setValue(lst_chk_options);
}
return chk_1_options;
}
public void setChk_1_options(UISelectItems chk_1_options) {
this.chk_1_options = chk_1_options;
}
If you want for session scope then reply, because binding elements in session scope giving problems in some cases...

Resources