Как создать динамические поля формы JSF - PullRequest
20 голосов
/ 18 августа 2010

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

Мы получаем файл 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<DynamicField>.

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

<h:dataTable value="#{dynamicFields}" var="field">
    <my:someCustomComponent value="#{field}" />
</h:dataTable>

Затем <my:someCustomComponent> вернет соответствующие компоненты формы JSF (т. Е. Label, inputText)

Другой подход заключается в том, чтобы просто отобразить <my:someCustomComponent>, и тогда будет возвращено HtmlDataTable с элементами формы. (Я думаю, что это, возможно, легче сделать).

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

Ответы [ 2 ]

54 голосов
/ 19 августа 2010

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


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

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

    <ui:repeat value="#{bean.fields}" var="field">
        <div class="field">
            <h:inputText value="#{bean.values[field.name]}" rendered="#{field.type == 'TEXT'}" />
            <h:inputSecret value="#{bean.values[field.name]}" rendered="#{field.type == 'SECRET'}" />
            <h:inputTextarea value="#{bean.values[field.name]}" rendered="#{field.type == 'TEXTAREA'}" />
            <h:selectOneRadio value="#{bean.values[field.name]}" rendered="#{field.type == 'RADIO'}">
                <f:selectItems value="#{field.options}" />
            </h:selectOneRadio>
            <h:selectOneMenu value="#{bean.values[field.name]}" rendered="#{field.type == 'SELECTONE'}">
                <f:selectItems value="#{field.options}" />
            </h:selectOneMenu>
            <h:selectManyMenu value="#{bean.values[field.name]}" rendered="#{field.type == 'SELECTMANY'}">
                <f:selectItems value="#{field.options}" />
            </h:selectManyMenu>
            <h:selectBooleanCheckbox value="#{bean.values[field.name]}" rendered="#{field.type == 'CHECKONE'}" />
            <h:selectManyCheckbox value="#{bean.values[field.name]}" rendered="#{field.type == 'CHECKMANY'}">
                <f:selectItems value="#{field.options}" />
            </h:selectManyCheckbox>
        </div>
    </ui:repeat>
    

    Пример подхода JSTL можно найти по адресу Как создать сетку из составного компонента JSF? Нет, JSTL абсолютно не подходит"плохая практика".Этот миф является пережитком эпохи JSF 1.x и продолжается слишком долго, потому что начинающие не совсем понимали жизненный цикл и возможности JSTL.Кстати, вы можете использовать JSTL только в том случае, если модель за #{bean.fields}, как в приведенном выше фрагменте, никогда не изменяется во время хотя бы области просмотра JSF.См. Также JSTL в JSF2 Facelets ... имеет смысл? Вместо этого использование binding для свойства бина по-прежнему является "плохой практикой".

    Что касается <ui:repeat><div>, то онона самом деле не имеет значения, какой итерационный компонент вы используете, вы даже можете использовать <h:dataTable>, как в исходном вопросе, или специфический итерационный компонент библиотеки компонентов, такой как <p:dataGrid> или <p:dataList>. При необходимости преобразовать большой кусок кода во включаемый или теговый файл .

    Что касается сбора представленных значений, #{bean.values} должен указывать на Map<String, Object>, который уже был предварительно создан.A HashMap достаточно.Вы можете заранее заполнить карту в случае элементов управления, которые могут устанавливать несколько значений.Затем вы должны предварительно заполнить его значением List<Object>.Обратите внимание, что я ожидаю, что Field#getType() будет enum, поскольку это облегчает обработку на стороне кода Java.Затем вы можете использовать оператор switch вместо неприятного блока if/else.


  2. Создать компоненты программно в postAddToView прослушивателе событий:

    <h:form id="form">
        <f:event type="postAddToView" listener="#{bean.populateForm}" />
    </h:form>
    

    С:

    public void populateForm(ComponentSystemEvent event) {
        HtmlForm form = (HtmlForm) event.getComponent();
        for (Field field : fields) {
            switch (field.getType()) { // It's easiest if it's an enum.
                case TEXT:
                    UIInput input = new HtmlInputText();
                    input.setId(field.getName()); // Must be unique!
                    input.setValueExpression("value", createValueExpression("#{bean.values['" + field.getName() + "']}", String.class));
                    form.getChildren().add(input);
                    break;
                case SECRET:
                    UIInput input = new HtmlInputSecret();
                    // etc...
            }
        }
    }
    

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

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

    Если вы все еще используете JSF 1.x, где <f:event> недоступен, вместо этого свяжите компонент формы с bean-объектом области запроса (а не сессией!) Через binding

    <h:form id="form" binding="#{bean.form}" />
    

    , а затем лениво заполняйтеэто в добытчикев форме:

    public HtmlForm getForm() {
        if (form == null) {
            form = new HtmlForm();
            // ... (continue with code as above)
        }
        return form;
    }
    

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


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


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

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

16 голосов
/ 18 августа 2010

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

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

persons.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>

persons.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, который отображается на <servlet-name> из FacesServlet и предполагает, что сам FacesServlet отображается на <url-pattern> из *.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, и этот фильтр сработает и преобразует persons.xml в persons.xhtml, используя persons.xsl и, наконец, поместит persons.xhtml туда, где ожидают JSF.

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

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

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

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

...