Reputation: 103
I wanted to create my own date converter.I have input field with f:converter id="MyConverter". When I type "today" value I want show today date. Method getAsObject is working good. I've got value "today" from input field and return new LocalDate(), but method getAsObject() have always null as a value. What should I do to fix it?
@FacesConverter(forClass = Date.class, value = "MyConverter")
public class MyConverter implements Converter {
@Override
public Object getAsObject(FacesContext context, UIComponent component, String value) throws ConverterException {
if (value == null) {
return null;
}
if (value.equals("today")) {
return new LocalDate();
}
//do something else
}
@Override
public String getAsString(FacesContext context, UIComponent component, Object value) throws ConverterException {
// value is always null!
if (value instanceof LocalDate) {
LocalDate date = (LocalDate) value;
return date.toString();
} else {
return "";
}
}
EDIT:
jsf tag (it is a little bit complicated) it's actually 2 date input fields (Date interval component), and I want to use MyDateConverter on it:
<cc:interface componentType="DateIntervalComponent">
<cc:attribute name="value" required="true" />
<cc:attribute name="placeholder" />
<cc:attribute name="labelFromDate" default="From Date" />
<cc:attribute name="labelToDate" default="To Date" />
</cc:interface>
<cc:implementation>
<div id="#{cc.userId}">
<b:input id="fromDate" binding="#{cc.FromDateComponent}"
label="#{cc.attrs.labelFromDate}">
<f:converter converterId="MyConverter" />
<f:ajax render="#{cc.userId}" />
</b:input>
<b:input id="toDate" binding="#{cc.ToDateComponent}"
label="#{cc.attrs.labelToDate}">
<f:converter converterId="MyConverter" />
<f:ajax render="#{cc.userId}" />
</b:input>
</div>
</cc:implementation>
@FacesComponent("DateIntervalComponent")
public class DateIntervalComponent extends UIInput implements NamingContainer {
private UIInput fromDateComponent;
private UIInput toDateComponent;
public DateIntervalComponent() {
setRendererType(null);
}
@Override
public String getFamily() {
return UINamingContainer.COMPONENT_FAMILY;
}
@Override
public DateInterval getValue() {
return (DateInterval) super.getValue();
}
@Override
public void processEvent(ComponentSystemEvent event) throws AbortProcessingException {
super.processEvent(event);
}
@Override
public void encodeBegin(FacesContext context) throws IOException {
if (fromDateComponent != null && toDateComponent != null) {
DateInterval value = getValue();
if (!fromDateComponent.isLocalValueSet()) {
fromDateComponent.setValue(value != null ? value.getStart() : null);
}
if (!toDateComponent.isLocalValueSet()) {
toDateComponent.setValue(value != null ? value.getEnd() : null);
}
}
super.encodeBegin(context);
}
@Override
public DateInterval getSubmittedValue() {
Object startValue = fromDateComponent.getValue();
Object endValue = toDateComponent.getValue();
return new DateInterval((Date) startValue, (Date) endValue);
}
@Override
public void validate(FacesContext context) {
super.validate(context);
if (!isValid()) {
fromDateComponent.setValid(false);
toDateComponent.setValid(false);
return;
}
}
@Override
public void resetValue() {
super.resetValue();
fromDateComponent.resetValue();
toDateComponent.resetValue();
}
public UIInput getFromDateComponent() {
return fromDateComponent;
}
public UIInput getToDateComponent() {
return toDateComponent;
}
public void setToDateComponent(UIInput toDateComponent) {
this.toDateComponent = toDateComponent;
}
public void setFromDateComponent(UIInput fromDateComponent) {
this.fromDateComponent = fromDateComponent;
}
}
regards
Upvotes: 0
Views: 1018
Reputation: 1314
You need to keep track of the state of your attributes as soon as the FacesComponent itself is STATELESS.
I use the following strategy to keep state of FacesComponent attibutes
First of all, create a generic class based on UINamingContainer (if this is your type of component).
public class MyUINamingContainer extends UINamingContainer {
@SuppressWarnings("unchecked")
/**
* Searches for an attribute and return it.
*/
protected <T> T getAttribute(String attName) {
return (T) getStateHelper().eval(attName);
}
@SuppressWarnings("unchecked")
/**
* Evaluate an attribute and return it if found.
*/
protected <T> T getAttribute(String localName, String attName) {
return (T) getStateHelper().eval(localName, getAttributes().get(attName));
}
/**
* Refresh the attributes state
*
* @param <T>
* @param attName attribute's name. This name will identify the attribute into the map.
It must be different from the name used to the attribute on the view, otherwise
it will cause a infinite loop.
* @param value attribute's value.
*/
protected <T> void setAttribute(String attName, T value) {
getStateHelper().put(attName, value);
}
}
Then, make my FacesComponent class inherit from it, so it would know the stateful getters and setters:
@FacesComponent("myComponent")
public class MyComponent extends MyUINamingContainer {
public SomeType getMyAttributeOnStateMap() {
return getAttribute("myAttributeOnStateMap","myAttribute");
}
public void setMyAttributeOnStateMap(SomeType myAttribute) {
setAttribute("myAttributeOnStateMap", myAttribute);
}
}
Into the component xhtml code:
<cc:interface componentType="myComponent">
<cc:attribute name="myAttribute"/>
</cc:interface>
<cc:implementation>
<h:input value="#{cc.myAttributeOnStateMap}" />
<cc:implementation>
PS.: Note that the attribute has a different name into the map. This is because it will get stuck in a loop it you use the same name for the map attribute and the view one. The method getAttribute(String,String) would always look for the same attribute and never find anything, leading you to a overflow.
PS2.: Note that the name used as the value into the h:input was the name used to store it into the map.
Upvotes: 1