如何创建动态 JSF 表单域 [英] How to create dynamic JSF form fields

查看:18
本文介绍了如何创建动态 JSF 表单域的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

I have found some similar questions like this one, however there are so many ways this can be done that it made me more confused.

We are getting an XML file that we are reading. This XML contains information on some form fields that needs to be presented.

So I created this custom DynamicField.java that has all the information we need:

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

So we have a List<DynamicField>.

I want to iterate through this list and populate the form fields so it looks something like this:

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

The <my:someCustomComponent> would then return the appropriate JSF form components (i.e. label, inputText)

Another approach would be to just display the <my:someCustomComponent> and then that would return an HtmlDataTable with form elements. (I think this is maybe easier to do).

Which approach is best? Can someone show me to some links or code where it shows how I can create this? I prefer complete code examples, and not answers like "You need a subclass of javax.faces.component.UIComponent".

解决方案

Since the origin is actually not XML, but a Javabean, and the other answer doesn't deserve to be edited into a totally different flavor (it may still be useful for future references by others), I'll add another answer based on a Javabean-origin.


I see basically three options when the origin is a Javabean.

  1. Make use of JSF rendered attribute or even JSTL <c:choose>/<c:if> tags to conditionally render or build the desired component(s). Below is an example using rendered attribute:

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

    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 <ui:repeat><div>, it really doesn't matter which iterating component you use, you can even use <h:dataTable> as in your initial question, or a component library specific iterating component, such as <p:dataGrid> or <p:dataList>. 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<String, Object> 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<Object> 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:

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

    With:

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

    (note: do NOT create the HtmlForm yourself! use the JSF-created one, this one is never null)

    This guarantees that the tree is populated at exactly the right moment, and keeps getters free of business logic, and avoids potential "duplicate component ID" trouble when #{bean} is in a broader scope than the request scope (so you can safely use e.g. a view scoped bean here), and keeps the bean free of UIComponent properties which in turn avoids potential serialization trouble and memory leaking when the component is held as a property of a serializable bean.

    If you're still on JSF 1.x where <f:event> is not available, instead bind the form component to a request (not session!) scoped bean via binding

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

    And then lazily populate it in the getter of the form:

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

    When using binding, it's very important to understand that UI components are basically request scoped and should absolutely not be assigned as a property of a bean in a broader scope. See also How does the 'binding' attribute work in JSF? When and how should it be used?


  3. Create a custom component with a custom renderer. I am not going to post complete examples since that's a lot of code which would after all be a very tight-coupled and application-specific mess.


Pros and cons of each option should be clear. It goes from most easy and best maintainable to most hard and least maintainable and subsequently also from least reuseable to best reuseable. It's up to you to pick whatever the best suits your functional requirement and current situation.

Noted should be that there is absolutely nothing which is only possible in Java (way #2) and impossible in XHTML+XML (way #1). Everything is possible in XHTML+XML as good as in Java. A lot of starters underestimate XHTML+XML (particularly <ui:repeat> and JSTL) in dynamically creating components and incorrectly think that Java would be the "one and only" way, while that generally only ends up in brittle and confusing code.

这篇关于如何创建动态 JSF 表单域的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆