h: проблема выбора selectOneRadio внутри вложенных элементов ui: repeat - PullRequest
1 голос
/ 23 октября 2019

В настоящее время я пытаюсь реализовать динамическую страницу опроса, содержащую страницы, разделы и вопросы, определенные пользователем (часть более крупного приложения). Пользователь может определить разные типы вопросов, которые будут отображать разные компоненты (радио, текстовое поле, текстовое поле, список выбора, ...). Это приложение в настоящее время развернуто в Wildfly 16 / Java 8 / JSF 2.3 / Servlet 4.

Поскольку пользователь может определить определенный набор значений и связанных изображений для переключателя, мне нужно настроить вывод переключателя,Поэтому я выбрал новый атрибут group, доступный в selectoneradio. Использование этого нового атрибута вызывает ошибочное поведение при использовании внутри многоуровневого ui:repeat.

. Следующий пример был упрощен и создан для иллюстрации проблемы.

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>

Когда форма отправлена, сохраняются только последние переключатели и значения текстового поля. Если я заменю интерфейс selectItems: повторить

<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:selectOneRadio value="#{test.answerMap[question.key]}" layout="lineDirection" >
  <f:selectItems value="#{test.fixedValues.entrySet()}" var="item" itemLabel="#{item.key}" itemValue="#{item.value}" />
</h:selectOneRadio>

все работает нормально.

Мне действительно нужно использовать первый вариант, поэтому я могу добавитьнекоторые изображения для значений переключателей.
Я что-то упустил?

Ответы [ 2 ]

0 голосов
/ 24 октября 2019

Как и предложил @Selaron, я создал проблему под eclipse-ee4j / mojarra

Спасибо за помощь!

0 голосов
/ 23 октября 2019

Похоже, вы нашли проблему. Я попытался упростить пример следующим образом:

Мой управляемый компонент:

package my.pkg;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

@Named
@RequestScoped
public class MyBean {
    private List<String> items;

    private List<Number> numbers;

    private Map<String, Object> mapVal;

    @PostConstruct
    public void init() {
        mapVal = new LinkedHashMap<>();
        items = new ArrayList<>();
        numbers = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            items.add("group_" + i);
            numbers.add(i);
        }
    }

// getters/setters ...

}

Пример формы:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<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:c="http://xmlns.jcp.org/jsp/jstl/core">

<h:head />

<h:body>

    <h:form id="frm1">

        <ui:repeat var="grp" value="#{myBean.items}">

            <ui:repeat var="num" value="#{myBean.numbers}" varStatus="stat">

                <h:selectOneRadio id="radioButton" value="#{myBean.mapVal[grp]}"
                    group="myGroup_#{grp}">
                    <f:selectItem itemValue="#{num}" itemLabel="#{grp}: #{num}" />
                </h:selectOneRadio>

                <br />

            </ui:repeat>

            <hr />
        </ui:repeat>

        <h:commandButton value="submit" />

        <h:outputText value="myBean.mapVal: #{myBean.mapVal}" />
    </h:form>

</h:body>
</html>

Пользовательский интерфейс пример ввода:

enter image description here

После отправки я ожидаю, что результат будет:

myBean.mapVal: {group_0=0, group_1=1, group_2=2}

Но это:

myBean.mapVal: {group_2=2}

Переключатели из группы 0 и 1 теряют свой выбор.

Для некоторых входов я получаю ожидаемые результаты:

myBean.mapVal: {group_0=0, group_1=0, group_2=0}
myBean.mapVal: {group_0=1, group_1=1, group_2=1}
myBean.mapVal: {group_0=2, group_1=2, group_2=2}

... с использованием Mojarra 2.3.9.


Для MyFaces 2.3.4 я получаю ожидаемый результат для выбора на скриншоте выше:

{group_0=0, group_1=1, group_2=2}

, но это не помогает для других выборов:

1)
expected: {group_0=0, group_1=0, group_2=0}
but is: {group_2=0}

2)
expected: {group_0=0, group_1=0, group_2=2}
but is: {group_1=0, group_2=2}

3)
expected: {group_0=1, group_1=1, group_2=0}
but is: {group_1=1, group_2=0}

По моему мнению, мы должны сообщить о проблеме для Моджарры и (если еще не присутствует) для MyFaces.


Обходной путь

Если я заменим

    <ui:repeat var="grp" value="#{myBean.items}">

на

    <c:forEach var="grp" items="#{myBean.items}">

, то получу ожидаемые результаты для Mojarra и MyFaces.

...