dev@jsftemplating.java.net

[Fwd: JSF Template-based Components]

From: Ken Paulsen <Ken.Paulsen_at_Sun.COM>
Date: Thu, 16 Aug 2007 08:47:24 -0700

I'm working on an article which may be published on TSS, attached is my
draft. If anyone has feedback, let me know.

Thanks!

Ken

attached mail follows:



I updated "Step 4", added a link to the componentDemo.war file, and a couple other small changes (such as the ones you suggested on IRC).  How does this look?  What else do I need to do to it?

Thanks!

Ken


Creating a component in JavaServer Faces is hard.  The JavaServer Faces 2.0 EG recognizes the importance of making component authoring easier and has made it one of its top priorities.  However, JSF 2.0 is too far off to wait for.  Let's look at why it's hard to create a component, then I'll show one way to help simplify this process.

To create a component you must:

If you look at each of these steps in detail, you'll see there are very specific requirements that you must follow many not obvious.  You must set the submitted value when doing decode, not the value property.  You must handle EL binding correctly (look at the local value if set first, set the local value until the update model phase).  In addition, you may find making your component customizable is a challenge.  Or if you define a child component, do you re-create it each time it is rendered, or add it to the component tree?  Adding a child to the component tree requires a deep understanding of how JSF works, much more than should be required.  How and when do you best you utilize facets?  JSF provides a lot of power, although it exposes too many of the internals of that power.

In this article I'd like to propose an approach to simplify the Renderer portion of this problem.  This isn't a cure for all the problems of creating a component, but it is a step in the right direction.  It may not even be the ideal solution for Renderers, but it's much better than alternatives available today.  The approach is to create a template-based Renderer to declaratively define the output of the component.  This approach is implemented in the JSFTemplating project (https://jsftemplating.dev.java.net).  In fact, the project's initial goal was solely to simplify JSF Renderer creation, however, the focus has moved to page creation lately.

Suppose you want to create a component to layout a page in newspaper columns.  You want the columns to have a border which can be configured via a border property.  You want a default title heading, but want to be able to supply a facet in case you need a more sophisticated heading.  The # of columns needs to be configurable and the children of this component should be equally divided into the columns where the first set of children go in the first column, the 2nd set in the next and so on.  Here's an example of what the component should look like in a page utilizing it:


    <my:columnLayout id="newspaper" border="2px dashed blue" columns="3" title="The Sparky Times">
        <h:outputLink ... />
        <h:graphicImage ... />
        <h:outputText ... />
        <h:outputLink ... />
        <h:graphicImage ... />
        <h:outputText ... />
    </my:columnLayout>



The resulting page should look something like:

Screenshot

Steps:

1) Create a UIComponent.

For this example we will simply extend from TemplateComponentBase (part of
JSFTemplating) and do almost nothing else:

ColumnLayout.java

    public class ColumnLayout extends TemplateComponentBase implements NamingContainer {

        /**
         *  <p> Constructor for <code>ColumnLayout</code>.</p>
         */
        public ColumnLayout() {
            super();
            setRendererType("org.example.ColumnLayout");

            // This is the location of the file that declares the layout
            setLayoutDefinitionKey("jsftemplating/columnlayout.jsf");
        }

        /**
         *  <p> Return the family for this component.</p>
         */
        public String getFamily() {
            return "org.example.ColumnLayout";
        }
    }


Although we will have properties, for this example we will rely on JSF attribute map to persist and manage these properties.  We only specify required information to find the template and to register this component correctly with JSF.

2) Create a JSFTemplating factory.

This is used to instantiate the UIComponent during the RestoreView phase.  For JSP, this would be a tag handler instead, and for Facelets a facelets taglib.

ColumnLayoutFactory.java

    @UIComponentFactory("my:columnLayout")
    public class ColumnLayoutFactory extends ComponentFactoryBase {

        public UIComponent create(FacesContext context, LayoutComponent descriptor, UIComponent parent) {
            // Create the UIComponent, this must be registered in the
            // faces-config.xml file.
            UIComponent comp = createComponent(
                    context, "org.example.ColumnLayout", descriptor, parent);

            // Set all the attributes / properties
            setOptions(context, descriptor, comp);

            // Return the component
            return comp;
        }
    }


As you can see this is boilerplate code, however, more sophisticated use cases make the factory pattern very powerful.  For example, a specialized factory could be written to provide themes for our component that have preset colors or titles.  That is outside the scope of this article, though.  The use of @annotations here makes the configuration very simple, however, that too is outside the scope of this article.

3) Modify the faces-config.xml file.

faces-config.xml

    <component>
        <component-type>org.example.ColumnLayout</component-type>
        <component-class>org.example.component.ColumnLayout</component-class>
    </component>
    <render-kit>
        <renderer>
            <component-family>org.example.ColumnLayout</component-family>
            <renderer-type>org.example.ColumnLayout</renderer-type>
            <renderer-class>com.sun.jsftemplating.renderer.TemplateRenderer</renderer-class>
        </renderer>
    </render-kit>



These are the necessary lines to register a component and its renderer.

4) Create your JSFTemplating Renderer Template.
   
columnLayout.jsf

<!beforeEncode
    // Before we start, initialize a request attribute (currentCol) to "0"
    setAttribute(key="currentCol" value="0");

    // Get the # of columns, default to '2'... normally the default should be
    // handled by the component, but we didn't put any code in our component.
    setAttribute(key="columns" value="2");
    if ($property{columns}) {
        setAttribute(key="columns" value="$property{columns}");
    }

    // Calculate the column width for the css we will write, save in request
    // scope variable "colWidth"
    my.getColumnWidth(columns="#{columns}", colWidth=>$attribute{colWidth});
/>

// Column around the whole component for easy client-side identification
"<div id="$this{clientId}">
    // Provide a place for a facet to replace the title, the contents of the
    // <!facet> tag provide the default title layout used if a 'title' facet is
    // not supplied.
    <!facet name="title">
        // Only render the default title if a 'title' property is supplied
        <!if $property{title}>
            <f:verbatim>
                <div id="$this{clientId}_title" class="colTitle"
                     style="position: absolute; left: 2%; width: 88%;
                     text-align: center; border: $property{border}">
                        $property{title}</div>
            </f:verbatim>
        </if>
    </facet>

    // Start the first column
    <f:verbatim>
        <div id="$this{clientId}_column0" class="column"
             style="position: absolute; left: 2%; top: 80px;
                 width: #{colWidth}%; border: $property{border}">
    </f:verbatim>

    // Loop through all the children of the columnLayout component
    <!foreach _child : $this{children}>
        // As we loop through the properties, add new columns as appropriate
        <!if #{currentCol}<#{col}>
            <!beforeEncode
                // This calculates the column # before the above 'if' statement
                my.getColumnNumber(
                    max="$attribute{_child-size}",
                    index="$attribute{_child-index}",
                    columns="#{columns}",
                    col=>$attribute{col});
            />
            <!encode
                // This only executes if the above 'if' statement is 'true'
                // Set a new "left" position for css, and update the column #
                my.getLeftPosition(columns="#{columns}", col="#{col}",
                    colPos=>$attribute{left});
                setAttribute(key="currentCol" value="#{col}");
            />

            // We need to end the previous column and start a new one
            <f:verbatim>
                </div>
                <div id="$this{clientId}_column#{col}" class="column"
                     style="position: absolute; left: #{left}%; top: 80px;
                         width: #{colWidth}%; border: $property{border}">
            </f:verbatim>
        </if>

        // This displays the child component we are currently evaluating in the
        // "foreach" loop with a <div> around it.
        "<div class="colEntry" id="$this{clientId}_entry$attribute{_child-index}">
            <component id="#{_child.id}" />
        "</div>
    </foreach>

    // Close the last column
    "</div>
"</div>



JSFTemplating currently supports 3 syntaxes for declaring pages (XML, the one above, and Facelets).  In the syntax above, lines starting with a double-quote (") produce output just like the content inside the <f:verbatim> tags.  The "<!beforeEncode .../>", and "<!encode .../>" represent events that call out to Java code to perform some operation.  Not everything is best represented in a template, the JSFTemplating events allow Java code to perform tasks that are best done in Java.  The code for the Java "handlers" in these events is shown later in this article.

The above template first does some initialization of request-scoped variables it uses during a "beforeEncode" event which is executed before any rendering is performed.

Next it starts an html <div> tag to wrap the entire component so that the component can easily be found in the resulting HTML using JavaScript or CSS.  Notice the use of $this{clientId} -- this is a proprietary JSFTemplating syntax to retrieve the component's clientId.  It would be nice if JSF provided relative information like this or provided other implicit objects that a component template author would find useful.

The template then looks for a "title" facet (i.e. <!facet name="title">) to determine if the user supplied their own title via a facet.  If so, it uses it.  If not, the contents of the <!facet> tag are used -- in this case it writes out title  text inside a <div> provided that the user supplied a "title" property.

Ater the title, it begins to render columns.  Each column starts with a <div> which has a unique "id" and has a CSS class of "column".  It also utilizes the "border" property to show any border the user specified.  Each child is rendered in the appropriate column and is also wrapped in a <div> in a similar way to make them more accessible via JavaScript and CSS.  As it loops through the children and renders them, it executes a conditional "if" statement to determine if it should start a new column.  This "if" statement has 3 parts: 1) beforeEncode, which calculates the current column, this is executed befor the if condition is evaulted; 2) encode, which provides more java handler code that is executed only if the condition is met; 3) The content to be written when this condition is true -- in this case an ending </div> and a new opening <div> for the next column.

Last after all the children of the columns have been written, the last column is ended and the component is ended.

Finally, lets look at the Java handlers which are referred to in the template:

MyHandlers.java

    public class MyHandlers {

        /**
         *  <p> This handler calculates the column width given the # of columns
         *      to display on a page.  It assumes at most 90% of the page will
         *      be used to display the columns.</p>
         */
        @Handler(id="my.getColumnWidth",
            input={
                @HandlerInput(name="columns", type=Integer.class, required=true)
            },
            output={
                @HandlerOutput(name="colWidth", type=Integer.class)
            })
        public static void calculateColumnWidth(HandlerContext context) {
            // Get the input.
            int numColumns = (Integer) context.getInputValue("columns");

            // Determine the width
            int colWidth = getColumnWidth(numColumns);

            // Set the output.
            context.setOutputValue("colWidth", colWidth);
        }

        /**
         *  <p> Does the actual column width determination.</p>
         */
        private static int getColumnWidth(int numColumns) {
            return 90 / numColumns - COL_PADDING;
        }

        /**
         *  <p> This handler calculates the column left position given the # of
         *      columns, the current item number, and the total number of items to
         *      be displayed.  It assumes at most 90% of the page will be used to
         *      display the columns.</p>
         */
        @Handler(id="my.getColumnNumber",
            input={
                @HandlerInput(name="columns", type=Integer.class, required=true),
                @HandlerInput(name="max", type=Integer.class, required=true),
                @HandlerInput(name="index", type=Integer.class, required=true)
            },
            output={
                @HandlerOutput(name="col", type=Integer.class)
            })
        public static void calculateColumn(HandlerContext context) {
            int numColumns = (Integer) context.getInputValue("columns");
            int idx = (Integer) context.getInputValue("index");
            int max = (Integer) context.getInputValue("max");
            context.setOutputValue("col", (numColumns * (idx-1)) / max);
        }

        /**
         *  <p> This handler calculates the column left position given the # of
         *      columns, the current item number, and the total number of items to
         *      be displayed.  It assumes at most 90% of the page will be used to
         *      display the columns.</p>
         */
        @Handler(id="my.getLeftPosition",
            input={
                @HandlerInput(name="columns", type=Integer.class, required=true),
                @HandlerInput(name="col", type=Integer.class, required=true)
            },
            output={
                @HandlerOutput(name="colPos", type=Integer.class)
            })
        public static void calculateColumnPosition(HandlerContext context) {
            // Get the input.
            int curColumn = (Integer) context.getInputValue("col");
            int numColumns = (Integer) context.getInputValue("columns");
            int colWidth = getColumnWidth(numColumns);
   
            // Determine the position
            int pos = (colWidth + COL_PADDING) * curColumn + COL_PADDING;

            // Set the output.
            context.setOutputValue("colPos", pos);
        }

        public static final int COL_PADDING = 2;
    }


These handlers use @annotations to eliminate their configuration.  Once they are compiled they are directly usable from the templates.  Their input and output is converted automatically to the correct Java types and required values inputs are ensured.  Multiple inputs and outputs are possible via the named I/O properties -- all accessible via the HandlerContext object that is provided by the framework.  This makes it easy to pass data between Java and the template.

5) Create your page to use the new JSF component.

demo.jsf

    "<style type="text/css">#newspaper_entry7 {border: 1px solid green}</style>
    "<style type="text/css">.colEntry {background-color: #EEEEEE}</style>

    <my:columnLayout id="newspaper" border="2px dashed blue" columns="3" title="The Sparky Times" >
        <!facet name="title">
            <h:panelGroup>
                <h:graphicImage url="https://glassfish-theme.dev.java.net/logo.gif" style="vertical-align: middle; padding-bottom: 15px;"/>
                <h:outputText style="font-size: 2em; font-family: Arial; color: blue;" value=" The Sparky Times" />
            </h:panelGroup>
        </facet>
        <h:outputLink value="https://jsftemplating.dev.java.net"><staticText value="JSFTemplating" /></h:outputLink>
        "Entry 2
        "Entry 3
        <h:outputLink value="https://glassfish.dev.java.net"><staticText value="GlassFish" /></h:outputLink>
        <h:outputLink value="https://woodstock.dev.java.net"><staticText value="Woodstock" /></h:outputLink>
        "Entry 6
        <h:outputLink value="http://blogs.sun.com/paulsen"><staticText value="Ken Paulsen's Blog" /></h:outputLink>
        "Entry 8
        "Entry 9
        <h:graphicImage url="https://glassfish-theme.dev.java.net/logo.gif" />
    </my:columnLayout>



You can see I added a few extra css entries and made use of the title facet.  You should also note that this is the same syntax as the component.  This is one advantage of JSFTemplating -- the page syntax is the component template syntax.

Here's a screen shot of the output:

screenshot

Now if you've compiled and deployed everything, you're ready to try it out!  One of the best features of the templating is that you may modify the ColumnLayout Renderer template (or the page using it) at runtime and see the results by simply hitting reload.  This greatly speeds up development time by eliminating the change-recompile-redeploy-reload cycle.

Conclusion:

While I didn't demonstrate how to ease many of the pains in component development, I showed how to simplify and speed up Renderer development.  Using this templating strategy for the renderer, makes component development in JSF much easier.  It is available today through JSFTemplating, so come take a look and provide feedback... or better yet get involved and help make it even better!

To download a demo app which includes these file so you can play with them:

    https://jsftemplating.dev.java.net/files/documents/5015/64400/componentDemo.war

Have fun!

Ken Paulsen
https://jsftemplating.dev.java.net





picture
(image/png attachment: 02-part)

picture
(image/png attachment: 03-part)