I have the following xhtml
code for a JSF page:
<h:dataTable value="#{bean.data}" var="elem">
<h:column>
<h:panelGroup binding="#{elem.displayComponent}"/>
</h:column>
</h:dataTable>
In my Bean
I get data objects that implement an interface like this:
public interface CustomElementProvider {
UIPanel getDisplayComponent();
void setDisplayComponent(UIPanel container);
}
One implementation might be:
public class LabelElement implements CustomElementProvider {
private UIPanel container;
@Override
UIPanel getDisplayComponent() {
if (container == null) {
FacesContext facesContext = FacesContext.getCurrentInstance();
container = (UIPanel) facesContext.getApplication().createComponent(UIPanel.COMPONENT_TYPE);
UIComponent inside = facesContext.getApplication().createComponent(HtmlOutputLabel.COMPONENT_TYPE);
// fill label with data
container.getChildren().add(inside);
}
return container;
}
// ... override setter
}
This works when the backing bean Bean
is providing the control, but not when the unmanaged data object does this. Then I get an exception:
javax.el.PropertyNotFoundException: Target Unreachable, identifier 'elem' resolved to null
Is there a way to avoid this?
What I found so far looking around StackOverflow was mainly a situation where someone wanted to use one of two or more predefined components (see Different components in same column in datatable in JSF 2.0). This does not work, since I want to be able to extend the system by writing new data objects providing their own implementation of the component without modifying the xhtml
code of the dataTable
.
Another solution for my problem would be to have a different interface:
public interface CustomElementProvider {
String getCustomComponentName();
}
And then I render the custom component given by this method. But I have no idea how to do that.
[Update] To make it more clear what I want. This is code that works:
<h:dataTable value="#{preferences.preferenceList}" var="pref">
<h:column>
<f:facet name="header">
<h:outputText value="Name"/>
</f:facet>
<h:outputLabel id="key" value="#{pref.key}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Value"/>
</f:facet>
<h:inputText id="value" value="#{pref.value}"
rendered="#{pref.valueTypeAsString eq 'java.lang.String'
or pref.valueTypeAsString eq 'java.lang.Integer'}"/>
<h:selectBooleanCheckbox id="valueBool" value="#{pref.value}"
rendered="#{pref.valueTypeAsString eq 'java.lang.Boolean'}"/>
<h:selectOneMenu id="valueEnum" value="#{pref.value}"
rendered="#{pref.valueTypeAsString eq 'java.lang.Enum'}">
<f:selectItems value="#{pref.enumEntries}"/>
</h:selectOneMenu>
</h:column>
</h:dataTable>
But you can see two problems here. First, when I want to add a new type of Preference that uses maybe a custom component, I would have to change (and bloat) the XHTML-code here. Also, I just don't want to modify the XHTML-code, I would like to have the system easily extendable.
Secondly, as you can see in the enum example, my interface has a method getEnumEntries()
since I need it for enums. This method makes no sense for other types, but I need it here. This is not nice.
[Update/Solution] I created the entire table programmatically. Not via a UIData
, because this would not help. I just add the table
, tr
, etc. tags plainly. This allows me to bind the edit controls nicely. As an example, this is the definition of a Boolean Preference item:
public class BooleanPreference extends PreferenceBase<Boolean> {
public BooleanPreference(String key, Boolean defaultValue, String elName, String description) {
super(key, defaultValue, elName, description);
}
@Override
public Boolean fromString(String strValue) {
return Boolean.valueOf(strValue);
}
@Override
public UIComponent getEditComponent() {
HtmlSelectBooleanCheckbox checkbox = new HtmlSelectBooleanCheckbox();
checkbox.setValueExpression("value", createValueExpression(String.format("#{%s.value}", getElName())));
return checkbox;
}
}
The only thing that is not so nice at the moment is that I need to set an EL-name that is something like preferences.boolProp
for a backing bean named Preferences
with a property boolProp
.