JSF 2.1 selectoneMenu with custom converter - jsf

what I'm trying to achieve is pretty simple:
a selectoneMenu populated using an Array of an Enum.
I defined a custom converter, and i noticed a strange thing:
in the converter's method "getAsString" the parameter value in not an element of the array gestioneFlussiGieRRM.tipiReport but the array itself!
I haven't found anything similar googling for it.
So: what am I doing Wrong?
Thank you in advance.
My Page
<h:selectOneMenu value="#{gestioneFlussiGieRRM.tipo}" id="reportType">
<f:converter converterId ="tipoReport.converter" />
<f:selectItems value="#{gestioneFlussiGieRRM.tipiReport}" var="t" itemValue="#{t}" itemLabel="#{t.descrizione}" />
</h:selectOneMenu>
My Managed bean:
private TipoReport tipo = TipoReport.StorageFacilityReport;
/* getter and setter for tipo */
/** #return the tipiReport */
public TipoReport[] getTipiReport() {
return TipoReport.values();
}
The Enum
public enum TipoReport {
StorageFacilityReport(1, "Storage Facility Report"), StorageParticipantActivityReport(2,
"Storage Participant Activity Report"), StorageUnavailabilityReport(3, "Storage Unavailability Report");
private int tipo;
private String descrizione;
private TipoReport(int tipo, String descrizione) {
this.tipo = tipo;
this.descrizione = descrizione;
}
public int getTipo() {
return tipo;
}
public String getDescrizione() {
return descrizione;
}
}
EDIT:
I'm Using JSF 2.1.27 mojarra implementation

Related

How store label in select menu to bean and populate in input text field [duplicate]

I am working on a JSF page which has a dropdown based on List<SelectItem>:
<h:selectOneMenu value="#{bean.selectedItem}">
<f:selectItems value="#{bean.availableItems}" />
</h:selectOneMenu>
I need to get both the value and label of the currently selected item. Right now I only get the value. How can I get the label, too?
You can't. That's just how HTML works. You know, JSF is a HTML code generator. The JSF <h:selectOneMenu> generates a HTML <select><option> . The HTML <select> element will only send the value attribute of the selected <option> element. It will not send its label.
But that shouldn't be a big issue. You namely already know both the value and label in the server side, inside the #{bean.availableItems}. All you need to do to get the associated label is to get it by the value as key. I suggest to make it a Map which in turn can also be used in f:selectItems.
Basic kickoff example:
public class Bean {
private String selectedItem; // +getter +setter
private Map<String, String> availableItems; // +getter
public Bean() {
availableItems = new LinkedHashMap<String, String>();
availableItems.put("value1", "label1");
availableItems.put("value2", "label2");
availableItems.put("value3", "label3");
}
public void submit() {
String selectedLabel = availableItems.get(selectedItem);
// ...
}
}
with
<h:selectOneMenu value="#{bean.selectedItem}">
<f:selectItems value="#{bean.availableItems.entrySet()}" var="entry"
itemValue="#{entry.key}" itemLabel="#{entry.value}" />
</h:selectOneMenu>
and in result
<p>Selected label is #{bean.availableItems[bean.selectedItem]}</p>
An alternative is to wrap both name and value in a javabean object representing an entity and set the whole object as value, via a converter.
See also:
Our selectOneMenu wiki page
How to populate options of h:selectOneMenu from database?
Instead of Using Map I tried like this and it's perfectly working for me to get both ItemValue and ItemLabel in the same property by using "ItemValue" attribute in selectItems tag.How ever provided no extra commas in the ItemLabel(#asfas....i had the same problem u mentioned so i selected this approach).
<h:selectOneMenu value="#{company.issueDesc}" required="true" onchange="submit()">
<f:selectItem itemLabel="-- Select a Issue -- " itemValue="0"/>
<f:selectItems value="#{company.issueList}" var="model" itemLabel="#{model.IssueDesc}" itemValue="#{model.Code},#{model.IssueDesc}" >
</f:selectItems>
</h:selectOneMenu>
Basically IssueDesc is String type in Bean Company
public Class Company{
private String issueDesc; // getters and setters
private int code; // getters and setters
private List<T>issueList; // getters and setters.
public void getLblandVal(){
String desc=getIssueDesc();
String[] str_ary=desc.split(",");
String s1=str_ary[0];
String s2=str_ary[1];
// parse **s1** to int;
}
}
What if the the value should be Integer and label String and both are needed in backing bean. Using Map in bean doesn't work because JSF interprets the map key as label. Ideally it would be a LinkedHashMap and search the text by a number.
Seems upside down to search number (value) by a text (key). What if some implementation of JSF adds extra space to test or letter case changes for some reason. Then the value is not found from map.
This will do the trick.
private String getLabel(List<SelectItem> list, String selection) {
String label = "";
for (int i = 0; i < list.size(); i++) {
if(selection.equals((String)list.get(i).getValue())) {
label = (String)list.get(i).getLabel();
break;
}
}
return label;
}
In your example, you would pass in the availableItems as 'list' and selectedItem as 'selection'. This method will return the label value corresponding to the selectedItem.
The following approach may also be useful in getting value and label using List <SelectItem>:
Here, facade, statesFacade fetches list of states from database/enterprise bean.
In view (xhtml page):
<h:selectOneMenu id="statesSelectUi" value="#{applicationBean1.state}">
<f:selectItems value="#{applicationBean1.stateSelectItems}"/>
<f:ajax render="selectedItem" event="change" execute="statesSelectUi"/>
</h:selectOneMenu>
<br/>
<br/>
<h:outputText id="selectedItem" value="#{applicationBean1.selectedState}"/>
In the Managed Bean(applicationBean1.java):
private String state;
/**
* #return the stateSelectItems
*/
public List<SelectItem> getStateSelectItemsItems() {
stateSelectItems.add(new SelectItem("-1","--- Select state ---"));
int statesCount = statesFacade.count();
List<StateSelectItems> states;
states = statesFacade.findAll();
for (int i = 0; i < statesCount; i++) {
stateSelectItems.add(new SelectItem(states.get(i).getStateSlNo(), states.get(i).getStateName()));
}
return stateSelectItems;
}
public String getSelectedState(){
if("".equals(getState()) || getState() ==null){
return "";
}else{
return "Selected State : " + getStateSelectItems.get(Integer.parseInt(getState())).getValue()+", "++ getStateSelectItems.get(Integer.parseInt(getState())).getLabel();
}
}

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.

How to Compare the values JSF selectonemenu

I am developing a JSF application
I have 2 selectOnemenu controls and submit button.
I need to disable the button if the values of 2 fields are equal
<h:selectOneMenu id="from" value="#{currencyBean.history.fromCurrency}" >
<f:selectItems value="#{currencyBean.currency}" var="c" itemValue="#{c}" itemLabel="#{c.name}"/>
</h:selectOneMenu>
<p:outputLabel for="to" value="to:" />
<h:selectOneMenu id="to" value="#{currencyBean.history.toCurrency}" >
<f:selectItems value="#{currencyBean.currency}" var="c" itemValue="#{c}" itemLabel="#{c.name}"/>
</h:selectOneMenu>
<p:commandButton id="calculateButton" value="Convert" update=":convert :historyPanel" onstart="PF('statusDialog').show()" onsuccess="PF('statusDialog').hide()" validateClient="true" ajax="true" action="#{currencyBean.Calculate()}" />
I tried to use onchange with ajax but everytime I change one dropdown the value of the second drowpdown became null in the backbean so I cannot read it.
Here is my backbean
#Named(value = "currencyBean")
#RequestScoped
public class CurrencyBean implements Serializable {
/**
* Creates a new instance of CurrencyBean
*/
private History history;
private Currency currency;
private Date currentDate;
#Inject
private Loginbean loginBean;
private List<History> historyList;
public List<History> getHistoryList() {
int userId = loginBean.getUserId();
if (userId != -1) {
return new HistoryHelper().GetHistoryListByUser(userId);
}
return null;
}
public CurrencyBean() {
history = new History();
}
public History getHistory() {
return history;
}
public void setHistory(History history) {
this.history = history;
}
public Currency[] getCurrency() {
return Currency.values();
}
public Date getCurrentDate() {
return new Date();
}
public void Calculate() {
int userId = loginBean.getUserId();
if (userId != -1) {
new CurrencyClient().Convert(userId, this.history);
}
}
}
any clue ?
My assumption is that all of your problems come from your managed bean scope. You have #Request scope so every request your managed bean will be removed from container, thus when you define onchange="submit()" (this is only my assumption because you haven't define how you implement onchange attribute) and you select value from one selectBox component values for this component is updated but the first one is still null. When you select second selectBox value updated from first selectBox doesn't exists anymore as managed bean has been removed after first request. You should try with wider scope for instance #ViewScope. If it doesn't help then further informations like implementation onchange attribute will be needed

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.

h:selectOneMenu with values from an enum becomes null after reload

I followed the advice from How to use enum values in f:selectItem(s) to build a h:selectOneMenu that takes the values of an enum. This works fine until I reload the page.
If I reload the page (Strg+R) what a user could do, the #{bean.relationship} property becomes null. All other properties, such as strings, numbers, etc. remain as they were before (Bean is #ViewScoped and #ManagedBean).
Here is the code from the JSF:
<h:selectOneMenu value="#{bean.relationship}">
<f:selectItems value="#{bean.relationshipTypes}"
var="types" itemValue="#{types}" itemLabel="#{types}" />
</h:selectOneMenu>
<h:inputText value="#{bean.name}" />
Here the code from the Enum:
public enum RelationshipType {
Family,
Friend
}
Here the code from the Bean:
private RelationshipType relationship; // plus getter & setter
private RelationshipType[] relationshipTypes;
private String name; // plus getter & setter
public RelationshipType[] getRelationshipTypes() {
return RelationshipType.values();
}
The enum is part of a larger entity. For ease of display a shortend version. Any idea?
#Geinmachi: Yet, other values stored in the bean are still there after the reload (e.g. name). So, why not for the property related to the enum?
I have used enums for data types
Enum
public enum Datatypes {
INT("Int"), FLOAT("Float"), DOUBLE("Double"), STRING("String"), DATE("Date"), DATETIME(
"DateTime"), BLOB("Blob");
private final String dataType;
private Datatypes(String dataType) {
this.dataType = dataType;
}
public String getDataType() {
return this.dataType;
}
#Override
public String toString() {
return dataType;
}
}
Bean
private List<Datatypes> datatypes = Arrays.asList(Datatypes.values());
public List<Datatypes> getDatatypes() {
return datatypes;
}
public void setDatatypes(List<Datatypes> datatypes) {
this.datatypes = datatypes;
}
xhtml
<p:selectOneMenu value="#{bean.dataType}">
<f:selectItem itemLabel="Select Data type"/>
<f:selectItems value="#{bean.dataTypes}" />
</p:selectOneMenu>
it working for me.

Resources