Im trying to bind selected values of SelectManyCheckbox to ArrayList, which is the value of my HashMap.
If my options are inside an ArrayList and selected options are aswell. i have no issue.
BUT when i want to set these selected options as a value of a map. it will populate an array of Object[] no matter what i try.
im using Mojarra 4.0.5.
@Component
@ViewScoped
@Getter
@Setter
public class TestView {
Map<String, ArrayList<Dto>> map;
ArrayList<String> strings;
ArrayList<Dto> options;
@PostConstruct
public void init() {
Dto dto1 = new Dto();
Dto dto2 = new Dto();
dto1.setAge(1);
dto1.setName("OPTION1");
dto2.setAge(2);
dto2.setName("OPTION2");
options = new ArrayList<Dto>();
options.add(dto1);
options.add(dto2);
map = new HashMap<String, ArrayList<Dto>>();
strings = new ArrayList<String>();
strings.add(new String("TEST1"));
strings.add(new String("TEST2"));
strings.forEach(s -> map.put(s, new ArrayList<Dto>()));
}
public void debug() {
System.out.println("debug");
ArrayList<ArrayList<Dto>> testarr = new ArrayList<ArrayList<Dto>>();
map.forEach((k,v)->testarr.add(v));
}
}
public class Dto {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
@FacesConverter(value="myConverter")
public class MyConverter implements Converter<Dto> {
private Map<Dto, String> entities = new HashMap<Dto, String>();
@Override
public String getAsString(FacesContext context, UIComponent component, Dto value) {
synchronized (entities) {
if (!entities.containsKey(value)) {
String uuid = UUID.randomUUID().toString();
entities.put(value, uuid);
return uuid;
} else {
return entities.get(value);
}
}
}
@Override
public Dto getAsObject(FacesContext context, UIComponent component, String value) {
for (Map.Entry<Dto, String> entry : entities.entrySet()) {
if (entry.getValue().equals(value)) {
return entry.getKey();
}
}
return null;
}
}
<ui:repeat var="str" value="#{testView.strings}" varStatus="strStatus">
<ui:param name="indexStr" value="#{strStatus.index}" />
<p:selectManyCheckbox value="#{testView.map[str]}"
converter="myConverter" layout="responsive" columns="6">
<p:ajax process="@this" />
<f:selectItems value="#{testView.options}" var="item"
itemLabel="#{item.name}" itemValue="#{item}" />
</p:selectManyCheckbox>
</ui:repeat>
<p:commandButton value="debug" icon="pi pi-check" process="@this"
action="#{testView.debug()}" />
You're victim of type erasure in generics.
Basically, in raw Java source code you have Map<String, ArrayList<Dto>> map
.
But in the compiled output of that code it has become Map map
and information about String
and ArrayList<Dto>
is lost.
This is the same problem the EL expressions #{...}
are facing. When it tries to inspect the type of value="#{testView.map[str]}"
via ValueExpression#getType()
it retrieves java.lang.Object
not java.lang.ArrayList
.
Technically speaking, setting collectionType
attribute as follows must work
<p:selectManyCheckbox ... collectionType="java.util.ArrayList">
But to my surprise it indeed doesn't have any effect. It would only work if the java.lang.Object
was actually an instance of java.util.Collection
. I consider this a bug in Mojarra. I've fixed this via issue 5427.
By the way, your converter is not thread safe and it's not entirely memory efficient: Arguments against a generic JSF object converter with a static WeakHashMap. Take a look at OmniFaces SelectItemsConverter
.