I'm currently trying to implement a dynamic survey page containing pages, sections and questions defined by the user (part of a larger application). The user may defined different question types, which will render a different component (radio, textarea, textfield, selectionlist, ...). This app is currently deployed in Wildfly 16 / Java 8 / JSF 2.3 / Servlet 4.
Since the user may define a specific set of values and associated images for the radio button, I need to customize the radio button output. So my option was to use the new group attribute available in selectoneradio.
Using this new attribute is causing erratic behaviour when used inside a multi-level ui:repeat.
The following example was simplified and created in order to illustrate the problem.
Class
package test;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.annotation.PostConstruct;
import javax.faces.view.ViewScoped;
import javax.inject.Named;
@Named("test")
@ViewScoped
public class Test implements Serializable
{
private static final long serialVersionUID = 1L;
private static Map<String, Map<String, String>> questionMap = new TreeMap<>();
private static Map<String, String> fixedValuesMap = new TreeMap<>();
private Map<String, String> answerMap = new TreeMap<>();
private List<String> sections = Arrays.asList("Section_1", "Section_2", "Section_3", "Section_4");
static
{
fixedValuesMap.put("1", "Value 1");
fixedValuesMap.put("2", "Value 2");
fixedValuesMap.put("3", "Value 3");
fixedValuesMap.put("4", "Value 4");
fixedValuesMap.put("5", "Value 5");
Map<String, String> sec1questions = new TreeMap<>();
sec1questions.put("1", "Question 1");
sec1questions.put("2", "Question 2");
sec1questions.put("3", "Question 3");
questionMap.put("Section_1", sec1questions);
Map<String, String> sec2questions = new TreeMap<>();
sec2questions.put("4", "Question 4");
questionMap.put("Section_2", sec2questions);
Map<String, String> sec3questions = new TreeMap<>();
sec3questions.put("5", "Question 5");
questionMap.put("Section_3", sec3questions);
Map<String, String> sec4questions = new TreeMap<>();
sec4questions.put("6", "Question 6");
questionMap.put("Section_4", sec4questions);
}
public Test()
{
}
@PostConstruct
private void init()
{
answerMap.put("1", null);
answerMap.put("2", null);
answerMap.put("3", null);
answerMap.put("4", null);
answerMap.put("5", null);
answerMap.put("6", null);
}
public String getQuestionType(String index)
{
switch(index)
{
case "1":
return "RADIO_BUTTON";
case "2":
return "RADIO_BUTTON";
case "3":
return "RADIO_BUTTON";
case "4":
return "TEXT_AREA";
case "5":
return "RADIO_BUTTON";
case "6":
return "FREE_TEXT";
default:
return "FREE_TEXT";
}
}
public Map<String, String> getQuestions(String section)
{
return questionMap.get(section);
}
public Map<String, String> getAnswerMap()
{
return answerMap;
}
public Map<String, String> getFixedValues()
{
return fixedValuesMap;
}
public List<String> getSections()
{
return sections;
}
public void changeRadio(String questionKey, String questionValue)
{
answerMap.put(questionKey, questionValue);
}
public String submit()
{
for(Map.Entry<String, String> entry : answerMap.entrySet())
System.out.println("ENTRY: " + entry.getKey() + " - " + entry.getValue());
return null;
}
}
XHTML
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:a="http://xmlns.jcp.org/jsf/passthrough">
<h:head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
<meta name="description" content="My Test"/>
<link rel="stylesheet" href="#{request.contextPath}/resources/bootstrap/css/bootstrap.min.css" />
<title>My Test</title>
</h:head>
<h:body>
<div class="container-fluid">
<div class="row">
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-4">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">UI Repeat Test</h1>
</div>
<h:form role="form" prependId="false">
<div class="row mx-2">
<ui:repeat var="sec" value="#{test.sections}">
<div class="col-12">
<ui:repeat var="question" value="#{test.getQuestions(sec)}">
<div>
<div class="col-12">
<div class="form-group">
<label for="answer">#{question.key} - #{question.value}</label>
<div class="col-12">
<h:panelGroup rendered="#{test.getQuestionType(question.key) eq 'FREE_TEXT'}" layout="block">
<h:inputText value="#{test.answerMap[question.key]}" styleClass="form-control"/>
</h:panelGroup>
<h:panelGroup rendered="#{test.getQuestionType(question.key) eq 'TEXT_AREA'}" layout="block">
<h:inputTextarea value="#{test.answerMap[question.key]}" styleClass="form-control" rows="2" />
</h:panelGroup>
<h:panelGroup rendered="#{test.getQuestionType(question.key) eq 'RADIO_BUTTON'}" layout="block">
<table class="radio-label checkbox">
<tbody>
<tr>
<ui:repeat var="item" value="#{test.fixedValues.entrySet()}">
<td class="text-center grid-margin">
<h:selectOneRadio group="#{question.key}" value="#{test.answerMap[question.key]}" layout="lineDirection" styleClass="checkbox" >
<f:selectItem itemValue="#{item.key}" itemLabel="#{item.value}" />
</h:selectOneRadio>
</td>
</ui:repeat>
</tr>
</tbody>
</table>
</h:panelGroup>
</div>
</div>
</div>
</div>
</ui:repeat>
</div>
</ui:repeat>
</div>
<div class="row mx-2 my-2">
<div class="col-12">
<h:commandButton value="Test Me" type="submit" action="#{test.submit}" styleClass="btn btn-primary" />
</div>
</div>
</h:form>
</main>
</div>
</div>
<script src="#{request.contextPath}/resources/jquery/jquery.slim.min.js"></script>
<script src="#{request.contextPath}/resources/bootstrap/js/bootstrap.bundle.min.js"></script>
</h:body>
</html>
When the form is submitted only the last radio button and text field values are stored. If I replace the selectItems ui:repeat
<table class="radio-label checkbox">
<tbody>
<tr>
<ui:repeat var="item" value="#{test.fixedValues.entrySet()}">
<td class="text-center grid-margin">
<h:selectOneRadio group="#{question.key}" value="#{test.answerMap[question.key]}" layout="lineDirection" styleClass="checkbox" >
<f:selectItem itemValue="#{item.key}" itemLabel="#{item.value}" />
</h:selectOneRadio>
</td>
</ui:repeat>
</tr>
</tbody>
</table>
with
<h:selectOneRadio value="#{test.answerMap[question.key]}" layout="lineDirection" >
<f:selectItems value="#{test.fixedValues.entrySet()}" var="item" itemLabel="#{item.key}" itemValue="#{item.value}" />
</h:selectOneRadio>
everything works fine.
I really need to use the first option, so I can add some images to the radio button values.
Am I missing something?
