如何为 h:dataTable/ui:repeat 的每行​​/项目设置转换器属性?

发布于 2024-12-06 07:32:10 字数 4478 浏览 9 评论 0 原文

我创建了一个自定义 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 作为

<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 类中设置一次,然后数据表根据初始属性呈现并转换值。

我预计属性是按行设置的。我怎样才能实现这个目标?

I have created a custom ISO date time 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;
    }
}

And I use the Converter as <mh:IsoDateTimeConverter> in the following view:

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

The problem is, when I first open this view, all properties are set in my Converter class only once and then the datatable renders and converts the values based on initial properties.

I expected that the properties are set on a per-row basis. How can I achieve this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

段念尘 2024-12-13 07:32:10

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

有几种方法可以让它发挥作用。

  • 将动态属性作为组件的 传递,并让 Converter 拦截该属性。您可以在这里找到一个示例:JSF ConvertDateTime with timezone in datatable。然后可以将其用作

    
        
        
    
    

  • 使用 EL 函数而不是转换器。您可以在此处找到示例: Facelets 和 JSTL(将日期转换为字符串以在字段中使用)。然后可以将其用作

    
    

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

    
        
            
        
    
    

    <前><代码>@ManagedBean
    @ViewScoped
    公共类 Bean 实现可序列化 {

    私有列表<项目>项目;
    私有数据模型模型;
    私有 DateTimeConverter 转换器;

    @PostConstruct
    公共无效初始化(){
    项目 = Arrays.asList(
    新项目(新日期(),“dd-MM-yyyy”),
    新项目(新日期(),“yyyy-MM-dd”),
    新项目(新日期(),“月/日/年”));
    模型 = new ListDataModel(items);
    转换器=新的DateTimeConverter();
    }

    公共数据模型获取模型(){
    返回模型;
    }

    公共转换器 getConverter() {
    converter.setPattern(model.getRowData().getPattern());
    返回转换器;
    }

    }

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

    这会导致

    <块引用>

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


  • 使用 OmniFaces 代替。它支持属性中EL的渲染时间评估。另请参阅 展示示例。< /p>

    
        
    
    

To the point, you expected that the converter's properties are set every time a datatable row is rendered. This is indeed not true. JSF will create only one converter instance per component when the view is to be built, it will not create/reset the converter each time the row is rendered.

There are several ways to get it to work.

  • Pass the dynamic attributes as <f:attribute> of the component and let the Converter intercept on that. You can find an example here: JSF convertDateTime with timezone in datatable. This can then be used as

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

  • Use an EL function instead of a Converter. You can find an example here: Facelets and JSTL (Converting a Date to a String for use in a field). This can then be used as

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

  • Bind the converter and datatable's DataModel as a property of the same managed bean. This way you will be able to set the converter's properties based on the row data before returning it. Here's a basic kickoff example based on standard JSF components and standard DateTimeConverter (it should work equally good on PrimeFaces components and with your custom converter):

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

    with

    @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;
        }
    
    }
    

    (the Item class is just a bean with two properties Date date and String pattern)

    this results in

    23-09-2011
    2011-09-23
    09/23/2011


  • Use OmniFaces <o:converter> instead. It supports render time evaluation of EL in the attributes. See also the <o:converter> showcase example.

    <h:outputText value="#{item.balanceDate}">
        <o:converter converterId="isoDateTimeConverter" pattern="#{item.pattern}" />
    </h:outputText>
    
甜味超标? 2024-12-13 07:32:10

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 类似。

这样做的优点是,在其他地方使用的相同的ConverterValidator可以重复使用而无需任何更改。

The above excellent (as always) answer from BalusC is comprehensive but didn't quite hit my exact requirement. In my case, I need to bind a Converter to each iteration in a ui:repeat. I need a different Converter depending on each item being repeated. The answer did point me in the right direction, though, so I thought it worth sharing my solution in case it helps anyone else.

I use a Converter that delegates all it's work to another Converter object specified in the attribute, as in the first of BalusC's answers. Note that this doesn't help at all if you wish to use converters with parameters, it's aimed at the situation where you would want to bind a Converter to a property of a repeating object.

Here's the delegating Converter. It's also a Validator, which works in exactly the same way.

// 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;
    }
}

So now where I would wish to use this code within my ui:repeat, which won't work:

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

I can instead use this code, which works OK:

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

Assuming that the repeating item has a public Converter getConverter() method and similar for the Validator.

This does have the advantage that the same Converters or Validators that are used elsewhere can be re-used without any changes.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文