Tuesday, June 4, 2013

Multiple dynamic includes with one JSF tag

Every JSF developer knows the ui:include and ui:param tags. You can include a facelet (XHTML file) and pass an object, which will be available in the included facelet, as follows
<ui:include src="/sections/columns.xhtml">
    <ui:param name="columns" value="#{bean.columns}"/>
</ui:include>
So, you can e.g. use it within a PrimeFaces DataTable with dynamich columns (p:columns)
<p:dataTable value="#{bean.entries}" var="data" rowKey="#{data.id}" ...>
    ...
    <ui:include src="/sections/columns.xhtml">
        <ui:param name="data" value="#{data}"/>
        <ui:param name="columns" value="#{bean.columns}"/>
    </ui:include>

</p:dataTable>
where the included facelet could contain this code
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
                xmlns:p="http://primefaces.org/ui"
                xmlns:ui="http://java.sun.com/jsf/facelets"
                ...>
    <p:columns value="#{columns}" var="column">
        <f:facet name="header">
            <h:outputText value="#{msgs[column.header]}"/>
        </f:facet>

        // place some input / select or complex composite component for multiple data types here.
        // a simple example for demonstration purpose:
        <p:inputText value="#{data[column.property]}"/>
    </p:columns>
</ui:composition>
#{bean.columns} refers to a List of special objects which describe the columns. I will name such objects ColumnModel. So, it is a List<ColumnModel>. A ColumnModel has e.g. the attributes header and property.

Go on. Now, if we want to add a support for sorting / filtering, we can use dynamic paths which refer to specific facelet files containg sorting or / and filtering feature(s). Simple bind the src attribute to a bean property.
<ui:include src="#{bean.columnsIncludeSrc}">
   <ui:param name="data" value="#{data}"/>
   <ui:param name="columns" value="#{bean.columns}"/>
</ui:include>
The bean has something like
private boolean isFilterRight;
private boolean isSortRight

// setter / getter

public String getColumnsIncludeSrc() {
   if (isFilterRight && isSortRight) {
      return "/include/columnsTableFilterSort.xhtml";
   } else if (isFilterRight && !isSortRight) {
      return "/include/columnsTableFilter.xhtml";
   } else if (!isFilterRight && isSortRight) {
      return "/include/columnsTableSort.xhtml";
   } else {
      return "/include/columnsTable.xhtml";
   }
}
Different facelets are included dependent on the set boolean rights. So, the decision about what file to be included is placed within a bean. To be more flexible, we can encapsulate the table in a composite component and move the decision logic to the component class.
<cc:interface componentType="xxx.component.DataTable">
    <cc:attribute name="id" required="false" type="java.lang.String"
        shortDescription="Unique identifier of the component in a NamingContainer"/>
    <cc:attribute name="entries" required="true"
        shortDescription="The data which are shown in the datatable. This is a list of object representing one row."/>
    <cc:attribute name="columns" required="true" type="java.util.List"
        shortDescription="The columns which are shown in the datatable. This is a list of instances of type ColumnModel."/>
    ...
</cc:interface>
<cc:implementation>
    <p:dataTable value="#{cc.attrs.entries}" var="data" rowKey="#{data.id}" ...>
        ...
        <ui:include src="#{cc.columnsIncludeSrc}">
            <ui:param name="data" value="#{data}"/>
            <ui:param name="columns" value="#{cc.attrs.columns}"/>
        </ui:include>

    </p:dataTable>
</cc:implementation>
How does ui:include work? This is a tag handler which is applied when the view is being built. In JSF 2, the component tree is built twice on POST requests, once in RESTORE_VIEW phase and once in RENDER_RESPONSE phase. On GET it is built once in the RENDER_RESPONSE phase. This behavior is specified in the JSF 2 specification and is the same in Mojarra and MyFaces. The view building in the RENDER_RESPONSE is necessary in case the page author uses conditional includes or conditional templates. So, you can be sure that the src attribute of the ui:include gets evaluated shortly before the rendering phase.

But come to the point! What I wrote until now was an introduction for a motivation to extend ui:include. Recently, I got a task to use a p:dataTable with dynamic columns and p:rowEditor. Like this one in the PrimeFaces showcase. The problem is only - such editing feature doesn't support p:columns. My idea was to add p:column tags multiple times, dynamically, but with different context parameters. You can imagine this as ui:include with ui:param in a loop. In the example above we intend to iterate over the List<ColumnModel>. Each loop iteration should make an instance of type ColumnModel available in the included facelet. So, I wrote a custom tag handler to include any facelet multiple times.
package xxx.taghandler;

import xxx.util.VariableMapperWrapper;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import javax.el.VariableMapper;
import javax.faces.component.UIComponent;
import javax.faces.view.facelets.FaceletContext;
import javax.faces.view.facelets.TagAttribute;
import javax.faces.view.facelets.TagAttributeException;
import javax.faces.view.facelets.TagConfig;
import javax.faces.view.facelets.TagHandler;

/**
 * Tag handler to include a facelet multiple times with different contextes (objects from "value").
 * The attribute "value" can be either of type java.util.List or array.
 * If the "value" is null, the tag handler works as a standard ui:include.
 */
public class InlcudesTagHandler extends TagHandler {

    private final TagAttribute src;
    private final TagAttribute value;
    private final TagAttribute name;

    public InlcudesTagHandler(TagConfig config) {
        super(config);

        this.src = this.getRequiredAttribute("src");
        this.value = this.getAttribute("value");
        this.name = this.getAttribute("name");
    }

    @Override
    public void apply(FaceletContext ctx, UIComponent parent) throws IOException {
        String path = this.src.getValue(ctx);
        if ((path == null) || (path.length() == 0)) {
            return;
        }

        // wrap the original mapper - this is important when some objects passed into include via ui:param
        // because ui:param invokes setVariable(...) on the set variable mappper instance
        VariableMapper origVarMapper = ctx.getVariableMapper();
        ctx.setVariableMapper(new VariableMapperWrapper(origVarMapper));

        try {
            this.nextHandler.apply(ctx, null);

            ValueExpression ve = (this.value != null) ? this.value.getValueExpression(ctx, Object.class) : null;
            Object objValue = (ve != null) ? ve.getValue(ctx) : null;

            if (objValue == null) {
                // include facelet only once
                ctx.includeFacelet(parent, path);
            } else {
                int size = 0;

                if (objValue instanceof List) {
                    size = ((List) objValue).size();
                } else if (objValue.getClass().isArray()) {
                    size = ((Object[]) objValue).length;
                }

                final ExpressionFactory exprFactory = ctx.getFacesContext().getApplication().getExpressionFactory();
                final String strName = this.name.getValue(ctx);

                // generate unique Id as a valid Java identifier and use it as variable for the provided value expression
                final String uniqueId = "a" + UUID.randomUUID().toString().replaceAll("-", "");
                ctx.getVariableMapper().setVariable(uniqueId, ve);

                // include facelet multiple times
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < size; i++) {
                    if ((strName != null) && (strName.length() != 0)) {
                        // create a new value expression in the array notation and bind it to the variable "name"
                        sb.append("#{");
                        sb.append(uniqueId);
                        sb.append("[");
                        sb.append(i);
                        sb.append("]}");

                        ctx.getVariableMapper().setVariable(strName,
                            exprFactory.createValueExpression(ctx, sb.toString(), Object.class));
                    }

                    // included facelet can access the created above value expression
                    ctx.includeFacelet(parent, path);

                    // reset for next iteration
                    sb.setLength(0);
                }
            }
        } catch (IOException e) {
            throw new TagAttributeException(this.tag, this.src, "Invalid path : " + path);
        } finally {
            // restore original mapper
            ctx.setVariableMapper(origVarMapper);
        }
    }
}
The most important call is ctx.includeFacelet(parent, path). The method includeFacelet(...) from the JSF API includes the facelet markup at some path relative to the current markup. The class VariableMapperWrapper is used for a name to value mapping via ui:param. For the example with columns the variable column will be mapped to expressions #{columns[0]}, #{columns[1]}, etc. before every includeFacelet(...) call as well. Well, not exactly to these expressions, at place of columns should be an unique name mapped again to the columns object (to avoid possible name collisions). The mapper class looks like as follows
package xxx.util;

import java.util.HashMap;
import java.util.Map;
import javax.el.ELException;
import javax.el.ValueExpression;
import javax.el.VariableMapper;

/**
 * Utility class for wrapping a VariableMapper. Modifications occur to the internal Map instance.
 * The resolving occurs first against the internal Map instance and then against the wrapped VariableMapper
 * if the Map doesn't contain the requested ValueExpression.
 */
public class VariableMapperWrapper extends VariableMapper {

    private final VariableMapper wrapped;

    private Map<String, ValueExpression> vars;

    public VariableMapperWrapper(VariableMapper orig) {
        super();
        this.wrapped = orig;
    }

    @Override
    public ValueExpression resolveVariable(String variable) {
        ValueExpression ve = null;
        try {
            if (this.vars != null) {
                // try to resolve against the internal map
                ve = this.vars.get(variable);
            }

            if (ve == null) {
                // look in the wrapped variable mapper
                return this.wrapped.resolveVariable(variable);
            }

            return ve;
        } catch (Throwable e) {
            throw new ELException("Could not resolve variable: " + variable, e);
        }
    }

    @Override
    public ValueExpression setVariable(String variable, ValueExpression expression) {
        if (this.vars == null) {
            this.vars = new HashMap<String, ValueExpression>();
        }

        return this.vars.put(variable, expression);
    }
}
Register the tag handler in a taglib XML file and you are done.
<tag>
    <tag-name>includes</tag-name>
    <handler-class>xxx.taghandler.InlcudesTagHandler</handler-class>
    <attribute>
        <description>
            <![CDATA[The relative path to a XHTML file to be include one or multiple times.]]>
        </description>
        <name>src</name>
        <required>true</required>
        <type>java.lang.String</type>
    </attribute>
    <attribute>
        <description>
            <![CDATA[Objects which should be available in the included XHTML files. This attribute can be either
            of type java.util.List or array. If it is null, the tag handler works as a standard ui:include.]]>
        </description>
        <name>value</name>
        <required>false</required>
        <type>java.lang.Object</type>
    </attribute>
    <attribute>
        <description>
            <![CDATA[The name of the parameter which points to an object of each iteration over the given value.]]>
        </description>
        <name>name</name>
        <required>false</required>
        <type>java.lang.String</type>
    </attribute>
</tag>
Now I was able to use it in a composite component as
<p:dataTable value="#{cc.attrs.entries}" var="data" rowKey="#{data.id}" ...>
    ...
    <custom:includes src="#{cc.columnsIncludeSrc}" value="#{cc.attrs.columns}" name="column">
        <ui:param name="data" value="#{data}"/>
    </custom:includes> 

</p:dataTable>
A typically facelet file (and the component tree) contains a quite regular p:column tag which means we are able to use all DataTable's features!
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
                xmlns:p="http://primefaces.org/ui"
                xmlns:ui="http://java.sun.com/jsf/facelets"
                ...>
    <p:column headerText="#{msgs[column.header]}">
        <p:cellEditor>
            <f:facet name="output">
                <custom:typedOutput outputType="#{column.outputTypeName}"
                   typedData="#{column.typedData}"
                   value="#{data[column.property]}"
                   timeZone="#{cc.timeZone}"
                   calendarPattern="#{cc.calendarPattern}"       
                   locale="#{cc.locale}"/>
            </f:facet>

            <f:facet name="input">
                <custom:typedInput inputType="#{column.inputTypeName}"
                   typedData="#{column.typedData}"
                   label="#{column.inputTypeName}"
                   value="#{data[column.property]}"
                   onchange="highlightEditedRow(this)"
                   timeZone="#{cc.timeZone}"
                   calendarPattern="#{cc.calendarPattern}"
                   locale="#{cc.locale}"/>
            </f:facet>
        </p:cellEditor>
    </p:column>
</ui:composition>


Note: This approach can be applied to another components and use cases. The InlcudesTagHandler works universell. For instance, I can imagine to create a dynamic Menu component in PrimeFaces without an underlying MenuModel. A list or array of some model class is still needed of course :-).

9 comments:

  1. Very good and clever post. Keep them coming . This allows navigating around some of the limitations of p:columns
    MK

    ReplyDelete
  2. I m sure about the version of EL. I m ask the question because i had never used JSTL. And this is looks better to use.thanks a lot.

    ReplyDelete
  3. Thanks a lot for the post!

    BTW, How did you added the red color and border to all the row when validation fails?

    ReplyDelete
  4. Red borders appear automatically, this is handled by PrimeFaces. Try to type something wrong in the second number field here http://www.primefaces.org/showcase/ui/datatableRowEditing.jsf

    ReplyDelete
  5. Thanks Oleg, do you happen to have the complete source for this recipe? Thank you.

    ReplyDelete
  6. Thanks for the post, it's a great idea, but would there be any downloadable source for reference? Our team find it difficult to reproduce the code in our sample project.

    ReplyDelete
    Replies
    1. Sorry, the source is not available for free (customer's project)

      Delete
  7. Hi there,
    I have impliment this and it is all working well and I have got to the point where I need to add a setPropertyActionListener to a button on each row and the cell editor. I wonder if you could tell what is in the <custom:typedInput and the <custom:typedOutput

    Thanks again for your excellent post!

    ReplyDelete

Note: Only a member of this blog may post a comment.