Here is the scenario - I want to create a view which can be used from a number of other views to create and edit a specific type of object.
My application has an address entity which can be shared between other entities.
In the view which maintains an entity, I would like a button/link which navigates to the address edit view for the address linked to that entity.
Another view which handles a different entity also needs to be able to navigate to the address edit view with its address.
The address view would then navigate back to the calling view once editing is completed.
My problem is that I can't seem to find a way to pass the address entity from the first view into the address view.
I think I want some kind of conversation scope, but don't know how to get the address without knowing about the page bean that references it, but obviously my address view can only know about addresses.
I am using JSF2.1 (MyFaces/PrimeFaces) and CDI (OpenWebBeans) and CODI.
I'm sure I must be missing something simple. (Simple relative to JSF/CDI terms that is!)
Just pass the address ID as request parameter and have the target view to convert, validate and set it in the bean by <f:viewParam>.
E.g.
<h:link value="Edit address" outcome="addresses/edit">
<f:param name="id" value="#{address.id}" />
</h:link>
and then in addresses/edit.xhtml
<f:metadata>
<f:viewParam id="id" name="id" value="#{editAddressBacking.address}"
converter="#{addressConverter}" converterMessage="Bad request. Unknown address."
required="true" requiredMessage="Bad request. Please use a link from within the system." />
</f:metadata>
<h:message for="id" />
<h:form rendered="#{not empty editAddressBacking.address}">
<h:inputText value="#{editAddressBacking.address.street}" />
...
</h:form>
In order to navigate back to the original page, you could pass another request parameter.
<h:link value="Edit address" outcome="addresses/edit">
<f:param name="id" value="#{address.id}" />
<f:param name="from" value="#{view.viewId}" />
</h:link>
(where #{view} is the implicit object referring to the current UIViewRoot)
and set it by <f:viewParam> as well so that you can in the submit method of the edit address backing bean just return to it:
public String save() {
// ...
return from + "?faces-redirect=true";
}
See also:
Communication in JSF 2 - Processing GET request parameters
I think I have come up with a solution.
As I am using CODI I can leverage the ConversationGroup annotation.
I've created an emtpy interface AddressConversation, then added this to all the backing beans that need to show the address/addressEdit.xhtml view, as well as the backing bean for the addressEdit view.
I'm also using CODI view config so my action methods return ViewConfig derived class objects.
#Named
#ConversationScoped
#ConversationGroup(AddressConversation.class)
public class AddressView implements Serializable
{
private Class<? extends Views> fromView;
private Address editAddress;
private Address returnAddress;
// Getters/setters etc...
public Class<? extends Views> cancelEdit()
{
returnAddress = null;
return fromView;
}
}
So in a calling view I have (PrimeFaces commandLink)
<p:commandLink value="#{enquiryView.addressLinkText}" action="#{enquiryView.editAddress()}" immediate="true"/>
and in the backing bean EnquiryView I can #Inject an instance of AddressView in the correct conversation group, then set the address and return view properties when action method is called.
#Named
#ConversationScoped
#ConversationGroup(AddressConversation.class)
public class EnquiryView implements Serializable
{
#Inject #ConversationGroup(AddressConversation.class) private AddressView addrView;
public Class<? extends Views> editAddress()
{
addrView.setAddress(editEnq.getAddress());
addrView.setFromView(Views.Enquiry.EnquiryEdit.class);
return Views.Address.AddressEdit.class;
}
}
I can also observe the navigation in EnquiryView and update the enquiry entity when an address has been "saved" in the address edit view.
protected void onViewConfigNav(#Observes PreViewConfigNavigateEvent navigateEvent)
{
if (navigateEvent.getFromView() == Views.Address.AddressEdit.class &&
navigateEvent.getToView() == Views.Enquiry.EnquiryEdit.class)
{
onEditAddressReturn();
}
}
private void onEditAddressReturn()
{
if (addrView.getReturnAddress() != null) {
// Save pressed
editEnq.setAddress(addrView.getReturnAddress());
}
}
If you want another entity to be set when the address is OK, just give the adresss process the EL name of the bean you want to set :
<f:param name="targetBeanSetter" value="enquiryBean.adress" />
And in Java :
public String executeAndBack() {
int last = this.targetBeanSetter.lastIndexOf('.');
String base = this.targetBeanSetter.substring(0, last);
String property = this.targetBeanSetter.substring(last + 1);
Object yourEntityToSet = FacesContext.getCurrentInstance().getELContext().getELResolver().getValue(context.getELContext(), null, base);
try {
PropertyUtils.setSimpleProperty(yourEntityToSet, property, constructeurHolder.getConstructeur());
} catch (Throwable e) {
throw new RuntimeException(e.getMessage());
}
return from + "?faces-redirect=true";
}
If you only need access to the choosen Adress, without building linked objects, when you are back on the first page, just inject in EnquiryView using
#ManagedProperty(value="{address}")
Address adress;
Related
As I struggled for hours I finally found where those annoying ClassCastExceptions came from, which I thought were produced by Hibernate and it's enum-mapping.
But they came from my JSF view, where I passed a List from
<h:selectManyCheckbox value="#{createUserManager.user.roles}" ... >
<f:selectItems value="#{createUserManager.roles}"/>
</h:selectManyCheckbox>
back into my backing bean.
My data simply consists of the values of an enum:
public Role[] getRoles()
{
return Role.values();
} .
I was really shocked when I tested the setter of roles in the User-class and got this:
public void setRoles(List<Role> paramRoles) {
System.out.println(paramRoles.get(0) instanceof Role); //output: false
for(Role role : paramRoles){ ...} //crashes with ClassCastException
}
Changing List<Role> paramRoles to List<String> paramRoles worked perfectly.
How is this possible? Shouldn't those generics be type safe or is type erasure in connection with JSF killing the whole type safety thing?
Also shouldn't the return value of h:selectManyCheckbox be List<Role>, like I passed in via the f:selectItems?
The behaviour you are experiencing is fully expected. Moreover, it is related to java generics in the same way as to how HTTP works.
The problem
The HTTP part
The problem is that you don't fully understand how HTTP works. When you submit data by pressing the submit button, parameters of your generated by JSF <h:selectManyCheckbox> tag, as a bunch of <input type="checkbox" name="..." value="userRoleAsString"> checkboxes, will be sent as strings and retrived ultimately as request.getParameter("checkboxName"); also as strings. Of course, JSF has no idea how to construct you model object class, Role.
The generics part
As you know due to the fact that java chose type erasure for generics to provide for backwards compatibility, information about generic types is basically a compile-type artifact and is lost at runtime. So at runtime your List<Role> erases to a plain, good old List. And as far as EL is a runtime language that uses Java Reflection API to deal with your expressions / call methods, at runtime no such information is available. Taking into account the HTTP part, JSF does its best and assigns string objects to your list, as it's all it can implicitly do. If you are willing to tell JSF to do otherwise, you need to do that explicitly, i.e. by specifying a converter to know what type of object to expect in an HTTP request.
The JSF part: aftermath
JSF has a provided javax.faces.Enum converter and in indeed works, if EL knew of the compile-time generic type of your list, that is Role. But it doesn't know of it. It would be not necessary to provide for a converter in case your multiple selection would be done on a Role[] userRoles object, or if you used the unique selection like in <h:selectOneMenu> with a value bound to Role userRole. In these examples the built-in enum converter will be called automatically.
So, to get it work as expected you need to provide for a Converter that will 'explain' JSF what type of values does this list hold, and how to do the transformations from Role to String, and vice versa.
It is worth noting that this will be the case with any bound List<...> values within the multiple choice JSF components.
Points of reference on Stack Overflow
After the problem was examined and resolved I was wondering if no one faced it in the past and searched for some previous answers here. Not surprisingly, it was asked before, and of course the problem was solved by BalusC. Below are two most valuable point of reference:
JSF 2.0 use enum in selectMany menu;
How to make a dropdown menu of a enum in JSF;
How to create and use a generic bean for enums in f:selectItems?.
The test case and two examples of working converters
Below I provide for a test case entirely for your understanding: everything works as expected apart from the third <h:selectManyCheckbox> component. It's up to you to trace it fully to eliminate the issue altogether.
The view:
<h:form>
Many with enum converter
<!-- will be mapped correctly with Role object -->
<h:selectManyCheckbox value="#{q16433250Bean.userRoles}" converter="roleEnumConverter">
<f:selectItems value="#{q16433250Bean.allRoles}" var="role" itemLabel="#{role.name}" />
</h:selectManyCheckbox>
<br/>
Many with plain converter
<!-- will be mapped correctly with Role object -->
<h:selectManyCheckbox value="#{q16433250Bean.userRoles2}" converter="roleConverter">
<f:selectItems value="#{q16433250Bean.allRoles2}" var="role" itemLabel="#{role.name}" />
</h:selectManyCheckbox>
<br/>
Without any converter
<!-- will NOT be mapped correctly with Role object, but with a default String instead -->
<h:selectManyCheckbox value="#{q16433250Bean.userRoles3}">
<f:selectItems value="#{q16433250Bean.allRoles}" var="role" itemLabel="#{role.name}" />
</h:selectManyCheckbox>
<br/>
Without any converter + array
<!-- will be mapped correctly with Role object -->
<h:selectManyCheckbox value="#{q16433250Bean.userRoles4}">
<f:selectItems value="#{q16433250Bean.allRoles}" var="role" itemLabel="#{role.name}" />
</h:selectManyCheckbox>
<br/>
<h:commandButton value="Submit" action="#{q16433250Bean.action}"/>
</h:form>
The bean:
#ManagedBean
#RequestScoped
public class Q16433250Bean {
private List<Role> userRoles = new ArrayList<Role>();//getter + setter
private List<Role> userRoles2 = new ArrayList<Role>();//getter + setter
private List<Role> userRoles3 = new ArrayList<Role>();//getter + setter
private Role[] userRoles4;//getter + setter
public enum Role {
ADMIN("Admin"),
SUPER_USER("Super user"),
USER("User");
private final String name;
private Role(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
public Role[] getAllRoles() {
return Role.values();
}
public String action() {
return null;
}
}
The converters:
#FacesConverter("roleEnumConverter")
public class RoleEnumConverter extends EnumConverter {
public RoleEnumConverter() {
super(Role.class);
}
}
and
#FacesConverter("roleConverter")
public class RoleConverter implements Converter {
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if(value == null || value.equals("")) {
return null;
}
Role role = Role.valueOf(value);
return role;
}
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (!(value instanceof Role) || (value == null)) {
return null;
}
return ((Role)value).toString();
}
}
I'm trying to get a primefaces SelectOneMenu selected item's name from code:
This is my SelectOneMenu:
FacesContext.getCurrentInstance().getViewRoot().findComponent("formMain:somSelect");
The Component is found.
I already tried to cast it into a SelectOneMenu but I won't get a method like "getSelectedValue()" which is written in the user manual in "client side api".
I also tried:
FacesContext.getCurrentInstance().getViewRoot().findComponent("formMain:somSelect").getAttributes().get("label");
But this returns NPE.
<p:selectOneMenu id="somSelect" value="#{userManagerBean.somValue}" valueChangeListener="#{userManagerBean.somListener}" styleClass="selecters">
<f:selectItems value="#{userSelectBean.userList}" />
</p:selectOneMenu>
UserManagerBean.java
#ManagedBean
#RequestScoped
public class UserManagerBean {
private String somValue;
private String selectedUser;
private List<User> userData;
private List<User> users;
public UserManagerBean() {
}
public String getSomValue(){
return somValue;
}
public void setSomValue(String somValue){
this.somValue = somValue;
}
// Getter for Table Content
public List<User> getUserData() {
return userData;
}
Any ideas?
€: The problem is that the selected item is only returned in the getter if I call a method and an ajax update:
<p:ajax update="panelMain" listener="#{userManagerBean.changeEvent}" />
But I don't get an correct selected item on page load ( item = null ).
As you are using list of beans to populate f:selectItems you should add converter or provide itemValue attribute. In your case as value is String provide itemValue with some String identifier of your User bean:
<f:selectItems value="#{userSelectBean.userList}" var="u" itemValue="#{u.code}" itemLabel="#{u.name}"/>
Change properties code and name to those which you have in your User bean. Be shore that itemValue points to String as your value in backing bean is String.
I need to get input throug form and put the value in Bean field.
After that I want that by pressing a button Bean action will be triggered and when it finished
I need to move to another page by using href.
I tried this:
<div class="getname">
<h:form>
Enter name
<h:inputText value="#{bean.name}" />
<br/>
</h:form>
<h:commandButton value="Purchase" action="#{bean.sendName}"/>
<a href="name.jsf?id=#{bean.id}" />
</div>
I manage to move to the right name.jsf page (according to the ID) but the action is not triggered.
any ideas?
The commandButton component is what will submit the form values to their managed properties. To do this you must place it inside the <h:form> component.
Furthermore, you do not need a seperate link to navigate away from the page after the action is invoked. A managed bean Action method can return a String which is defines the navigation that FacesServlet will FORWARD to.
#ManagedBean("bean")
public class MyBean {
private String id;
private String name;
/// property accessors and other stuff
public String sendName() {
// do stuff
return "name.jsf?id=" + this.id;
}
}
When the action is complete this return value instructs to navigate you to that defined view.
Command Button and href are both different actions , click on command button will execute action and click on href will take user to the page name.jsf
<h:commandButton value="Purchase" action="#{bean.sendName}"/>
<a href="name.jsf?id=#{bean.id}" />
You need something like this in your bean
#ManagedBean("bean")
public class TestBean {
#ManagedProperty(value = "#{param.id}")
private String id;
private String name;
public String sendName() {
//do stuff
return "name.jsf?id=" + this.id;
}
}
You can also do this in your xhtml to get the same outcome,
<f:metadata>
<f:viewParam name="id" value="#{bean.id}" />
</f:metadata>
Above line will set the parameter id in bean from the request parameter id before your bean is initialized.
I have this f:viewParam that I try to bind validate and convert a userId into Player, and I got an unexpected results.
<f:metadata>
<f:viewParam name="userId" value="#{myBean.selectedPlayer}" converter="pConverter"
converterMessage="Bad Request. Unknown User" required="true"
requiredMessage="Bad Request. Please use a link from within the system" />
</f:metadata>
<h:body>
<p:messages id="msgs"/>
<h:form>
<ul>
<li>Harry</li>
<li>Tom</li>
<li>Peter</li>
</ul>
</h:form>
<h:form>
<h:panelGrid columns="2" rendered="#{not empty myBean.selectedPlayer}">
<h:outputText value="Id: #{myBean.selectedPlayer.id}"/>
<h:outputText value="Name: #{myBean.selectedPlayer.name}"/>
</h:panelGrid>
</h:form>
<h:form id="testForm">
<h:inputText value="#{myBean.text}"/>
<p:commandButton value="Switch" update=":msgs testForm"/>
<h:outputText value="#{myBean.text}" rendered="#{not empty myBean.text}"/>
</h:form>
</h:body>
My Converter look like this
#FacesConverter(value="pConverter")
public class PConverter implements Converter {
private static final List<Player> playerList;
static{
playerList = new ArrayList<Player>();
playerList.add(new Player(1, "Harry"));
playerList.add(new Player(2, "Tom"));
playerList.add(new Player(3, "Peter"));
}
#Override
public Object getAsObject(FacesContext fc, UIComponent uic, String value) {
if(value == null || !value.matches("\\d+")){
return null;
}
long id = Long.parseLong(value);
for(Player p : playerList){
if(p.getId() == id){
return p;
}
}
throw new ConverterException(new FacesMessage("Unknown userId: " + value));
}
#Override
public String getAsString(FacesContext fc, UIComponent uic, Object value) {
if(!(value instanceof Player) || value == null){
return null;
}
return String.valueOf(((Player)value).getId());
}
}
As I click the three link (Harry, Tom, Peter), the converter work great. It converter the id and bind the player back to my managed bean. I then type something in the text box, then click Switch, the first time it work fine, what I typed appear next to the button, but then I change what I type, and click Switch again, then error message appear Bad Request. Please use a link from within the system, which is the error message for required for f:viewParam. If I took the f:viewParam out then everything work fine. Surprisingly, if I switch from f:viewParam to o:viewParam (OmniFaces), then it work great.
That's because the <f:viewParam> runs on every single HTTP request, also on postbacks. It works in your case fine for plain GET links, because you're passing exactly that parameter in the links. It fails in your case for POST forms, because you aren't passing that parameter in the button. So it becomes null in the request parameter map and the required validator kicks in and hence this validation error.
To keep the <f:viewParam required="true"> happy on POST forms as well, you basically need to retain the initial request parameter by <f:param> in the command buttons/links.
<p:commandButton value="Switch" update=":msgs testForm">
<f:param name="userId" value="#{param.userId}" />
</p:commandButton>
The OmniFaces <o:viewParam>, which is designed to be used in combination with view scoped beans, has an additional check in the isRequired() getter (source code here):
#Override
public boolean isRequired() {
// The request parameter get lost on postbacks, however it's already present in the view scoped bean.
// So we can safely skip the required validation on postbacks.
return !FacesContext.getCurrentInstance().isPostback() && super.isRequired();
}
So, this skips the required validator on every postback (and additionally, it also skips setting the model value on every postback due to its stateless nature). That's why you don't see the validation error and you still have the proper model value (which isn't reset on every postback).
I have a weird problem with a custom #FacesConverter with JBoss-7.1.0.CR1b during an AJAX call in p:selectOneMenu (Primefaces 3.0).
The simplified Converter looks like this, there are no NPE or other exceptions in this class
#FacesConverter("MyConverter")
public class MyConverter implements Converter
{
public Object getAsObject(FacesContext fc, UIComponent uic, String value)
{
logger.debug("getAsObject value: "+value);
if (submittedValue.trim().equals("")) {return null;}
else
{
MyEjb ejb = new MyEjb();
ejb.setId(Long.parseLong(value()));
return ejb; //**** alternative with return null; ****
}
}
public String getAsString(FacesContext fc, UIComponent uic, Object value)
{
if (value == null || value.equals("")) {return "";}
else
{
MyEjb ejb = (MyEjb)value;
return ""+ejb.getId();
}
}
}
The converter is used in a p:selectOneMenu:
<h:form>
<p:selectOneMenu value="#{clientBean.selected}" converter="MyConverter">
<f:selectItems value="#{clientBean.all}" var="my"
itemLabel="#{my.name}" itemValue="#{my}"/>
<p:ajax listener="#{clientBean.changed}" />
</p:selectOneMenu>
</h:form>
That's not rocket engineering, the changed method simply makes a debug:
public void changed()
{
logger.info("changed() "+selected);
}
But now the annoying part: The changed() is never called with the code like above, but I get the converter invoked three times:
12:37:51,500 DEBUG getAsObject value: 35
12:37:51,502 DEBUG getAsObject value:
12:37:51,503 DEBUG getAsObject value:
If I change the p:selectOneMenu value="#{clientBean.selectedId}" to a long selectedId and don't use the Converter the method is called once. Even if I return null in getAsObject()the changed() is called (once). I don't assume it's Primefaces related, because I have the same behavior if I use h:selectOneMenu and f:ajax.
You should have a <p:messages />, <p:growl /> or <h:messages /> which is been ajax-updated in your view. You should also pay attention to warnings in server logs about possible missing faces messages. The chance is big that you're seeing the infamous Validation error: Value not valid validation error.
After conversion, JSF will validate if the submitted object is one of the available items as part of safeguard against tampered/hacked requests. JSF will do that by submittedObject.equals(oneOfAvailableObjects) for each of the available objects as you have there in <f:selectItems>. If nothing matches, then JSF will display this validation error.
In your particular case, the MyEjb class has apparently no equals() method or its implementation is broken. See also Right way to implement equals contract.