I would like to create a p:selectOneMenu item where the values and selected value display translated strings. So if the user has switched their language to french, the dropdown contents and selection will all be displayed in french, etc.
Currently I have a list of values to display, each of which knows what their string value is in each language (I realize this is probably an antipattern, will refactor once this is working.)
I've solved the issue of displaying the drop down elements in translated fashion using the following code. I've also created a subclass of the Omnifaces SelectItemsConverter class to get a translated string of each object.
<p:selectOneMenu
value="#{linkagecontroller.selectedLink}"
converter="linkageTypeSelectItemsConverter"
var="lnk">
<f:selectItems value="#{linkagecontroller.linkageTypes}"
var="item"/>
<p:column>
<div class="link-item-#{lnk.linkageTypeId}">
<h:outputText value="#{lnk.getKeyValue(language.localeCode)}" />
</div>
</p:column>
</p:selectOneMenu>
LinkageTypeSelectItemsConverter
#FacesConverter("linkageTypeSelectItemsConverter")
public class LinkageTypeSelectItemsConverter extends SelectItemsConverter implements Serializable {
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
final String locale = context.getApplication().evaluateExpressionGet(context,
"#{language.localeCode}",
String.class);
if (value instanceof LinkageTypeKey) {
return ((LinkageTypeKey) value).getKeyValue(locale);
} else {
return super.getAsString(context, component, value);
}
}
}
When the form is displayed, I can debug this converter, it's returning values translated, but the selected element ID is always being displayed in the default language, english. Thoughts?
The converter is for converting item values, not item labels.
You need to explicitly specify itemLabel.
<p:selectOneMenu
value="#{linkagecontroller.selectedLink}"
converter="omnifaces.SelectItemsConverter">
<f:selectItems value="#{linkagecontroller.linkageTypes}" var="item"
itemValue="#{item}" itemLabel="#{item.getKeyValue(language.localeCode)}" />
</p:selectOneMenu>
This is only a somewhat strange approach of localization. You usually put translations in a resource bundle instead of in the model itself. It could even be done without a converter if it's an enum.
See also:
How to use enum values in f:selectItem(s)
Localizing enum values in resource bundle.
Related
I have this select:
<p:selectOneMenu value="#{bean.val}">
<f:selectItem itemValue="X" itemLabel="Select" />
<f:selectItems value="#{bean.getVals()}" />
<p:ajax update="wrapper" />
</p:selectOneMenu>
And this is the wrapper it updates:
<p:panel id="wrapper">
<p:panel rendered="#{bean.val == 'A' or bean.val == 'B'}">
<!-- insert your code here -->
</p:panel>
</p:panel>
The wrapper is outside the select, at the same level.
At the start, it's all ok. The wrapper is hidden.
If I select 'A' and then 'C', for example, the wrapper disappear. BUT, if I select 'A' or 'B' and 'X' again (the first "choice", Select), the wrapper does NOT disappear!
I have put a breakpoint inside the setter of bean.val. The setter in invoked for all the choices BUT for the first one the value inside the debugger is the same as the previous one!
BUT! If I remove the validator, it works!
This is the validator:
#FacesValidator(value="requiredSelect")
public class RequiredSelect implements Validator {
protected MessageUtil messageUtil = new MessageUtil();
#Override
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
String val = (String) value;
if ("X".equals(val)) {
FacesMessage msg = this.messageUtil.getDefaultValidationError();
throw new ValidatorException(msg);
}
}
}
Previously I used another validator:
#FacesValidator(value="requiredString")
public class RequiredString implements Validator {
protected MessageUtil messageUtil = new MessageUtil();
#Override
public void validate(
FacesContext context,
UIComponent component,
Object value
) throws ValidatorException {
String val = (String) value;
if (val == null || val.trim().isEmpty()) {
FacesMessage msg = this.messageUtil.getDefaultValidationError();
throw new ValidatorException(msg);
}
}
}
but it did not work, because the empty string was not saved to the backing bean at menu change. So, if, for example, you selected 'A' and then back '' (Select) and you try to submit the form, the select is signaled with errors, but the value of the select returns to be 'A'! So the bogus 'X' value.
I'm using Primefaces 3.4.1 and Mojarra 2.1.7
Your issue is a bit of a XY-problem. You are trying to hack your way out of creating a required p:selectOneMenu using the X select item. This requires you to create a validator. If you simply make the p:selectOneMenu required, and would use null as the itemValue of the placeholder, it prevents the need to handle the X, so you don't need a validator.
Selecting null will cause validation to fail. So, once a selection is made you no longer want to render the null placeholder. Normally one would use the hideNoSelectionOption attribute of the p:selectOneMenu. But this is not available in PrimeFaces 3.4.1. As you don't want to upgrade, you can add this behavior by creating a custom renderer for the SelectOneMenu.
Create a new class, say my.custom.MySelectOneMenuRenderer, and extend SelectOneMenuRenderer. In this you want to #Override the encodeInput method to something like:
protected SelectItem selectMeOption = new SelectItem(null, "Select");
#Override
protected void encodeInput(
FacesContext context, SelectOneMenu menu, String clientId,
List<SelectItem> selectItems, Object values, Object submittedValues,
Converter converter
) throws IOException {
String classes = menu.getStyleClass();
Pattern pattern = Pattern.compile("\\bnormalSelect\\b");
Matcher matcher = pattern.matcher(classes);
boolean isNormal = matcher.find();
Object value = menu.getValue();
// If there's not a class "normalSelect" and no value is set, add the
// null option as the first item
if (!isNormal) {
if (value == null) {
selectItems.add(0, selectMeOption);
}
else {
SelectItem firstOption = selectItems.get(0);
if (selectMeOption.equals(firstOption)) {
selectItems.remove(0);
}
}
}
super.encodeInput(
context, menu, clientId, selectItems, values, submittedValues,
converter
);
}
Add your custom renderer to the render-kit section in your faces-config.xml like:
<render-kit>
<renderer>
<component-family>org.primefaces.component</component-family>
<renderer-type>org.primefaces.component.SelectOneMenuRenderer</renderer-type>
<renderer-class>my.custom.MySelectOneMenuRenderer</renderer-class>
</renderer>
</render-kit>
View example:
<p:selectOneMenu value="#{bean.val}" layout="pageDirection">
<f:selectItems value="#{bean.vals()}" />
<p:ajax update="#{empty bean.val ? '#this' : ''}" />
</p:selectOneMenu>
For a working example see: https://github.com/jepsar/primefaces-test/blob/so_68457571/src/main/webapp/test.xhtml
See also:
How to sort f:selectItems in each p:selectOneMenu of my application?
Please have a look at the JSF Life-Cycle for example here:
https://www.torsten-horn.de/img/JSF-Lifecycle.gif
And you will note, that if you throw "Validator-Exceptions" the "Update Model Values" Phase is skipped. (Process Events forwards to render response).
Hence your value "X", which you consider invalid in your validator is never send to the bean, and therefore the wrapper won't vanish.
What you actually want is:
Define a proper "NoSelect-Option"
Make the field "required".
Then - upon submission - the required field will be considered empty and throw a validator-exception where you need it.
ps.: A neat way to just bypass this "Issue" is to use hideNoSelectionOption on the selectOneMenu - that way, the form starts with "Please select", but the user can't switch back to "Please select", once he made an choice:
<p:selectOneMenu value="#{bean.val}" hideNoSelectionOption="#{not empty bean.val}">
<f:selectItem itemValue="#{null}" itemLabel="Select" noSelectionOption="true" />
<f:selectItems value="#{bean.getVals()}" />
<p:ajax update="#this,wrapper" process="#this" />
</p:selectOneMenu>
Updated, added #this to p:ajax.
PS: this does not work with old versions of Primefaces that does not have hideNoSelectionOption
Ok, this is my solution, thanks to the dognose's answer first part:
Change the input to:
<p:selectOneMenu value="#{bean.val}" required="true">
<f:selectItem itemValue="" itemLabel="Select" />
<f:selectItems value="#{bean.getVals()}" />
<p:ajax update="wrapper" />
</p:selectOneMenu>
No need to itemValue="#{none}", since null strings are automatically transformed to empty string by default, by jboss or primefaces, I do not remember. No need of noSelectionOption="true" too (I do not understand its scope... the validation works even if I don't put it)
added to a package my.package the files messages.properties, with this content:
javax.faces.component.UIInput.REQUIRED = Error
javax.faces.component.UIInput.REQUIRED_detail = One or more fields are missing or wrong. Please check the form
Then I added a p:messages like this one:
<p:messages id="messages" closable="true" showDetail="true" />
Then, I used this trick to remove the duplicate messages.
I'm using Primefaces version 5.3 autocomplete in a web project, I have written the search method and converter for the java entities i am searching for. These all work well the selected entity from the autocomplete is set correctly in the backing bean using a p:ajax tab and initially the entity name, which i specify in the itemValue, is set in the text input.
When i then submit the form that includes this autocomplete the variable in the backing bean which i set from the autocomplete stays as it is which is intended but the text input on the autocomplete will display the entities id instead of the name as i'd specified on the itemValue. This is because it is calling the toString method in my converter but I want it still to display the name yet i need the converter..
Has anyone come across this issue that may be able to help?
I have found a thread else where that describe this behaviour but it is a couple years old now and doesn't have an answer.
This thread is:
http://forum.primefaces.org/viewtopic.php?f=8&t=37918
As it may explain it better than i have...
Any help is appreciated.
UPDATE: code added
Here is my autocomplete tag, complete method returns a java List of entity type.
<h:form>
<p:autoComplete id="autocomplete" value="#{bean.selectedEntity}"
completeMethod="#{bean.listOfPossibleEntities}"
itemValue="#{_e}" itemLabel="#{_e.name}" autocomplete="off"
minQueryLength="3" var="_e"
placeholder="Enter Entity Name Here"
converter="EntityConverter" forceSelection="true">
<p:ajax event="itemSelect" update="enclosingForm"/>
<p:column>
<h:outputText value="#{_e.name}" />
</p:column>
</p:autoComplete>
<p:commandButton update="#form" >
</h:form>
FacesConverter looks like as below, i use a DAO call to our database find the object for each id
private EntityDAO entityDAO = (EntityDAO)Component.getInstance("entityDAO");
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
Integer id = Integer.valueOf(value);
return entityDAO.findById(id,false);
}
#Override
public String getAsString(FacesContext fc, UIComponent uic, Object object) {
String result = "";
if(object != null) {
if(object instanceof Entity){
result = ""+ String.valueOf(((Entity) object).getEntityId());
}
}
return result;
}
So yes on update of the h:form after the entity on the autocomplete is selected and the submitting with the p:commandButton the value display or the entity will change from the entity name to the entity id.
Hope this helps further thanks.
I set up a selectOneMenu with POJOs and a converter, in a p:dialog, see the sources below. It does work, except that initially, when it is first displayed in not-dropped-down state, the first choice is selected, not the one corresponding to the bean value. If I save the state of the selectOneMenu without interacting with it at all, the initially selected first choice is saved and so the real value is overwritten, but if I select a differenct choice, it is saved properly. The bean value to which the selectOneMenu is bound can't be null.
I debugged the converter, and it turned out, that when the backing data is loaded and the dialog is refreshed and displayed, all of the choices go through the converter's getAsString(), plus the choice for the real bean value again. Still, the first choice gets actually selected and displayed in the selectOneMenu. When the dialog's form is commited, the actually selected choice goes through the converter's getAsObject(), regardless whether that was the wrongly selected initial value or the manually selected one.
Please advise what might be the problem.
The xhtml of the button that invokes the dialog, this is in a different form:
<p:commandButton id="toolbarEditButton"
value="Edit selected" update=":editMediaForm"
disabled="#{!contentManager.mediaSelected}"
actionListener="#{contentManager.editSelectedMedia}"
onclick="PF('editMediaWidget').show()" />
The xhtml of the dialog:
<p:dialog id="editMediaDialog" widgetVar="editMediaWidget"
modal="true" resizable="false" >
<h:form id="editMediaForm" >
<p:panelGrid rendered="#{contentManager.isMediaSelected()}" columns="2" >
... <!-- other properties of the selected element -->
<p:outputLabel value="Media type" />
<p:selectOneMenu value="#{contentManager.selectedMedia.mediaType}"
converter="#{mediaTypeConverter}">
<f:selectItems value="#{mediaTypeConverter.allMediaTypes}"
var="mt" itemLabel="#{mt.name}" itemValue="#{mt}" />
<p:ajax listener="#{contentManager.onMediaTypeChanged()}" />
</p:selectOneMenu>
</p:panelGrid>
</h:form>
</p:dialog>
The converter:
#Override
public Object getAsObject(FacesContext arg0, UIComponent arg1, String stringId) {
Long id = Long.valueOf(stringId);
for (MediaType mt : mediaTypes) {
if (mt.getPkid().equals(id)) {
return mt;
}
}
return null;
}
#Override
public String getAsString(FacesContext arg0, UIComponent arg1, Object mtObj) {
MediaType mt = (MediaType) mtObj;
return mt.getPkid().toString();
}
public List<MediaType> getAllMediaTypes() {
return mediaTypes;
}
Edit: the backing bean
#SessionScoped // javax.enterprise.context.SessionScoped
#Named("contentManager") // javax.inject.Named
public class ContentManager implements Serializable {
...
private List<Media> mediaList;
private Media selectedMedia = null;
...
public boolean isMediaSelected() {
if (selectedMedia == null) return false;
return true;
}
...
public void saveSelectedMedia() {
myDao.saveMedia(selectedMedia);
}
public void editSelectedMedia() {
// only for debugging, could be removed
}
}
The dialog is brought up and the form is updated by an edit button which is only available after an element is selected from a dataTable (selectedMedia). The update does seem to work, since the other properties of the selected element are properly updated and displayed in the dialog, so the bean value behind the selectOneMenu should be ok.
Update: of course I also examined the generated HTML. The <select> seems to be OK for me, it contains the proper values to be converted by the converter. (The selection is still wrong)
<select id="form:blah_input" name="form:blah_input" tabindex="-1">
<option value="1" selected="selected">1 - half_horizontal</option>
<option value="2">2 - half_vertical</option>
<!-- etc -->
</select>
The objects displayed in the SelectOneMenu have to have proper equals() method, not only the default Object#equals which is only true if they are the same object. This explains why the initial displayed value was always the first: the bean value never matched any of the possible values, so the SelectOneMenu component simply displayed the first one.
So the mistake was not in the JSF or backing bean code, but in the displayed domain objects' (MediaType) code. Adding the equals() method there solved the problem.
Here's a snippet of the form I'm trying to submit:
<h:outputText value="Employees"></h:outputText>
<h:selectManyListbox id="employees"
value="#{lookupControl.memberEmployees}"
converter="#{employeeConverter}">
<f:selectItems value="#{lookupControl.employees}"
var="emp"
itemLabel="#{emp.EMPLOYEE_NUMBER}"
itemValue="#{emp}"/>
</h:selectManyListbox>
<h:message id="employeesMsg"
for="employees"
errorStyle="color:red; display:block"
styleClass="errorMessage"/>
<h:outputText value="Project Lead"></h:outputText>
<h:selectOneListbox id="projectLead"
value="#{lookupControl.chosenLead}"
converter="#{employeeConverter}">
<f:selectItems value="#{lookupControl.employees}"
var="emp"
itemLabel="#{emp.EMPLOYEE_NUMBER}"
itemValue="#{emp}"/>
</h:selectOneListbox>
<h:message id="projectLeadMsg"
for="projectLead"
errorStyle="color:red; display:block"
styleClass="errorMessage"/>
And here's the converter employeeConverter:
#FacesConverter(value = "employeeConverter")
public class EmployeeConverter implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
// hack to get an em
HelperBean helper = FacesContext.getCurrentInstance().getApplication().evaluateExpressionGet(context, "#{helperBean}", HelperBean.class);
EntityManager em = helper.getEm();
System.out.println(value);
Employee tmp = DBHelper.findEmployee(em, value);
return tmp;
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
Employee tmp = (Employee) value;
return tmp.getEMPLOYEE_NUMBER();
}
}
The idea being that a "Project" (the object being created when this form is submitted) has a list of members (List<Employee>) and a team leader (Employee). When I try submitting the form, I get a message along the lines of:
Conversion Error setting value 'dbaccess.persistence.Employee[id=66666666]' for 'null Converter'.
It's saying it can't convert from a String to an Employee, but it worked for the field right above it. I'm a little confused.
The "null converter" is the exception that the converter instance cannot be found. Since you're referencing the converter as a managed bean by converter="#{employeeConverter}, it would only be found if it's annotated with #javax.faces.bean.ManagedBean
Add #ManagedBean(name = "employeeConverterBean") into class EmployeeConverter
#ManagedBean(name = "employeeConverterBean")
#FacesConverter(value = "employeeConverter")
public class EmployeeConverter implements Converter {
and use converter="#{employeeConverterBean}" instead of converter="#{employeeConverter}"
You are referring to a scoped variable in your converter= attribute, but #FacesConverter does not create a scoped variable. Rather, it registers your converter class with the converter ID you specify.
From the documentation for FacesConverter.value:
The value of this annotation attribute is taken to be the converter-id …
Remove the converter= attribute entirely from your h:selectManyListbox, and instead, add a nested f:converter element:
<h:selectManyListbox id="employees"
value="#{lookupControl.memberEmployees}">
<f:converter converterId="employeeConverter"/>
<f:selectItems value="#{lookupControl.employees}"
var="emp"
itemLabel="#{emp.EMPLOYEE_NUMBER}"
itemValue="#{emp}"/>
</h:selectManyListbox>
Update: I'd always taken the tag documentation literally, which says the attribute's value "must evaluate to javax.faces.convert.Converter". BalusC points out that a nested f:converter is not needed; passing a literal converter ID in the converter= attribute will work:
<h:selectManyListbox id="employees"
value="#{lookupControl.memberEmployees}"
converter="employeeConverter">
<f:selectItems value="#{lookupControl.employees}"
var="emp"
itemLabel="#{emp.EMPLOYEE_NUMBER}"
itemValue="#{emp}"/>
</h:selectManyListbox>
I had never felt comfortable doing that in the past, without a documented guarantee that it works. I just looked through the JSF specification and found this under its "Standard HTML RenderKit Tag Library" section:
The following action must be taken to handle the value of the converter property. If isLiteralText() on the converter property returns true, get the value of the property and treat it as a converterId by passing it as the argument to the createConverter() method of the Application instance for this webapp, then pass the created Converter to the setConverter() method of the component for this tag.
So yes, passing a converter-id in the converter= attribute is completely supported.
Iam new to JSF technology, currently in our project we are using JSF 2.0 with spring and hibernate integration.I have one doubt regarding h:selectOneMenu and f:selectItems.
From the Database i'm getting a list of UserBeans.I'm using like
<h:selectOneMenu id="users" value="#{MyBean.user}">
<f:selectItems value="#{MyBean.userList}" var="user" itemLabel="#{user.userName}" itemValue="#{user.userId}" />
</h:selectOneMenu>
Here user is of type UserBean and userList is the list of UserBeans.
In the view page it is showing correctly but in the backing bean when i select one item and click on submit button, it showing the selected user as NULL.
My doubt is i can only pass List of SelectItem objects for the f:selectItems or any other beans list ..??
Is there any other way to populate the list of UserBeans otherthan SelectItem for selectItems.
Thank you All,
Anil
You have set the user ID as value of the select items, but you seem to be trying to bind the value to a fullworthy User property in the bean. You need to bind it as an user ID.
<h:selectOneMenu id="users" value="#{MyBean.userId}">
<f:selectItems value="#{MyBean.userList}" var="user" itemLabel="#{user.userName}" itemValue="#{user.userId}" />
</h:selectOneMenu>
If your sole intent is to be able to select and set User instead of only the ID, then you need a Converter to convert between User and String. That's because HTTP/HTML doesn't understand Java objects. It only understands strings. Here's a kickoff example:
<h:selectOneMenu id="users" value="#{MyBean.user}">
<f:selectItems value="#{MyBean.userList}" var="user" itemLabel="#{user.userName}" itemValue="#{user}" />
</h:selectOneMenu>
with
#FacesConverter(forClass=User.class)
public class UserConverter implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
try {
return userService.findById(Long.valueOf(value));
} catch (SomeException e) {
throw new ConverterException(new FacesMessage(String.format("Cannot convert %s to User", value)), e);
}
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
return String.valueOf(((User) value).getId());
}
}
However, this is a pretty expensive job. I'd suggest to stick to passing ID around and obtain the real user in the bean's action method just once instead of letting the converter do it for every single item.