25

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.

2
  • I think they are now rendered in a one-column table (and they were in a two-column table before). A workaround could be to use an h:panelGrid inside your composite component with the first column big enough (although it is a bit ugly;-). Commented Apr 19, 2011 at 10:25
  • Thanks you for clarification ! You see the problem. I've although had this idea, but as you say: this is ugly and you'll run into problems later because there many tables rendered and they don't depend on one another, so they can be moved by layout manager independently. Commented Apr 21, 2011 at 5:33

2 Answers 2

49

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}&#160;#{required ? '*&#160;' : ''}" /> </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:

enter image description here

Hope this helps.

Sign up to request clarification or add additional context in comments.

11 Comments

Wow this is an answer, by BalusC himself!:) You already helped me many times when I used google. I have another problem with this, but you've given me the complete answer for this case, so I'll go open another question.
i think this is solution so complicated in the end, which i prefer not to use composite component inside of panelGrid anymore.
@Valter: if you have only 1 form, then it may look indeed clumsy. But if you have ~5 or more of such forms in your website which should be laid out according the same standards, then it's way much better. Note that my answer doesn't show a composite component, but just a tag file.
@BalusC Wouldn't it be easier to drop the attribute 'bean' and just pass the property directly, e.g. property=#{bean.username}? Or did you have a specific reason to include the bean property seperately?
@Theo: this will work fine for output components, but not for input components. The property can't be set then.
|
4

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="&nbsp;" 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"); } } 

1 Comment

+1 not for it to be the nicest solution but for the thought it sparked that you could use this on different occasions if there really is no way around it.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.