How to make a grid of JSF composite component?

2018-12-31 18:42发布

问题:

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.

回答1:

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

Hope this helps.



回答2:

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\");
        }
    }