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

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

问题描述

我发现了一些类似的问题,例如这个,但是有很多方法可以做到这一点,这让我更加困惑.

我们正在读取一个 XML 文件.此 XML 包含有关需要显示的某些表单字段的信息.

所以我创建了这个自定义的DynamicField.java,它包含我们需要的所有信息:

public class DynamicField {私有字符串标签;//字段标签私人字符串字段密钥;//一些标识字段的键私有字符串字段值;//字段值私有字符串类型;//可以是输入、收音机、选择框等//吸气剂 + 吸气剂.}

所以我们有一个List.

我想遍历此列表并填充表单字段,使其看起来像这样:

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

然后将返回适当的 JSF 表单组件(即标签、输入文本)

另一种方法是只显示,然后返回一个带有表单元素的HtmlDataTable.(我认为这可能更容易做到).

哪种方法最好?有人可以向我展示一些显示如何创建它的链接或代码吗?我更喜欢完整的代码示例,而不是诸如您需要 javax.faces.component.UIComponent 的子类"之类的答案.

解决方案

因为起源实际上不是 XML,而是 Javabean,另一个答案不值得编辑成完全不同的风味(它可能仍然对其他人将来的参考有用),我将添加另一个基于 Javabean 来源的答案.

<小时>

当源是 Javabean 时,我基本上看到三个选项.

  1. 利用 JSF rendered 属性甚至 JSTL / 标签有条件地渲染或构建所需的组件.下面是一个使用 rendered 属性的例子:

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

</ui:repeat>

JSTL 方法的示例可以在 How to 中找到制作JSF复合组件的网格? 不,JSTL绝对不是一个坏习惯".这个神话是 JSF 1.x 时代遗留下来的,并且持续了太久,因为初学者没有清楚地了解 JSTL 的生命周期和权力.就这一点而言,只有在 #{bean.fields} 后面的模型(如上述代码段)至少在 JSF 视图范围内不会发生变化时,您才能使用 JSTL.另请参见 JSF2 Facelets 中的 JSTL... 有意义吗? 而是使用 binding 到 bean 属性仍然是一种不好的做法".

至于<ui:repeat><div>,你使用哪个迭代组件其实并不重要,你甚至可以使用 如您最初的问题,或特定于组件库的迭代组件,例如 .如有必要,重构大块代码到包含文件或标记文件.

关于收集提交的值,#{bean.values} 应该指向一个已经预先创建的Map.一个 HashMap 就足够了.如果控件可以设置多个值,您可能需要预填充地图.然后,您应该使用 List 作为值来预填充它.请注意,我希望 Field#getType() 是一个 enum,因为这可以简化 Java 代码端的处理.然后,您可以使用 switch 语句代替讨厌的 if/else 块.


  • postAddToView 事件侦听器中以编程方式创建组件:

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

    与:

    public void populateForm(ComponentSystemEvent event) {HtmlForm form = (HtmlForm) event.getComponent();对于(字段字段:字段){switch (field.getType()) {//如果是枚举则最简单.案例文本:UIInput input = new HtmlInputText();input.setId(field.getName());//必须是唯一的!input.setValueExpression("value", createValueExpression("#{bean.values['" + field.getName() + "']}", String.class));form.getChildren().add(input);休息;案例秘密:UIInput input = new HtmlInputSecret();//等等...}}}

    (注意:不要自己创建HtmlForm!使用JSF创建的,这个永远不会null)

    这保证了在正确的时刻填充树,并使 getter 不受业务逻辑的影响,并避免当 #{bean} 在更广泛的范围内时潜在的重复组件 ID"问题比请求范围(所以你可以安全地使用例如一个视图范围的 bean 在这里),并保持 bean 没有 UIComponent 属性,这反过来又避免了当组件被持有时潜在的序列化问题和内存泄漏可序列化 bean 的属性.

    如果您仍然使用 不可用的 JSF 1.x,请通过 将表单组件绑定到请求(不是会话!)范围的 bean绑定

    然后在表单的 getter 中懒惰地填充它:

    public HtmlForm getForm() {如果(形式==空){form = new HtmlForm();//...(继续上面的代码)}退货单;}

    在使用 binding 时,理解 UI 组件基本上是请求范围的并且绝对不应该在更广泛的范围内被分配为 bean 的属性是非常重要的.另见 'binding' 属性在 JSF 中是如何工作的?何时以及如何使用?


  • 使用自定义渲染器创建自定义组件.我不打算发布完整的示例,因为这些代码太多了,毕竟这些代码会非常紧耦合且特定于应用程序一团糟.


  • 应该清楚每个选项的优缺点.它从最容易和最好维护到最难和最难维护,随后也从最不可重用到最好重用.您可以选择最适合您的功能需求和当前情况的选项.

    应该注意的是,绝对没有只有在Java(方式#2)中是可能的,而在XHTML+XML(方式#1)中是不可能的.在 XHTML+XML 中一切皆有可能,就像在 Java 中一样.许多初学者在动态创建组件方面低估了 XHTML+XML(尤其是 和 JSTL),错误地认为 Java 将是唯一的"方式,而这通常只会结束用脆弱和混乱的代码.

    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天全站免登陆