I have lot\'s of outputLabel and inputText pairs in panelGrids
<h:panelGrid columns=\"2\">
<h:outputLabel value=\"label1\" for=\"inputId1\"/>
<h:inputText id=\"inputId1/>
<h:outputLabel value=\"label2\" for=\"inputId2\"/>
<h:inputText id=\"inputId2/>
...
</h:panelGrid>
I want to have some behaviour for all of them: like same validation or same size for every inputText. So I have created a composite component which just includes an outputLabel and and an inputText
<my:editField value=\"field1\"/>
<my:editField value=\"field2\"/>
But now when I put them in a gridPanel, they do not get aligned depending on the length of the label text. I understand why, but I don\'t know how to work around.
A composite component gets indeed rendered as a single component. You want to use a Facelet tag file instead. It gets rendered exactly as whatever its output renders. Here\'s a kickoff example assuming that you want a 3-column form with a message field in the third column.
Create tag file in /WEB-INF/tags/input.xhtml
(or in /META-INF
when you want to provide tags in a JAR file which is to be included in /WEB-INF/lib
).
<ui:composition
xmlns:c=\"http://java.sun.com/jsp/jstl/core\"
xmlns:f=\"http://java.sun.com/jsf/core\"
xmlns:h=\"http://java.sun.com/jsf/html\"
xmlns:ui=\"http://java.sun.com/jsf/facelets\">
<c:set var=\"id\" value=\"#{not empty id ? id : (not empty property ? property : action)}\" />
<c:set var=\"required\" value=\"#{not empty required and required}\" />
<c:choose>
<c:when test=\"#{type != \'submit\'}\">
<h:outputLabel for=\"#{id}\" value=\"#{label} #{required ? \'* \' : \'\'}\" />
</c:when>
<c:otherwise>
<h:panelGroup />
</c:otherwise>
</c:choose>
<c:choose>
<c:when test=\"#{type == \'text\'}\">
<h:inputText id=\"#{id}\" value=\"#{bean[property]}\" label=\"#{label}\" required=\"#{required}\">
<f:ajax event=\"blur\" render=\"#{id}-message\" />
</h:inputText>
<h:message id=\"#{id}-message\" for=\"#{id}\" />
</c:when>
<c:when test=\"#{type == \'password\'}\">
<h:inputSecret id=\"#{id}\" value=\"#{bean[property]}\" label=\"#{label}\" required=\"#{required}\">
<f:ajax event=\"blur\" render=\"#{id}-message\" />
</h:inputSecret>
<h:message id=\"#{id}-message\" for=\"#{id}\" />
</c:when>
<c:when test=\"#{type == \'select\'}\">
<h:selectOneMenu id=\"#{id}\" value=\"#{bean[property]}\" label=\"#{label}\" required=\"#{required}\">
<f:selectItems value=\"#{options.entrySet()}\" var=\"entry\" itemValue=\"#{entry.key}\" itemLabel=\"#{entry.value}\" />
<f:ajax event=\"change\" render=\"#{id}-message\" />
</h:selectOneMenu>
<h:message id=\"#{id}-message\" for=\"#{id}\" />
</c:when>
<c:when test=\"#{type == \'submit\'}\">
<h:commandButton id=\"#{id}\" value=\"#{label}\" action=\"#{bean[action]}\" />
<h:message id=\"#{id}-message\" for=\"#{id}\" />
</c:when>
<c:otherwise>
<h:panelGroup />
<h:panelGroup />
</c:otherwise>
</c:choose>
</ui:composition>
Define it in /WEB-INF/example.taglib.xml
(or in /META-INF
when you want to provide tags in a JAR file which is to be included in /WEB-INF/lib
):
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<facelet-taglib
xmlns=\"http://java.sun.com/xml/ns/javaee\"
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"
xsi:schemaLocation=\"http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd\"
version=\"2.0\">
<namespace>http://example.com/jsf/facelets</namespace>
<tag>
<tag-name>input</tag-name>
<source>tags/input.xhtml</source>
</tag>
</facelet-taglib>
Declare the taglib usage in /WEB-INF/web.xml
(this is not needed when the tags are provided by a JAR file which is included in /WEB-INF/lib
! JSF will auto-load all *.taglib.xml
files from /META-INF
).
<context-param>
<param-name>javax.faces.FACELETS_LIBRARIES</param-name>
<param-value>/WEB-INF/example.taglib.xml</param-value>
</context-param>
(multiple taglib files can be separated by semicolon ;
)
Finally just declare it in your main page templates.
<!DOCTYPE html>
<html lang=\"en\"
xmlns=\"http://www.w3.org/1999/xhtml\"
xmlns:f=\"http://java.sun.com/jsf/core\"
xmlns:h=\"http://java.sun.com/jsf/html\"
xmlns:ui=\"http://java.sun.com/jsf/facelets\"
xmlns:my=\"http://example.com/jsf/facelets\"
>
<h:head>
<title>Facelet tag file demo</title>
</h:head>
<h:body>
<h:form>
<h:panelGrid columns=\"3\">
<my:input type=\"text\" label=\"Username\" bean=\"#{bean}\" property=\"username\" required=\"true\" />
<my:input type=\"password\" label=\"Password\" bean=\"#{bean}\" property=\"password\" required=\"true\" />
<my:input type=\"select\" label=\"Country\" bean=\"#{bean}\" property=\"country\" options=\"#{bean.countries}\" />
<my:input type=\"submit\" label=\"Submit\" bean=\"#{bean}\" action=\"submit\" />
</h:panelGrid>
</h:form>
</h:body>
</html>
(the #{bean.countries}
should return a Map<String, String>
with country codes as keys and country names as values)
Screenshot:
Hope this helps.
There should have been a switch in panelGrid to render composite components separately. I have a solution for this. You can have separate composite components instead of clubbing them together. In each composite component you can use ui:fragments to demarcate the components you want to separately fall under different columns. Following is extract from my inputText.xhtml:
<html xmlns=\"http://www.w3.org/1999/xhtml\"
xmlns:h=\"http://java.sun.com/jsf/html\"
xmlns:f=\"http://java.sun.com/jsf/core\"
xmlns:composite=\"http://java.sun.com/jsf/composite\"
xmlns:ui=\"http://java.sun.com/jsf/facelets\">
<composite:interface>
<composite:attribute name=\"id\" />
<composite:attribute name=\"value\" />
<composite:attribute name=\"label\" />
<composite:attribute name=\"readonly\" />
<composite:attribute name=\"disabled\" />
<composite:attribute name=\"required\" />
</composite:interface>
<composite:implementation>
<ui:fragment id=\"label\">
<h:outputText id=\"#{cc.attrs.id}Label\" value=\"#{cc.attrs.label}\"
for=\"#{cc.attrs.id}\" />
<h:outputLabel value=\"#{bundle[\'label.STAR\']}\"
rendered=\"#{cc.attrs.required}\" styleClass=\"mandatory\"
style=\"float:left\"></h:outputLabel>
<h:outputLabel value=\" \" rendered=\"#{!cc.attrs.required}\"
styleClass=\"mandatory\"></h:outputLabel>
</ui:fragment>
<ui:fragment id=\"field\">
<h:inputText id=\"#{cc.attrs.id}\" value=\"#{cc.attrs.value}\"
styleClass=\"#{not component.valid ? \'errorFieldHighlight medium\' : \'medium\'}\"
disabled=\"#{cc.attrs.disabled}\" required=\"#{cc.attrs.required}\"
label=\"#{cc.attrs.label}\" readonly=\"#{cc.attrs.readonly}\">
</h:inputText>
</ui:fragment>
</composite:implementation>
</html>
Now this will not going to align in the form which is inside the panelGrid:
<h:panelGrid width=\"100%\">
<my:inputText label=\"#{bundle[\'label.fname\']}\" value=\"#{bean.fname}\" id=\"fname\"></my:inputtext>
<my:inputText label=\"#{bundle[\'label.lname\']}\" value=\"#{bean.lname}\" id=\"lname\"></my:inputtext>
</panelGrid>
So i have extended the GroupRenderer\'s encodeRecursive method, to add after label and a before field:
// inside my extended renderer
protected void encodeRecursive(FacesContext context, UIComponent component)
throws IOException {
// Render our children recursively
if (component instanceof ComponentRef
&& component.getId().equals(\"field\")) {
context.getResponseWriter().startElement(\"td\", component);
}
super.encodeRecursive(context, component);
if (component instanceof ComponentRef
&& component.getId().equals(\"label\")) {
context.getResponseWriter().endElement(\"td\");
}
}