【问题标题】:How to set converter properties for each row/item of h:dataTable/ui:repeat?如何为 h:dataTable/ui:repeat 的每一行/项目设置转换器属性?
【发布时间】:2011-11-23 17:44:24
【问题描述】:

我创建了一个自定义 ISO 日期时间Converter

public class IsoDateTimeConverter implements Converter, StateHolder {

    private Class type;
    private String pattern;

    private boolean transientValue = false;

    public void setType(Class type) {
        this.type = type;
    }

    public void setPattern(String pattern) {
        this.pattern = pattern;
    }

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) throws ConverterException {
        if (StringCheck.isNullOrEmpty(value)) {
            throw new ConverterException("value not specified");
        }

        try {
            if (IsoDate.class.equals(type)) {

                if (WebConst.ISO_DATE_NONE.equals(value)) {
                    return IsoDate.DUMMY;
                } else {
                    //TODO User spezifische TimeZone auslesen
                    return new IsoDate(value, TimeZone.getDefault().getID());
                }

            } else if (IsoTime.class.equals(type)) {

                if (WebConst.ISO_TIME_NONE.equals(value)) {
                    return IsoTime.DUMMY;
                } else {
                    //TODO User spezifische TimeZone auslesen
                    return new IsoTime(value, TimeZone.getDefault().getID());
                }

            } else if (IsoTimestamp.class.equals(type)) {

                if (WebConst.ISO_TIMESTAMP_NONE.equals(value)) {
                    return IsoTimestamp.DUMMY;
                } else {
                    //TODO User spezifische TimeZone auslesen
                    return new IsoTimestamp(value, TimeZone.getDefault().getID());
                }

            } else {
                throw new ConverterException("value not convertible");
            }
        } catch (Exception e) {
            throw new ConverterException(e.getMessage());
        }
    }

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) throws ConverterException {
        if (value == null) {
            throw new ConverterException("value not specified");
        }

        if (IsoDate.class.equals(value)) {
            IsoDate isoDate = (IsoDate) value;

            if (isoDate.isDummy()) {
                return WebConst.ISO_DATE_NONE;
            } else {
                //TODO User spezifische TimeZone auslesen
                return isoDate.toString(pattern, TimeZone.getDefault().getID(), false);
            }

        } else if (IsoTime.class.equals(value)) {
            IsoTime isoTime = (IsoTime) value;

            if (isoTime.isDummy()) {
                return WebConst.ISO_TIME_NONE;
            } else {
                //TODO User spezifische TimeZone auslesen
                return isoTime.toString(pattern, TimeZone.getDefault().getID(), false);
            }

        } else if (IsoTimestamp.class.equals(value)) {
            IsoTimestamp isoTimestamp = (IsoTimestamp) value;

            if (isoTimestamp.isDummy()) {
                return WebConst.ISO_TIMESTAMP_NONE;
            } else {
                //TODO User spezifische TimeZone auslesen
                return isoTimestamp.toString(pattern, TimeZone.getDefault().getID(), false);
            }

        } else {
            throw new ConverterException("value not convertible");
        }
    }

    @Override
    public Object saveState(FacesContext context) {
        return new Object[]{type, pattern};
    }

    @Override
    public void restoreState(FacesContext context, Object state) {
        type = (Class) ((Object[]) state)[0];
        pattern = (String) ((Object[]) state)[1];
    }

    @Override
    public boolean isTransient() {
        return transientValue;
    }

    @Override
    public void setTransient(boolean transientValue) {
        this.transientValue = transientValue;
    }
}

我在以下视图中使用Converter 作为<mh:IsoDateTimeConverter>

<p:dataTable value="#{imports.list}" var="item">
    <p:column>
        <h:outputText value="#{item.balanceDate}" immediate="true">
            <mh:IsoDateTimeConverter type="#{webConst.ISO_DATE_CLASS}" pattern="#{webConst.ISO_DATE_FORMAT}"/>
        </h:outputText>
    </p:column>
</p:dataTable>

问题是,当我第一次打开这个视图时,我的Converter 类中的所有属性只设置一次,然后数据表会根据初始属性呈现和转换值。

我希望属性是按行设置的。我怎样才能做到这一点?

【问题讨论】:

    标签: jsf datatable jsf-2 converters uirepeat


    【解决方案1】:

    到了这一点,您希望每次呈现数据表行时都会设置转换器的属性。这确实不是真的。当要构建视图时,JSF 将只为每个组件创建一个转换器实例,它不会在每次呈现行时创建/重置转换器。

    有几种方法可以让它工作。

    • 将动态属性作为组件的&lt;f:attribute&gt; 传递,并让Converter 拦截它。您可以在此处找到示例:JSF convertDateTime with timezone in datatable。然后可以将其用作

      <h:outputText value="#{item.balanceDate}">
          <f:converter converterId="isoDateTimeConverter" />
          <f:attribute name="pattern" value="#{item.pattern}" />
      </h:outputText>
      

    • 使用 EL 函数而不是 Converter。你可以在这里找到一个例子:Facelets and JSTL (Converting a Date to a String for use in a field)。然后可以将其用作

      <h:outputText value="#{mh:convertIsoDate(item.balanceDate, item.pattern)}" />
      

    • 将转换器和数据表的DataModel 绑定为同一个托管bean 的属性。这样,您将能够在返回之前根据行数据设置转换器的属性。这是一个基于标准 JSF 组件和标准 DateTimeConverter 的基本启动示例(它应该同样适用于 PrimeFaces 组件和您的自定义转换器):

      <h:dataTable value="#{bean.model}" var="item">
          <h:column>
              <h:outputText value="#{item.date}" converter="#{bean.converter}" />
          </h:column>
      </h:dataTable>
      

      @ManagedBean
      @ViewScoped
      public class Bean implements Serializable {
      
          private List<Item> items;
          private DataModel<Item> model;
          private DateTimeConverter converter;
      
          @PostConstruct
          public void init() {
              items = Arrays.asList(
                  new Item(new Date(), "dd-MM-yyyy"), 
                  new Item(new Date(), "yyyy-MM-dd"), 
                  new Item(new Date(), "MM/dd/yyyy"));
              model = new ListDataModel<Item>(items);
              converter = new DateTimeConverter();
          }
      
          public DataModel<Item> getModel() {
              return model;
          }
      
          public Converter getConverter() {
              converter.setPattern(model.getRowData().getPattern());
              return converter;
          }
      
      }
      

      Item 类只是一个具有两个属性 Date dateString pattern 的 bean)

      这会导致

      23-09-2011
      2011-09-23
      2011 年 9 月 23 日


    • 请改用OmniFaces &lt;o:converter&gt;。它支持属性中 EL 的渲染时间评估。另见the &lt;o:converter&gt; showcase example

      <h:outputText value="#{item.balanceDate}">
          <o:converter converterId="isoDateTimeConverter" pattern="#{item.pattern}" />
      </h:outputText>
      

    【讨论】:

    • 我使用f:convertNumber(与您的f:* 模式匹配)和someCurrencyCode 的动态属性currencyCode="#{rowVar.someCurrencyCode}" 是实体的属性,rowVar 来自p:dataTable var="rowVar"。那么我必须在这里编写自己的转换器吗?我已经有了 Primefaces 6.2,如果有帮助的话。或者 Omnifaces 看起来很简单。
    【解决方案2】:

    BalusC 的上述出色(一如既往)回答很全面,但并没有完全达到我的确切要求。就我而言,我需要将Converter 绑定到ui:repeat 中的每个迭代。根据重复的每个项目,我需要不同的Converter。不过,答案确实为我指明了正确的方向,所以我认为值得分享我的解决方案,以防它对其他人有所帮助。

    我使用Converter 将其所有工作委托给属性中指定的另一个Converter 对象,如BalusC 的第一个答案所示。请注意,如果您希望使用带参数的转换器,这根本没有帮助,它针对的是您希望将 Converter 绑定到重复对象的属性的情况。

    这是委托Converter。它也是一个Validator,其工作方式完全相同。

    // package and imports omitted for brevity
    @FacesConverter(value="delegatingConverter")
    @FacesValidator(value="delegatingValidator")
    public class Delegator implements Converter, Validator {
    
        // Constants ---------------------------------------------------------------
        private static final String CONVERTER_ATTRIBUTE_NAME = "delegateConverter";
        private static final String VALIDATOR_ATTRIBUTE_NAME = "delegateValidator";
    
        // Business Methods --------------------------------------------------------
        @Override
        public Object getAsObject(FacesContext context, UIComponent component, 
                String value) throws ConverterException {
            return retrieveDelegate(component, Converter.class, CONVERTER_ATTRIBUTE_NAME)
                    .getAsObject(context, component, value);
        }
    
        @Override
        public String getAsString(FacesContext context, UIComponent component, 
                Object value) throws ConverterException {
            return retrieveDelegate(component, Converter.class, CONVERTER_ATTRIBUTE_NAME)
                    .getAsString(context, component, value);
        }
    
        @Override
        public void validate(FacesContext context, UIComponent component, 
                Object value) throws ValidatorException {
            retrieveDelegate(component, Validator.class, VALIDATOR_ATTRIBUTE_NAME)
                    .validate(context, component, value);
        }
        
        private <T> T retrieveDelegate(UIComponent component, Class<T> clazz,
                String attributeName) {
            Object delegate = component.getAttributes().get(attributeName);
            if (delegate == null) {
                throw new UnsupportedOperationException("No delegate was specified."
                        + "  To specify, use an f:attribute tag with: name=\"" 
                        + attributeName + "\"");
            }
            if (!(clazz.isAssignableFrom(delegate.getClass()))) {
                throw new UnsupportedOperationException("The specified delegate "
                        + "was not a " + clazz.getSimpleName() + " object.  " +
                        "Delegate was: " + delegate.getClass().getName());
            }
            return (T) delegate;
        }
    }
    

    所以现在我希望在我的 ui:repeat 中使用此代码,但它不起作用:

    <h:outputText value="#{item.balanceDate}">
        <f:converter binding="#{item.converter} />
        <f:validator binding="#{item.validator} />
    </h:outputText>
    

    我可以改用这段代码,它工作正常:

    <h:outputText value="#{item.balanceDate}">
      <f:converter converterId="delegatingConverter"/>
      <f:validator validatorId="delegatingValidator"/>
      <f:attribute name="delegateConverter" value="#{item.converter}"/>
      <f:attribute name="delegateValidator" value="#{item.validator}"/>
    </h:outputText>
    

    假设重复项具有public Converter getConverter() 方法,Validator 类似。

    这样做的好处是,在别处使用的相同Converters 或Validators 可以重复使用而无需任何更改。

    【讨论】:

      猜你喜欢
      • 2014-01-03
      • 1970-01-01
      • 1970-01-01
      • 2011-12-03
      • 2011-02-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多