How to make a grid of JSF composite component?

后端 未结 2 1197
感动是毒
感动是毒 2020-11-22 15:27

I have lot\'s of outputLabel and inputText pairs in panelGrids


  
          


        
相关标签:
2条回答
  • 2020-11-22 15:43

    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.

    0 讨论(0)
  • 2020-11-22 16:00

    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");
            }
        }
    
    0 讨论(0)
提交回复
热议问题