Как создать динамические поля формы JSF

Я нашел несколько похожих вопросов, например, , этот , однако существует так много способов, которыми это можно сделать, что это меня запутало.

Мы получаем XML файл, который мы читаем. Этот XML содержит информацию о некоторых полях формы, которые необходимо представить.

Итак, я создал этот пользовательский DynamicField.java , который содержит всю необходимую нам информацию:

public class DynamicField {
  private String label; // label of the field
  private String fieldKey; // some key to identify the field
  private String fieldValue; // the value of field
  private String type; // can be input,radio,selectbox etc

  // Getters + setters.
}

Итак, у нас есть List .

Я хочу перебрать этот список и заполнить поля формы, чтобы он выглядел примерно так:


    

вернет соответствующие компоненты формы JSF (т. е. label, inputText)

Другой подход - просто отобразить , а затем вернуть HtmlDataTable с элементами формы. (Я думаю, что это, возможно, легче сделать).

Какой подход лучше? Может кто-то показать мне некоторые ссылки или код, где он показывает, как я могу создать это? Я предпочитаю полные примеры кода, а не ответы типа «Вам нужен подкласс javax.faces.component.UIComponent ».

21
задан Community 23 May 2017 в 12:34
поделиться

2 ответа

Поскольку на самом деле источником является не XML, а Javabean, а другой ответ не заслуживает редактирования в совершенно другом вкусе (он все еще может быть полезен для будущих ссылок других), я добавлю еще один ответ, основанный на Javabean-оригинале.


Я вижу в основном три варианта, когда источником является Javabean.

  1. Использовать атрибут JSF rendered или даже JSTL / теги для условного рендеринга или создания нужного компонента(ов). Ниже приведен пример с использованием атрибута rendered:

    
     

    An example of JSTL approach can be found at How to make a grid of JSF composite component? No, JSTL is absolutely not a "bad practice". This myth is a leftover from JSF 1.x era and continues too long because starters didn't clearly understand the lifecycle and powers of JSTL. To the point, you can use JSTL only when the model behind #{bean.fields} as in above snippet does not ever change during at least the JSF view scope. See also JSTL in JSF2 Facelets... makes sense? Instead, using binding to a bean property is still a "bad practice".

    As to the

    , it really doesn't matter which iterating component you use, you can even use as in your initial question, or a component library specific iterating component, such as or . Refactor if necessary the big chunk of code to an include or tagfile.

    As to collecting the submitted values, the #{bean.values} should point to a Map which is already precreated. A HashMap suffices. You may want to prepopulate the map in case of controls which can set multiple values. You should then prepopulate it with a List as value. Note that I expect the Field#getType() to be an enum since that eases the processing in the Java code side. You can then use a switch statement instead of a nasty if/else block.


  2. Create the components programmatically in a postAddToView event listener:

    
     
    
    

    С:

    public void populateForm(ComponentSystemEvent event) {
     HtmlForm form = (HtmlForm) event.getComponent();
     for (Field field : fields) {
     switch (field.getType()) { // Проще всего, если это перечисление.
     case TEXT:
     UIInput input = new HtmlInputText();
     input.setId(field.getName()); // Должен быть уникальным!
     input.setValueExpression("value", createValueExpression("#{bean.values['" + field.getName() + "']}", String.class));
     form.getChildren().add(input);
     break;
     case SECRET:
     UIInput input = new HtmlInputSecret();
     // и т.д..
     }
     }
    }
    

    (Примечание: НЕ создавайте HtmlForm самостоятельно! используйте созданную в JSF, эта форма никогда не будет null)

    Это гарантирует, что дерево будет заполнено в нужный момент, и сохраняет геттеры свободными от бизнес-логики, и избегает потенциальных проблем с "дублированием ID компонента", когда #{bean} находится в более широкой области видимости, чем область видимости запроса (поэтому вы можете безопасно использовать, например, view scoped bean здесь). например, view scoped bean здесь), и сохраняет боб свободным от свойств UIComponent, что в свою очередь позволяет избежать потенциальных проблем с сериализацией и утечкой памяти, когда компонент хранится как свойство сериализуемого боба.

    Если вы все еще используете JSF 1.x, где недоступен, вместо этого привяжите компонент формы к бобу с привязкой к запросу (не сессии!) через binding

    
    

    А затем лениво заполните его в геттере формы:

    public HtmlForm getForm() {
     if (form == null) {
     form = new HtmlForm();
     // ... (продолжить код, как указано выше)
     }
     return form;
    }
    

    При использовании binding очень важно понимать, что компоненты UI в основном привязаны к запросу и не должны быть назначены в качестве свойства bean в более широкой области видимости. См. также Как работает атрибут 'binding' в JSF? Когда и как его следует использовать?


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


  4. Плюсы и минусы каждого варианта должны быть ясны. Он идет от наиболее простого и лучше всего сопровождаемого к наиболее сложному и наименее сопровождаемому, а затем также от наименее повторно используемого к наилучшему повторно используемому. Вам решать, какой вариант лучше всего соответствует вашим функциональным требованиям и текущей ситуации.

    Следует отметить, что нет абсолютно ничего, что было бы только возможно в Java (способ №2) и невозможно в XHTML+XML (способ №1). Все возможно в XHTML+XML так же хорошо, как и в Java. Многие начинающие недооценивают XHTML+XML (особенно и JSTL) в динамическом создании компонентов и ошибочно думают, что Java будет "единственным и неповторимым" способом, в то время как это обычно заканчивается хрупким и запутанным кодом.

    55
    ответ дан 29 November 2019 в 06:23
    поделиться

    Если источником является XML, я предлагаю использовать совершенно другой подход: XSL . Facelets основан на XHTML. Вы можете легко использовать XSL для перехода от XML к XHTML. Это можно сделать с помощью немного приличного фильтра , который срабатывает до того, как JSF выполнит свою работу.

    Вот начальный пример.

    person.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <persons>
        <person>
            <name>one</name>
            <age>1</age>
        </person>
        <person>
            <name>two</name>
            <age>2</age>
        </person>
        <person>
            <name>three</name>
            <age>3</age>
        </person>
    </persons>
    

    person.xsl

    <?xml version="1.0" encoding="UTF-8"?>
    
    <xsl:stylesheet 
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
        xmlns:f="http://java.sun.com/jsf/core"
        xmlns:h="http://java.sun.com/jsf/html">
    
        <xsl:output method="xml"
            doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN"
            doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"/>
    
        <xsl:template match="persons">
            <html>
            <f:view>
                <head><title>Persons</title></head>
                <body>
                    <h:panelGrid columns="2">
                        <xsl:for-each select="person">
                            <xsl:variable name="name"><xsl:value-of select="name" /></xsl:variable>
                            <xsl:variable name="age"><xsl:value-of select="age" /></xsl:variable>
                            <h:outputText value="{$name}" />
                            <h:outputText value="{$age}" />
                        </xsl:for-each>
                    </h:panelGrid>
                </body>
            </f:view>
            </html>
        </xsl:template>
    </xsl:stylesheet>
    

    JsfXmlFilter , который отображается на из FacesServlet и предполагает, что Сам FacesServlet отображается на из *. Jsf .

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException
    {
        HttpServletRequest r = (HttpServletRequest) request;
        String rootPath = r.getSession().getServletContext().getRealPath("/");
        String uri = r.getRequestURI();
        String xhtmlFileName = uri.substring(uri.lastIndexOf("/")).replaceAll("jsf$", "xhtml"); // Change this if FacesServlet is not mapped on `*.jsf`.
        File xhtmlFile = new File(rootPath, xhtmlFileName);
    
        if (!xhtmlFile.exists()) { // Do your caching job.
            String xmlFileName = xhtmlFileName.replaceAll("xhtml$", "xml");
            String xslFileName = xhtmlFileName.replaceAll("xhtml$", "xsl");
            File xmlFile = new File(rootPath, xmlFileName);
            File xslFile = new File(rootPath, xslFileName);
            Source xmlSource = new StreamSource(xmlFile);
            Source xslSource = new StreamSource(xslFile);
            Result xhtmlResult = new StreamResult(xhtmlFile);
    
            try {
                Transformer transformer = TransformerFactory.newInstance().newTransformer(xslSource);
                transformer.transform(xmlSource, xhtmlResult);
            } catch (TransformerException e) {
                throw new RuntimeException("Transforming failed.", e);
            }
        }
    
        chain.doFilter(request, response);
    }
    

    Выполняется http://example.com/context/persons.jsf , и этот фильтр сработает и преобразует person.xml в person.xhtml ] с помощью person.xsl и, наконец, поместите person.xhtml туда, где его ожидает JSF.

    Да, XSL требует некоторого обучения, но IMO - правильный инструмент для работы, поскольку источником является XML, а местом назначения также является XML.

    Чтобы выполнить отображение между формой и управляемым компонентом, просто используйте Map . Если вы назовете поля ввода так

    <h:inputText value="#{bean.map.field1}" />
    <h:inputText value="#{bean.map.field2}" />
    <h:inputText value="#{bean.map.field3}" />
    ...
    

    Представленные значения будут доступны с помощью Map ключей field1 , field2 , field3 , и т. д.

    16
    ответ дан 29 November 2019 в 06:23
    поделиться
    Другие вопросы по тегам:

    Похожие вопросы: