我已经创建了一个自定义的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将仅为每个组件创建一个转换器实例,它不会在每次呈现行时创建/重置转换器。
有几种方法可以让它工作。
-
传递动态属性作为组件的
<f:attribute>
,并让Converter
拦截它。您可以在这里找到一个示例:JSF convertDateTime与datatable中的时区。这可以用作<h:outputText value="#{item.balanceDate}"> <f:converter converterId="isoDateTimeConverter" /> <f:attribute name="pattern" value="#{item.pattern}" /> </h:outputText>
-
使用EL函数代替
Converter
。您可以在这里找到一个示例:Facelets和JSTL(将日期转换为字段中使用的字符串)。这可以用作<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 date
和String pattern
的bean)结果是
23-09-2011
2011-09-23
09/23/2011 -
使用OmniFaces
<o:converter>
代替。它支持属性中EL的呈现时间计算。参见<o:converter>
展示示例。<h:outputText value="#{item.balanceDate}"> <o:converter converterId="isoDateTimeConverter" pattern="#{item.pattern}" /> </h:outputText>
来自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
方法类似。
这样做的好处是,在其他地方使用的相同的Converter
s或Validator
s可以重复使用,而不需要任何更改。