When I use f:selectItems to display items in a Map I cannot display the value of the Map item, only the key. f:selectItems does not use the itemLabel at all. When I use a List instead things work.
The following does make use of the itemLabel to display the "description" of an item in a List:
<h:selectOneMenu>
<f:selectItems value="#{testBB.testList}" var="s"
itemLabel="TEST #{s.description}" itemValue="#{TEST s.name}" />
</h:
selectOneMenu>
The following attempt to display the value of an item in a Map does not work. It displays the item's key, but not using the itemLabel attribute, as can be discerned by that lack of output of the "TEST" text.
<rich:select>
<f:selectItems value="#{testBB.testMap}" var="s"
itemLabel="TEST #{s.value}" itemValue="TEST #{s.key}" />
</rich:select>
The simple backing bean used follows:
public class TestBB {
private Map<String, String> testMap;
private List<TestItem> testList;
public TestBB() {
testMap = new HashMap<String, String>();
testMap.put("1_key", "Item One");
testMap.put("2_key", "Item Two");
testMap.put("3_key", "Item Three");
testList = new ArrayList<TestItem>();
testList.add( new TestItem("name_1", "description_1") );
testList.add( new TestItem("name_2", "description_2") );
testList.add( new TestItem("name_3", "description_3") );
}
public Map<String, String> getTestMap() {
return testMap;
}
public List<TestItem> getTestList() {
return testList;
}
}
So, any ideas on how to make this work, that is, how to effectively use a Map with selectItems?
Your question is sound, but the code makes it confusing and ambiguous. I'll just ignore your code in this answer.
As to the concrete question "How to use Map in <f:selectItems>", you need to realize that map keys are by default been used as item labels and that map values are by default been used as item values. You seem to expect it to be the other way round (honestly, I'd intuitively also expect that, but that was just a design desicion --the map key forces uniqueness and option labels should in UI perspective definitely be unique, but option values does not necessarily need to be unique).
So, this should do (note that I use LinkedHashMap here as it maintains the insertion order):
map = new LinkedHashMap<String, String>();
map.put("Label 1", "value1");
map.put("Label 2", "value2");
map.put("Label 3", "value3");
with
<f:selectItems value="#{bean.map}" />
If you want so swap the keys and values, then you should be iterating over Map#entrySet(). This works only when your environment supports EL 2.2 as you have to invoke it by a direct method invocation as there's no getter for that.
E.g.
map = new LinkedHashMap<String, String>();
map.put("value1", "Label 1");
map.put("value2", "Label 2");
map.put("value3", "Label 3");
with
<f:selectItems value="#{bean.map.entrySet()}" var="entry"
itemValue="#{entry.key}" itemLabel="#{entry.value}" />
See also:
Our selectOneMenu wiki page
Related
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();
}
}
I am trying to add SelectItem objects to Set collection. However, duplicate values are being added. Is there any way to prevent these duplicate values?
for(String s: list) {
Set<SelectItem> typeSet = new HashSet<SelectItem>();
typeSet.add(new SelectItem(s));
}
Just use Set<String> instead of Set<SelectItem>. The SelectItem#equals()/#hashCode() isn't implemented at all to take the actual value into account.
private Set<String> typeSet;
#PostConstruct
public void init() {
List<String> list = getItSomehow();
typeSet = new LinkedHashSet<String>(list);
}
It's since JSF2 just usable on <f:selectItems> as well.
<f:selectItems value="#{bean.typeSet}" />
See also:
Our selectOneMenu wiki page
I'm trying to iterate through a List of Map items, i.e. an ArrayList of HashMaps or something similar, and I'm trying to do this in primefaces datatable. This is basically what I'm trying to do:
<body>
<h:form>
<p:dataTable value="#{customerBean.list}" var="map">
<c:forEach items="#{map}" var="entry">
<p:column headerText="#{entry.key}">
#{entry.value}
</p:column>
</c:forEach>
</p:dataTable>
</h:form>
</body>
In this case, customerBean.list is a List<Map<String, String>> and entry is a Map<String, String>.
What I want to do, is create a dynamic amount of columns, based on the amount of entries in a Map<String, String> while using the map entry's key as a header name, and the value as the output. The c:forEach thing seems to work fine when I'm using a hardcoded Map<String, String>, but apparently it can't loop through the var of the p:dataTable. I assume that the program takes precaution to avoid having to loop through Maps of different sizes. So how can I make this work anyway? How can I create an arbitrary amount of columns based on the amount of entries in a Map? Note that I'm a 100% certain that every Map<String, String> is of equal size in my List<Map<String, String>>
EDIT:
Here's my bean source. The code works fine and everything, the problem is just with the loop not willing to go through my map:
#ManagedBean
#SessionScoped
public class CustomerBean {
private List<Map<String, String>> list = new ArrayList<Map<String, String>>();
private Mapper mapper = new Mapper();
public CustomerBean() {
list = mapper.all(); //gets data from database
}
public List<Map<String, String>> getList() {
return list;
}
public void setList(List<Map<String, String>> list) {
this.list = list;
}
}
The problem is unrelated to the Map usage in this context. The problem is that you're trying to get a #{map} variable that's only available when view is being rendered, but you're relying on its value at the moment when view is being built. The latter is performed on an earlier lifecycle phase, so it is basically unavailable when you demand it.
Still, tag handler, or view build tag, like <c:forEach>, is the only way to populate the variable number of columns, as <p:column> is assessed when component tree is being built.
Another thing worth noting is that the backing bean bound to <c:forEach> tag's property, such as items, must be anything but view scoped, like request scoped, otherwise it will be recreated upon every request which will bring unexpected/undesired results, as the demanded bean is not there when you try to access its properties. There are some other setup constellations solving this issue, but they're not the subject of discussion here.
<p:dataTable value="#{customerBean.list}" var="map">
<c:forEach items="#{forEachBean.columnsMap}" var="entry">
<p:column headerText="#{entry.key}">
#{map[entry.key]}
</p:column>
</c:forEach>
</p:dataTable>
Also worth noting that there is a helper <p:columns> component that does roughly the same.
there is a selectOneMenu in my example with a f:selectItems-attribute. The select-items are resolved from my bean like this:
<h:selectOneMenu value="#{bean.value}">
<f:selectItems value="#{bean.selectItems}" var="obj" itemValue="#{obj}" itemLabel="#{obj.name}"/>
</h:selectOneMenu>
The method getSelectItems() in my bean looks like that:
public List<MyObject> getSelectItems() {
List<MyObject> list = new LinkedList<MyObject>();
MyObject obj = new MyObject("Peter");
list.add(obj);
return list;
}
The objects that are displayed are simple objects with a attribute "name".
Nothing special up to this point. But now i change my method to that:
public List<MyObject> getSelectItems() {
List<MyObject> list = new LinkedList<MyObject>();
MyObject obj = new MyObject("<script>alert('xss is bad');</script>");
list.add(obj);
return list;
}
The javascript doesn´t get escaped by MenuRenderer-Class and my page shows me the alert-message.
Is there any cause why the default value of the escape-attribute of SelectItem is "false"?
How can i fix that problem? (I use Mojarra 2.1.7)
The default should indeed not have been false. I've reported it as issue 2747.
In the meanwhile, add itemLabelEscaped="true" to escape it anyway.
<f:selectItems ... itemLabelEscaped="true" />
Note that this is only necessary when you're using GenericObjectSelectItems, i.e. when you're supplying a E[]/List<E>/Map<K, V> instead of List<SelectItem>/SelectItem[]. Also note that escaping is only absolutely mandatory when it concerns user-controlled input (which is fortunately very rarely the case in dropdown values).
Since EL version 2.2, the following value expression is allowed:
<h:outputText value="#{entry.getRow(column)}" />
Where column would be another variable. Eventually, what seemed to work so well on an outputText, I would like to reuse on an inputText:
<h:inputText value="#{entry.setRow(column)}" />
"setRow" is defined as follows:
public void setRow(String columnName, String content) {
// ...
}
My question is: Does that work? Or rather, I know that doesn't work, since I'm getting an error about how the requested "setRow" method does not exist. So, does what I am trying to do here work in general - and if so, how can it be done?
Thanks for any feedback and best regards
Pascal
That's indeed not a valid expression for a "set" operation. The value expression has to be a fullworthy bean property expression, but you're having there a bean method expression.
You can achieve the particular functional requirement using a Map instead.
private Map<String, String> columns = new HashMap<String, String>();
public Map<String, String> getColumns() {
return columns;
}
with
<h:inputText value="#{bean.columns[column]}" />
On form submit, EL will use Map#put() method to set the value (hence, no setter required for the map) which will then be available in the action method by iterating over the map entries.