JSF2:How to bind string value to h:commandLink action - jsf

Hi i need to dyamically set h:commandLink action as a string value from bean side. Here explains my problem with code
MenuObject.java
public class MenuObject {
private String menuName;
private String menuAction;
public String getMenuName() {
return menuName;
}
public void setMenuName(String menuName) {
this.menuName = menuName;
}
public String getMenuAction() {
return menuAction;
}
public void setMenuAction(String menuAction) {
this.menuAction = menuAction;
}
}
MenuCreator.java
public class MenuCreator {
public List getMenu(){
List menuList = new ArrayList();
MenuObject menu1 = new MenuObject();
menu1.setMenuAction("accountController.beginSearch()");
menu1.setMenuName("Account");
menuList.add(menu1);
MenuObject menu2 = new MenuObject();
menu2.setMenuAction("companyController.beginSearch()");
menu2.setMenuName("Company");
menuList.add(menu1);
return menuList;
}
main.xhtml
<ui:repeat value="#{menuCreator.menu}" var="subMenu">
<li class="glyphicons cogwheels"><h:commandLink action="#{subMenu.menuAction}"><i></i><span><h:outputText value="#{subMenu.menuName}"/></span></h:commandLink></li>
</ui:repeat>
Here what i need is i need to dynamically change commandlink action value with respect to bean string value (here it was menuAction). But in this situation i got following exception
javax.el.MethodNotFoundException: /WEB-INF/layouts/main.xhtml #137,85 action="#{menuCreator.menu}": Method not found: com.util.MenuObject#30c96021.menuAction()
at com.sun.faces.facelets.el.TagMethodExpression.invoke(TagMethodExpression.java:109)
at javax.faces.component.MethodBindingMethodExpressionAdapter.invoke(MethodBindingMethodExpressionAdapter.java:87)
at com.sun.faces.application.ActionListenerImpl.processAction(ActionListenerImpl.java:101)
at net.bull.javamelody.JsfActionListener.processAction(JsfActionListener.java:65)

You are trying to use the EL to return a value expression to be used as a method expression in a single expression. The JEE7 tutorial states:
9.3 Value and Method Expressions
The EL defines two kinds of expressions: value expressions and method expressions.
Value expressions can either yield a value or set a value. Method expressions reference
methods that can be invoked and can return a value.
You can achive this behaviour using javascript or use a library that offers you a dynamic menu component, like primefaces.

Maybe you can try something like Command Pattern. This is only an idea, I did not tested it.
In the xhtml:
<ui:repeat value="#{menuCreator.menu}" var="subMenu">
<li class="glyphicons cogwheels">
<h:commandLink action="#{invoker.callAction}" value="#{subMenu.menuName}">
<f:setPropertyActionListener target="#{invoker.action}" value="#{subMenu.action}" />
</h:commandLink>
</li>
</ui:repeat>
The command pattern:
/* The Command interface */
public interface Command {
String execute();
}
The menu item:
public class MenuObject {
private String menuName;
private Command action;
// Getters and setters...
}
The invoker:
#Named("invoker")
public class Invoker {
private Command action;
public String callAction(){
return action.execute();
}
}

Related

Automatically set value of a managed bean variable with JSF

I would like to pass an value to a managed bean under the hood. So I have this managed bean:
#ManagedBean(name = "mbWorkOrderController")
#SessionScoped
public class WorkOrderController {
// more attributes...
private WorkOrder workOrderCurrent;
// more code here...
public WorkOrder getWorkOrderCurrent() {
return workOrderCurrent;
}
public void setWorkOrderCurrent(WorkOrder workOrderCurrent) {
this.workOrderCurrent = workOrderCurrent;
}
}
It holds a parameter workOrderCurrent of the custom type WorkOrder. The class WorkOrder has an attribute applicant of type String.
At the moment I am using a placeholder inside my inputtext to show the user, what he needs to type inside an inputText.
<p:inputText id="applicant"
value="#{mbWorkOrderController.workOrderCurrent.applicant}"
required="true" maxlength="6"
placeholder="#{mbUserController.userLoggedIn.username}" />
What I want to do, is to automatically pass the value of mbUserController.userLoggedIn.username to mbWorkOrderController.workOrderCurrent.applicant and remove the inputText for applicant completely from my form.
I tried to use c:set:
<c:set value="#{mbUserController.userLoggedIn.username}" target="#{mbWorkOrderController}" property="workOrderCurrent.applicant" />
But unfortunatelly I get a javax.servlet.ServletException with the message:
The class 'WorkOrderController' does not have the property 'workOrderCurrent.applicant'.
Does anybody have an advice?
The class 'WorkOrderController' does not have the property 'workOrderCurrent.applicant'.
Your <c:set> syntax is incorrect.
<c:set value="#{mbUserController.userLoggedIn.username}"
target="#{mbWorkOrderController}"
property="workOrderCurrent.applicant" />
You seem to be thinking that the part..
value="#{mbWorkOrderController.workOrderCurrent.applicant}"
..works under the covers as below:
WorkOrderCurrent workOrderCurrent = mbWorkOrderController.getWorkOrderCurrent();
workOrderCurrent.setApplicant(applicant);
mbWorkOrderController.setWorkOrderCurrent(workOrderCurrent);
This isn't true. It works under the covers as below:
mbWorkOrderController.getWorkOrderCurrent().setApplicant(applicant);
The correct <c:set> syntax is therefore as below:
<c:set value="#{mbUserController.userLoggedIn.username}"
target="#{mbWorkOrderController.workOrderCurrent}"
property="applicant" />
That said, all of this isn't the correct solution to the concrete problem you actually tried to solve. You should perform model prepopulating in the model itself. This can be achieved by using #ManagedProperty to reference another bean property and by using #PostConstruct to perform initialization based on it.
#ManagedBean(name = "mbWorkOrderController")
#SessionScoped
public class WorkOrderController {
#ManagedProperty("#{mbUserController.userLoggedIn}")
private User userLoggedIn;
#PostConstruct
public void init() {
workOrderCurrent.setApplicant(userLoggedIn.getUsername());
}
// ...
}
Perhaps you could explain the context a bit more, but here's another solution. If you're navigating from another page, you can pass some identifier of work WorkOrder in the URL, like this http://host:port/context/page.xhtml?workOrderId=1.
Then, you can set the identifier in the managed bean like this:
<h:html>
<f:viewParam name="workOrderId" value="#{mbWorkOrderController.id}"/>
</h:html>
You'll have to add a new property to your bean:
public class WorkOrderController {
private long id;
public long getId() { return id; }
public void setId(long id) { this.id = id; }
// ...
}
And then, after the property has been set by JSF, you can find the work order in a lifecycle event:
<h:html>
<f:viewParam name="workOrderId" value="#{mbWorkOrderController.id}"/>
<f:event type="preRenderView" listener="#{mbWorkOrderController.findWorkOrder()}"/>
</h:html>
public class WorkOrderController {
private long id;
public long getId() { return id; }
public void setId(long id) { this.id = id; }
public void findWorkOrder() {
this.workOrderCurrent = null /* some way of finding the work order */
}
// ...
}
This strategy has the advantage of letting you have bookmarkable URLs.

Why the SelectCheckboxMenu + Map<Integer,List<Integer>> just turn the value inside the variable? [duplicate]

I have used the HashMap method for binding a list of checkboxes to a Map<String, Boolean> with success. This is nice since it allows you to have a dynamic number of checkboxes.
I'm trying to extend that to a variable length list of selectManyMenu. Being that they are selectMany, I'd like to be able to bind to a Map<String, List<MyObject>>. I have a single example working where I can bind a single selectManyMenu to a List<MyObject> and everything works fine, but whey I put a dynamic number of selectManyMenus inside a ui:repeat and attempt to bind to the map, I end up with weird results. The values are stored correctly in the map, as verified by the debugger, and calling toString(), but the runtime thinks the map's values are of type Object and not List<MyObject> and throws ClassCastExceptions when I try to access the map's keys.
I'm guessing it has something to do with how JSF determines the runtime type of the target of your binding, and since I am binding to a value in a Map, it doesn't know to get the type from the value type parameter of the map. Is there any workaround to this, other than probably patching Mojarra?
In general, how can I have a page with a dynamic number of selectManyMenus? Without, of course using Primefaces' <p:solveThisProblemForMe> component. (In all seriousness, Primefaces is not an option here, due to factors outside of my control.)
The question UISelectMany on a List<T> causes java.lang.ClassCastException: java.lang.String cannot be cast to T had some good information that I wasn't aware of, but I'm still having issues with this SSCE:
JSF:
<ui:define name="content">
<h:form>
<ui:repeat value="#{testBean.itemCategories}" var="category">
<h:selectManyMenu value="#{testBean.selectedItemMap[category]}">
<f:selectItems value="#{testBean.availableItems}" var="item" itemValue="#{item}" itemLabel="#{item.name}"></f:selectItems>
<f:converter binding="#{itemConverter}"></f:converter>
<f:validator validatorId="test.itemValidator"></f:validator>
</h:selectManyMenu>
</ui:repeat>
<h:commandButton value="Submit">
<f:ajax listener="#{testBean.submitSelections}" execute="#form"></f:ajax>
</h:commandButton>
</h:form>
</ui:define>
Converter:
#Named
public class ItemConverter implements Converter {
#Inject
ItemStore itemStore;
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
return itemStore.getById(value);
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
return Optional.of(value)
.filter(v -> Item.class.isInstance(v))
.map(v -> ((Item) v).getId())
.orElse(null);
}
}
Backing Bean:
#Data
#Slf4j
#Named
#ViewScoped
public class TestBean implements Serializable {
private static final long serialVersionUID = 1L;
#Inject
ItemStore itemStore;
List<Item> availableItems;
List<String> itemCategories;
Map<String, List<Item>> selectedItemMap = new HashMap<>();
public void initialize() {
log.debug("Initialized TestBean");
availableItems = itemStore.getAllItems();
itemCategories = new ArrayList<>();
itemCategories.add("First Category");
itemCategories.add("Second Category");
itemCategories.add("Third Category");
}
public void submitSelections(AjaxBehaviorEvent event) {
log.debug("Submitted Selections");
selectedItemMap.entrySet().forEach(entry -> {
String key = entry.getKey();
List<Item> items = entry.getValue();
log.debug("Key: {}", key);
items.forEach(item -> {
log.debug(" Value: {}", item);
});
});
}
}
ItemStore just contains a HashMap and delegate methods to access Items by their ID field.
Item:
#Data
#Builder
public class Item {
private String id;
private String name;
private String value;
}
ItemListValidator:
#FacesValidator("test.itemValidator")
public class ItemListValidator implements Validator {
#Override
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
if (List.class.isInstance(value)) {
if (((List) value).size() < 1) {
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_FATAL, "You must select at least 1 Admin Area", "You must select at least 1 Admin Area"));
}
}
}
}
Error:
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to java.util.List
Stacktrace snipped but occurs on this line:
List<Item> items = entry.getValue();
What am I missing here?
As hinted in the related question UISelectMany on a List<T> causes java.lang.ClassCastException: java.lang.String cannot be cast to T, generic type arguments are unavailable during runtime. In other words, EL doesn't know you have a Map<String, List<Item>>. All EL knows is that you have a Map, so unless you explicitly specify a converter for the selected values, and a collection type for the collection, JSF will default to String for selected values and an object array Object[] for the collection. Do note that the [ in [Ljava.lang.Object indicates an array.
Given that you want the collection type to be an instance of java.util.List, you need to specify the collectionType attribute with the FQN of the desired concrete implementation.
<h:selectManyMenu ... collectionType="java.util.ArrayList">
JSF will then make sure that the right collection type is being instantiated in order to fill the selected items and put in the model. Here's a related question where such a solution is being used but then for a different reason: org.hibernate.LazyInitializationException at com.sun.faces.renderkit.html_basic.MenuRenderer.convertSelectManyValuesForModel.
Update: I should have tested the above theory. This doesn't work in Mojarra when the collection behind collectionType is in turn wrapped in another generic collection/map. Mojarra only checks the collectionType if the UISelectMany value itself already represents an instance of java.util.Collection. However, due to it being wrapped in a Map, its (raw) type becomes java.lang.Object and then Mojarra will skip the check for any collectionType.
MyFaces did a better job in this in its UISelectMany renderer, it works over there.
As far as I inspected Mojarra's source code, there's no way to work around this other way than replacing Map<String, List<Long>> by a List<Category> where Category is a custom object having String name and List<MyObject> selectedItems properties. True, this really kills the advantage of Map of having dynamic keys in EL, but it is what it is.
Here's a MCVE using Long as item type (just substitute it with your MyObject):
private List<Category> categories;
private List<Long> availableItems;
#PostConstruct
public void init() {
categories = Arrays.asList(new Category("one"), new Category("two"), new Category("three"));
availableItems = Arrays.asList(1L, 2L, 3L, 4L, 5L);
}
public void submit() {
categories.forEach(c -> {
System.out.println("Name: " + c.getName());
for (Long selectedItem : c.getSelectedItems()) {
System.out.println("Selected item: " + selectedItem);
}
});
// ...
}
public class Category {
private String name;
private List<Long> selectedItems;
public Category(String name) {
this.name = name;
}
// ...
}
<h:form>
<ui:repeat value="#{bean.categories}" var="category">
<h:selectManyMenu value="#{category.selectedItems}" converter="javax.faces.Long">
<f:selectItems value="#{bean.availableItems}" />
</h:selectManyMenu>
</ui:repeat>
<h:commandButton value="submit" action="#{bean.submit}">
<f:ajax execute="#form" />
</h:commandButton>
</h:form>
Do note that collectionType is unnecessary here. Only the converter is still necessary.
Unrelated to the concrete problem, I'd like to point out that selectedItemMap.entrySet().forEach(entry -> { String key ...; List<Item> items ...;}) can be simplified to selectedItemMap.forEach((key, items) -> {}) and that ItemListValidator is unnecessary if you just use required="true" on the input component.

Parsing argument with JSF commandlink to managed bean [duplicate]

This question already has an answer here:
How to write a hardcoded string value inside EL expression #{ } in JSF?
(1 answer)
Closed 6 years ago.
I tried to parse argument with JSF to managed bean with ajax. my JSF code is this
<h:commandLink id="user" action="#{pageBean.setPage("user")}" >
user
<f:ajax execute="user" render="contentBody" />
</h:commandLink>
managed bean is this
#ManagedBean
public class PageBean {
private String path;
private String page;
public PageBean() {
}
#PostConstruct
public void init(){
path = "/WEB-INF/dashboard.xhtml";
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getPage() {
return page;
}
public void setPage(String page) {
this.page = page;
}
}
But when I run this I got following error. Why is that?
Error Parsing /WEB-INF/templete.xhtml: Error Traced[line: 37] Element type "h:commandLink" must be followed by either attribute specifications, ">" or "/>".
You are using double quotes in your attribute around user.
<h:commandLink id="user" action="#{pageBean.setPage("user")}" >
This results in templete.xml not beeing a valid XML-File.
Correct example line using single quotes (as proposed by #gWombat):
<h:commandLink id="user" action="#{pageBean.setPage('user')}" >
You should use simple quotes around the parameter user:
action="#{pageBean.setPage('user')}"

jsf selectonemenu selectitems from an enum

Ok, I think I've seen all the matches about this in StackOverflow and other Internet sites. My code is as follows:
Class:
public enum pruebaEnum{PRUEBA1, PRUEBA2, PRUEBA3};
private pruebaEnum prueba;
private pruebaEnum[] pruebaList;
public pruebaEnum getPrueba() {
return prueba;
}
public void setPrueba(pruebaEnum prueba) {
this.prueba = prueba;
}
public pruebaEnum[] getPruebaList() {
return pruebaEnum.values();
}
public void setPruebaList(pruebaEnum[] pruebaList) {
this.pruebaList = pruebaList;
}
JSF code:
<t:selectOneMenu id="categorization" value="#{BookManual.prueba}">
<t:selectItems Value="#{BookManual.pruebaList}"/>
</t:selectOneMenu>
The fact is I only get an empty dropbox. I don't know what I am doing wrong....
Attribute names are case sensitive. You used Value, but it's value.
By the way, you don't need a setter for <f:selectItems>. Get rid of it to save dead code and unnecessary future confusions because it's never invoked.

Dynamic action construction

I have a basic JSF question. I have a loop where I am trying to create mutile command link depending on the list value. and that command link will call the corresponding action from the list filed.
Basically I have this bean:
public class FavoriteTasks implements Serializable {
private static final long serialVersionUID = -8702569738872927728L;
private String key;
private String action;
private String widget;
private String name;
public FavoriteTasks(String key, String action, String widget, String name) {
super();
this.key = key;
this.action = action;
this.widget = widget;
}
And then populating it using properties file:
private void setUpFavTasks(UserUIPreferencesVO uiPref) {
List<String> fTaskList = uiPref.getFavoriteTasks();
favTasks =new ArrayList<FavoriteTasks>();
for(String var:fTaskList){
FavoriteTasks ft = new FavoriteTasks(var,
ConfigurationData.getValue(var+".action"),
ConfigurationData.getValue(var+".widget"),
ConfigurationData.getValue(var+".name"));
favTasks.add(ft);
}
}
Now the issue is the action is not understanding that it needs to get the value first and read that and then make the method call depending on the value.
<ui:repeat value="#{userSessionBean.favTasks}" var="favTasks" >
<li><ice:commandLink styleClass="shortcut-menu" action="#{favTasks.action}">
<f:param name="filterByContentWidget" value="#{favTasks.widget}" />
<f:param name="filterByContentGroup" value="#{favTasks.key}" />
<f:param name="menuName" value="#{favTasks.name}" />
<h:outputText value="#{msgs[favTasks.key]}" />
</ice:commandLink>
</li>
</ui:repeat>
action is trying to get favTasks.action and failing as there are no such method. it needs to read the value stored in favTasks.action and then go to the method that value is saying... for example if the favTasks.action = catalogHandler.showCatalog. it needs to invoke catalogHandler.showCatalog not favTasks.action
The action attribute is used to indicate the next view when you click the commandLink. It is a method expression that returns a String.
For example:
public String method() {
//do something
return "success";
}
and in your commandLink as
<ice:commandLink value="Submit" action="#{bean.method}" />
when clicked will take you to success.xhtml
Also you need to declare public setters/getters you just can't get/set any private variables that you have in your code:
private String key;
private String widget;
private String name;

Resources