Showing posts with label Composite Component. Show all posts
Showing posts with label Composite Component. Show all posts

Monday, February 8, 2016

Recursive tree of composite components

While trying to create kind of tree structure (menu, discussion, comments, etc) with Facelets and composite components, you'll probably ever have attempted to recursively nest a composite component in itself like the below <my:tree node="#{bean.tree}"> composite component example.

<cc:interface>
    <cc:attribute name="node" type="com.example.SomeTreeModel" />
</cc:interface>
<cc:implementation>
    <c:if test="#{not empty cc.attrs.node.children}">
        <ul>
            <c:forEach items="#{cc.attrs.node.children}" var="node">
                <li>
                    #{node.data}
                    <my:tree node="#{node}" />
                </li>
            </c:forEach>
        </ul>
    </c:if>
</cc:implementation>

And then you was surprised to see a stack overflow error (line numbers match Mojarra 2.2.12) — not surprisingly, this was also ever asked on Stack Overflow:

java.lang.StackOverflowError
    at java.lang.Exception.(Exception.java:84)
    at java.lang.RuntimeException.(RuntimeException.java:80)
    at javax.el.ELException.(ELException.java:66)
    at com.sun.faces.facelets.el.VariableMapperWrapper.resolveVariable(VariableMapperWrapper.java:107)
    at com.sun.faces.facelets.tag.jsf.CompositeComponentTagHandler$1.resolveVariable(CompositeComponentTagHandler.java:377)
    at com.sun.faces.facelets.el.VariableMapperWrapper.resolveVariable(VariableMapperWrapper.java:103)
    at com.sun.faces.facelets.tag.jsf.CompositeComponentTagHandler$1.resolveVariable(CompositeComponentTagHandler.java:377)
    at com.sun.faces.facelets.el.VariableMapperWrapper.resolveVariable(VariableMapperWrapper.java:103)
    at com.sun.faces.facelets.tag.jsf.CompositeComponentTagHandler$1.resolveVariable(CompositeComponentTagHandler.java:377)
    at com.sun.faces.facelets.el.VariableMapperWrapper.resolveVariable(VariableMapperWrapper.java:103)
    [repeat]

What's happening here? Which Facelets EL variable exactly is being resolved on itself in an infinite loop? We have already used JSTL <c:forEach> instead of <ui:repeat> to avoid an infinite component tree during view build time (as the <ui:repeat> only runs during view render time not during view build time, see also JSTL in JSF2 Facelets… makes sense?), so that can't be the cause.

Upon inspection (just putting a debug breakpoint in VariableMapperWrapper) it appears to be the EL variable #{cc}.

What turns out, when you pass #{node} to the nested composite, then you're technically basically passing #{cc.attrs.node.children[index]} to it. The nested composite is in turn thus interpreting its #{cc.attrs.node} as #{cc.attrs[cc.attrs.node.children[index]]}. But ... in the context of the nested composite, the #{cc} is referring the nested composite itself! So, the #{cc.attrs.node} actually refers to the nested composite's own node. This results in a never ending loop.

We ultimately actually want it to be #{cc.attrs[cc.parent.attrs.node.children[index]]} where the #{cc.parent} just refers the parent component via UIComponent#getParent(). So, let's try it.

<cc:interface>
    <cc:attribute name="node" type="com.example.SomeTreeModel" />
</cc:interface>
<cc:implementation>
    <c:if test="#{not empty cc.attrs.node.children}">
        <ul>
            <c:forEach items="#{cc.attrs.node.children}" var="node" varStatus="loop">
                <li>
                    #{node.data}
                    <my:tree node="#{cc.parent.attrs.node.children[loop.index]}" />
                </li>
            </c:forEach>
        </ul>
    </c:if>
</cc:implementation>

It still yields a stack overflow error! It's only different from the above. Top lines of stack trace may vary depending on remaining stack size, but the repeating part is below (line numbers match Mojarra 2.2.12, Tomcat 8.0.30 and Weld 2.3.0):

    [repeat]
    at com.sun.faces.el.CompositeComponentAttributesELResolver$ExpressionEvalMap.get(CompositeComponentAttributesELResolver.java:393)
    at javax.el.MapELResolver.getValue(MapELResolver.java:65)
    at com.sun.faces.el.DemuxCompositeELResolver._getValue(DemuxCompositeELResolver.java:176)
    at com.sun.faces.el.DemuxCompositeELResolver.getValue(DemuxCompositeELResolver.java:203)
    at org.apache.el.parser.AstValue.getValue(AstValue.java:169)
    at org.apache.el.ValueExpressionImpl.getValue(ValueExpressionImpl.java:184)
    at org.jboss.weld.el.WeldValueExpression.getValue(WeldValueExpression.java:50)
    at com.sun.faces.facelets.el.ContextualCompositeValueExpression.getValue(ContextualCompositeValueExpression.java:158)
    at com.sun.faces.facelets.el.TagValueExpression.getValue(TagValueExpression.java:109)
    at javax.faces.component.UIComponentBase$AttributesMap.get(UIComponentBase.java:2427)
    at com.sun.faces.el.CompositeComponentAttributesELResolver$ExpressionEvalMap.get(CompositeComponentAttributesELResolver.java:393)
    [repeat]

What's happening here? Which composite component attribute exactly is being evaluated on itself in an infinite loop?

What turns out, the attributes are stored as deferred value expressions, not as immediately evaluated values. We all already kind of knew it (the #{} versus ${} in good old JSP), but it still easily slips our mind. When #{cc.parent.attrs.node} is about to be evaluated, the parent gives back the sole EL value expression #{cc.attrs.node} as a ValueExpression object to the child and then the child is evaluating it in its own context. However, as you have read above, the #{cc} refers to the current composite, not the intented parent. This results again in an infinite loop.

We ultimately want the composite to internally immediately evaluate the #{cc.attrs.node} and store it somewhere as a request based instance variable instead of storing only the ValueExpression object in the component state. A backing component is a good candidate. There you can hook on UIComponent#setValueExpression() and then caputure and immediately evaluate the node attribute. So let's try it.

@FacesComponent("treeComposite")
public class TreeComposite extends UINamingContainer {

    private SomeTreeModel node;

    @Override
    public void setValueExpression(String name, ValueExpression expression) {
        if ("node".equals(name)) {
            setNode((SomeTreeModel) expression.getValue(getFacesContext().getELContext()));
        }
        else {
            super.setValueExpression(name, expression);
        }
    }

    public SomeTreeModel getNode() {
        return node;
    }

    public void setNode(SomeTreeModel node) {
        this.node = node;
    }

}

Note: for SomeTreeModel you could use org.omnifaces.model.tree.TreeModel as also used by <o:tree>.

Declare the above backing component as componentType of the composite interface and then replace #{cc.attrs.node} over all place by #{cc.node}.

<cc:interface componentType="treeComposite">
    <cc:attribute name="node" type="com.example.SomeTreeModel" />
</cc:interface>
<cc:implementation>
    <c:if test="#{not empty cc.node.children}">
        <ul>
            <c:forEach items="#{cc.node.children}" var="node" varStatus="loop">
                <li>
                    #{node.data}
                    <my:tree node="#{cc.parent.node.children[loop.index]}" />
                </li>
            </c:forEach>
        </ul>
    </c:if>
</cc:implementation>

Now it finally works! :)

By the way, my hand is free again! Cast was removed February 1st. The rightmost fingers are still weak, and I'm still using only 2-3 fingers to type, and I have to pause my hand more often, but hey, there is progress as compared to last month.

Wednesday, January 16, 2013

Composite component with multiple input fields

Introduction

Composite components are a nice JSF2/Facelets feature. As stated in this stackoverflow.com answer, you can use it to create a reuseable component with a single responsibility based on existing JSF components and/or HTML.

Use Composite Components if you want to create a single and reuseable custom UIComponent with a single responsibility using pure XML. Such a composite component usually consists of a bunch of existing components and/or HTML and get physically rendered as single component. E.g. a component which shows a rating in stars based on a given integer value. An example can be found in our Composite Component wiki page.

The wiki page contains however only an example of a composite component with a pure output function (showing a rating in stars). Creating a composite component based on a bunch of closely related UIInput components is a little tougher, but it's not demonstrated in the wiki page. So, let's write a blog about it.

Back to top

Bind java.util.Date value to 3 day/month/year dropdowns

Although the calendar popup is becoming more popular these days, a not uncommon requirement is to have a date selection by three dropdown lists representing the day, month and year. In JSF terms, you'd thus need three <h:selectOneMenu> components and a little bit of ajax or even plain vanilla JavaScript in order to get the days right depending on the selected month and year. Not every month has the same amount of days and a particular month has even a different amount of days depending on the year.

Let's start with some XHTML first:

<ui:component
    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:cc="http://java.sun.com/jsf/composite"
>
    <cc:interface componentType="inputDate">
        <cc:attribute name="value" type="java.util.Date"
            shortDescription="The selected Date. Defaults to today." />
        <cc:attribute name="maxyear" type="java.lang.Integer"
            shortDescription="The maximum year. Defaults to current year." />
        <cc:attribute name="minyear" type="java.lang.Integer"
            shortDescription="The minimum year. Defaults to maxyear minus 100." />
    </cc:interface>
    <cc:implementation>
        <span id="#{cc.clientId}" style="white-space:nowrap">
            <h:selectOneMenu id="day" binding="#{cc.day}" converter="javax.faces.Integer">
                <f:selectItems value="#{cc.days}" />
            </h:selectOneMenu>
            <h:selectOneMenu id="month" binding="#{cc.month}" converter="javax.faces.Integer">
                <f:selectItems value="#{cc.months}" />
                <f:ajax execute="day month" listener="#{cc.updateDaysIfNecessary}" />
            </h:selectOneMenu>
            <h:selectOneMenu id="year" binding="#{cc.year}" converter="javax.faces.Integer">
                <f:selectItems value="#{cc.years}" />
                <f:ajax execute="day year" listener="#{cc.updateDaysIfNecessary}" />
            </h:selectOneMenu>
        </span>
    </cc:implementation>
</ui:component>

Save it as /resources/components/inputDate.xhtml.

The componentType attribute of the <cc:interface> tag is perhaps new to you. It basically allows you to bind the composite component to a so-called backing component. This must be an instance of UIComponent and implement at least the NamingContainer interface (as required by the JSF composite component specification). Given that we basically want to create an input component, we'd like to extend from UIInput. The component type inputDate represents the component type and should be exactly the same value as is been declared in the value of the @FacesComponent annotation. The concrete backing component instance is available by the implicit EL variable #{cc} inside the <cc:implementation>.

The three <h:selectOneMenu> components are all via binding attribute bound as UIInput properties of the backing component which allows easy access to the submitted values and the (local) values. The <f:selectItems> also obtains all available values from the backing component. The <f:ajax> listener is also declared in the backing component. The enduser has only to provide a java.util.Date property as composite component value. The backing component does all the heavy lifting job.

Oh, there's also a <span> with the client ID of the composite component. This allows easy referencing in ajax updates from outside as follows:

<my:inputDate id="foo" ... />
...
<f:ajax ... render="foo" />

The composite component is by its own client ID available in the JSF component tree and thus accessible for ajax updates, but this client ID is by default nowhere represented by a HTML element and thus JavaScript wouldn't be able to find it in the HTML DOM (via document.getElementById() and so on) in order to update the HTML representation. So you need to supply your own HTML representation. This is in detail explained in the following stackoverflow.com questions:

Back to top

Backing component of the composite component

Here's the necessary Java code!

package com.example;

import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import javax.faces.component.FacesComponent;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIInput;
import javax.faces.component.UINamingContainer;
import javax.faces.context.FacesContext;
import javax.faces.convert.ConverterException;
import javax.faces.event.AjaxBehaviorEvent;

@FacesComponent("inputDate")
public class InputDate extends UIInput implements NamingContainer {

    // Fields -------------------------------------------------------------------------------------

    private UIInput day;
    private UIInput month;
    private UIInput year;

    // Actions ------------------------------------------------------------------------------------

    /**
     * Returns the component family of {@link UINamingContainer}.
     * (that's just required by composite component)
     */
    @Override
    public String getFamily() {
        return UINamingContainer.COMPONENT_FAMILY;
    }

    /**
     * Set the selected and available values of the day, month and year fields based on the model.
     */
    @Override
    public void encodeBegin(FacesContext context) throws IOException {
        Calendar calendar = Calendar.getInstance();
        int maxYear = getAttributeValue("maxyear", calendar.get(Calendar.YEAR));
        int minYear = getAttributeValue("minyear", maxYear - 100);
        Date date = (Date) getValue();

        if (date != null) {
            calendar.setTime(date);
            int year = calendar.get(Calendar.YEAR);

            if (year > maxYear || minYear > year) {
                throw new IllegalArgumentException(
                    String.format("Year %d out of min/max range %d/%d.", year, minYear, maxYear));
            }
        }

        day.setValue(calendar.get(Calendar.DATE));
        month.setValue(calendar.get(Calendar.MONTH) + 1);
        year.setValue(calendar.get(Calendar.YEAR));
        setDays(createIntegerArray(1, calendar.getActualMaximum(Calendar.DATE)));
        setMonths(createIntegerArray(1, calendar.getActualMaximum(Calendar.MONTH) + 1));
        setYears(createIntegerArray(maxYear, minYear));
        super.encodeBegin(context);
    }

    /**
     * Returns the submitted value in dd-MM-yyyy format.
     */
    @Override
    public Object getSubmittedValue() {
        return day.getSubmittedValue()
            + "-" + month.getSubmittedValue()
            + "-" + year.getSubmittedValue();
    }

    /**
     * Converts the submitted value to concrete {@link Date} instance.
     */
    @Override
    protected Object getConvertedValue(FacesContext context, Object submittedValue) {
        try {
            return new SimpleDateFormat("dd-MM-yyyy").parse((String) submittedValue);
        }
        catch (ParseException e) {
            throw new ConverterException(e); // This is not to be expected in normal circumstances.
        }
    }

    /**
     * Update the available days based on the selected month and year, if necessary.
     */
    public void updateDaysIfNecessary(AjaxBehaviorEvent event) {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.DATE, 1);
        calendar.set(Calendar.MONTH, (Integer) month.getValue() - 1);
        calendar.set(Calendar.YEAR, (Integer) year.getValue());
        int maxDay = calendar.getActualMaximum(Calendar.DATE);

        if (getDays().length != maxDay) {
            setDays(createIntegerArray(1, maxDay));

            if ((Integer) day.getValue() > maxDay) {
                day.setValue(maxDay); // Fix the selected value if it exceeds new max value.
            }

            FacesContext context = FacesContext.getCurrentInstance(); // Update day field.
            context.getPartialViewContext().getRenderIds().add(day.getClientId(context));
        }
    }

    // Helpers ------------------------------------------------------------------------------------

    /**
     * Return specified attribute value or otherwise the specified default if it's null.
     */
    @SuppressWarnings("unchecked")
    private <T> T getAttributeValue(String key, T defaultValue) {
        T value = (T) getAttributes().get(key);
        return (value != null) ? value : defaultValue;
    }

    /**
     * Create an integer array with values from specified begin to specified end, inclusive.
     */
    private static Integer[] createIntegerArray(int begin, int end) {
        int direction = (begin < end) ? 1 : (begin > end) ? -1 : 0;
        int size = Math.abs(end - begin) + 1;
        Integer[] array = new Integer[size];

        for (int i = 0; i < size; i++) {
            array[i] = begin + (i * direction);
        }

        return array;
    }

    // Getters/setters ----------------------------------------------------------------------------

    public UIInput getDay() {
        return day;
    }

    public void setDay(UIInput day) {
        this.day = day;
    }

    public UIInput getMonth() {
        return month;
    }

    public void setMonth(UIInput month) {
        this.month = month;
    }

    public UIInput getYear() {
        return year;
    }

    public void setYear(UIInput year) {
        this.year = year;
    }

    public Integer[] getDays() {
        return (Integer[]) getStateHelper().get("days");
    }

    public void setDays(Integer[] days) {
        getStateHelper().put("days", days);
    }

    public Integer[] getMonths() {
        return (Integer[]) getStateHelper().get("months");
    }

    public void setMonths(Integer[] months) {
        getStateHelper().put("months", months);
    }

    public Integer[] getYears() {
        return (Integer[]) getStateHelper().get("years");
    }

    public void setYears(Integer[] years) {
        getStateHelper().put("years", years);
    }

}

The backing component instance has basically a lifetime of exactly one HTTP request. This means that it's recreated on every single HTTP request, like as a request scoped managed bean. So if you ever manually create variables during an encodeXxx() method which you'd like to be available in any of the component's methods during the subsequent postback request (the form submit), then you should not be assigning it as a field of the class. It would get lost by end of initial request and reinitialize to default (e.g. null) during the postback request.

If you've ever developed a custom UIComponent, or looked in the source code of an existing UIComponent, then you have probably already seen the StateHelper which is available by the inherited getStateHelper() method. This basically takes care about the component's state across postbacks. It has basically the same lifetime as a view scoped managed bean. You can use the put() method to store a variable in the component's state. You can use the get() or eval() method to get or EL-evaluate a variable from the component's state. In this particular backing component, this is done so for the <f:selectItems> dropdown values. Look at their getters/setters, they all delegate directly to StateHelper.

This is not done so for the UIInput properties which represents each of the <h:selectOneMenu> components. JSF will namely already automatically set them via binding attribute during building/restoring of the view. Even more, you're not supposed to save complete UIComponent instances in component's state. Note that you can also use e.g. UIInput day = (UIInput) findComponent("day"); instead of binding="#{cc.day}" with a day property, but this may result in some boilerplate code as you need this in multiple methods.

When the component is about to be rendered, the encodeBegin() method is invoked which basically obtains the maxyear, minyear and value attributes and initializes the dropdowns. The first two attributes represent the maximum and minimum value of the "year" dropdown, which in turn defaults to respectively the current year and the maximum year minus 100. The input value is as per <cc:attribute type> already expected to be an instance of java.util.Date. Note that the value is obtained by the getValue() method which is inherited from UIInput. After a simple max/min year check, the individual day, month and year fields are obtained from the calendar and set as values of dropdown components. Finally the available values of the dropdown components are filled.

When the form is submitted and the request values have been applied (which is basically what the decode() method of the input component should be doing, but as we're delegating it to the three dropdown components, we actually don't need to override anything here), the getSubmittedValue() method will be invoked in order to obtain the "raw" submitted value which is used for the usual conversion/validation steps. The backing component will return the submitted value as a string in dd-MM-yyyy format. It's important that this value is not null, otherwise JSF will skip the conversion/validation/modelupdate. If you happen to use MyFaces instead of Mojarra, then you need to replace getSubmittedValue() call on child component by getValue():


    @Override
    public Object getSubmittedValue() {
        return day.getValue()
            + "-" + month.getValue()
            + "-" + year.getValue();
    }

Or, if you'd like to cover both:


    @Override
    public Object getSubmittedValue() {
        return (day.getSubmittedValue() == null && day.isLocalValueSet() ? day.getValue() : day.getSubmittedValue())
            + "-" + (month.getSubmittedValue() == null && month.isLocalValueSet() ? month.getValue() : month.getSubmittedValue())
            + "-" + (year.getSubmittedValue() == null && year.isLocalValueSet() ? year.getValue() : year.getSubmittedValue());
    }

Here, the child component is first checked if it has no submitted value and has its local value set, and if that's the case, then return its local value instead of the submitted value. This is needed because Mojarra and MyFaces don't agree on whether to process the UIInput component itself first before processing its children, or the other way round. Mojarra first processes the UIInput component itself before its children, and therefore needs getSubmittedValue(). MyFaces, on the other hand, first processes the children before the UIInput component itself, and therefore needs getValue().

Shortly after getting the submitted value, JSF will invoke the getConvertedValue() method, passing exactly the submitted value as 2nd argument. Normally this method is not to be overridden and everything is delegated to default JSF Converter mechanisms, but the backing component has it overriden to take the opportunity to convert the submitted value to a concrete java.util.Date instance which will ultimately be updated in the model.

Note that no validation is performed and that's not necessary, because it's impossible for a hacker to provide a different submitted value than shown in the dropdowns (e.g. a day of 33). In any attempt, JSF would simply fail the usual way with Validation Error: Value is not valid on the associated dropdown.

Finally, there's a "proprietary" ajax action listener method updateDaysIfNecessary() which should update the day dropdown depending on the value of the month and year dropdowns, if necessary. It basically determines the maximum day of the given month and year and checks if the available days and currently selected day needs to be altered if the maximum day has been changed. If that's the case, then a programmatic ajax render will be instructed by adding the client ID of the day dropdown to PartialViewContext#getRenderIds().

Usage example

Here's how you can use it in an arbitrary form. First declare the composite component's XML namespace in the top level XML element:

xmlns:my="http://java.sun.com/jsf/composite/components"

The prefix "my" is fully to your choice. The /components part of the path is also fully to your choice, it's basically the name of the subfolder in the /resources folder where you've placed the composite component XHTML file. Given this XML namespace, it's thus available as <my:inputDate> as follows:


<h:form>
    <my:inputDate value="#{bean.date1}" /><br />
    <my:inputDate value="#{bean.date2}" maxyear="2050" /><br />
    <my:inputDate value="#{bean.date3}" maxyear="2000" minyear="1990" /><br />
    <h:commandButton value="Submit" action="#{bean.submit}" />
</h:form>

The date1, date2 and date3 properties are all of type java.util.Date. Nothing special, it can even be null, it would default to today's date anyway. See also this little video demo of how the component behaves in the UI.