How do I pass an attribute from composite component to backing bean by using backing component?

Anatoly picture Anatoly · Dec 30, 2014 · Viewed 9.3k times · Source

I have the following code in my facelet page:

  <hc:rangeChooser1 id="range_chooser" 
                    from="#{testBean.from}"
                    to="#{testBean.to}"
                    listener="#{testBean.update}"
                    text="#{testBean.text}">
        <f:ajax event="rangeSelected"
                execute="@this"
                listener="#{testBean.update}"                   
                render=":form:growl range_chooser"/>
    </hc:rangeChooser1>

This is my composite component:

<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"
    xmlns:p="http://primefaces.org/ui">
    <cc:interface componentType="rangeChooser">
        <!-- Define component attributes here -->
        <cc:clientBehavior name="rangeSelected" event="change" targets="hiddenValue"/>
        <cc:attribute name="from" type="java.util.Calendar"/>
        <cc:attribute name="to" type="java.util.Calendar"/>
        <cc:attribute name="text" type="java.lang.String"/>

    </cc:interface>


    <cc:implementation>

        <div id="#{cc.clientId}">
                 ...
                <p:inputText id="hiddenValue" value="#{cc.attrs.text}"/>
                 ...
        </div>
    </cc:implementation>
</ui:component>

How do I pass attributes from, to and text from composite component to backing bean? I mean inject these values in backing component, and not through

<p:inputText id="hiddenValue" value="#{cc.attrs.text}"/>

Update: there's more correct definition what do I need: Be able to mutate objects which I pass from the backing bean to the composite component inside a backing component of the composite component. So when I perform process or execute my composite component I get the updated values.

This is my backing component:

@FacesComponent("rangeChooser")
public class RangeChooser extends UIInput implements NamingContainer  {
    private String text;
    private Calendar from;
    private Calendar to;

    @Override
    public void encodeBegin(FacesContext context) throws IOException{

        super.encodeBegin(context);
    }


    public String getText() {
        String text = (String)getStateHelper().get(PropertyKeys.text);
        return text;
    }

    public void setText(String text) {
        getStateHelper().put(PropertyKeys.text, text);
    }

    /*
        same getters and setters for Calendar objects, from and to
    */

}

I just can't realize how do I move on? In general I need to take a value from <p:inputText id="hiddenValue" value="#{cc.attrs.text}"/> and convert it to two Calendars object from and to. It will be great if somebody can point me toward right direction from here. I know that I need to use getAttributes().put(key,value) but don't know where to put this code. Thank you in advance.

Answer

Michele Mariotti picture Michele Mariotti · Jan 8, 2015

Based on your comments this is what you expect.

Note that even if this implementation works, it is conceptually incorrect!

You are considering from and to as INPUTS (not input components, but input values) and text as OUTPUT. This is not the way JSF is intended to work!

However, here it is

<cc:interface componentType="rangeComponent">
    <cc:attribute name="from" />
    <cc:attribute name="to" />
    <cc:clientBehavior name="rangeSelected" event="dateSelect" targets="from to"/>
</cc:interface>

<cc:implementation>

    <div id="#{cc.clientId}">
        <p:calendar id="from" value="#{cc.attrs.from}"/>
        <p:calendar id="to" value="#{cc.attrs.to}"/>
    </div>

</cc:implementation>

used in page:

<h:form>
    <e:inputRange from="#{rangeBean.from}" to="#{rangeBean.to}" text="#{rangeBean.text}">
        <p:ajax event="rangeSelected" process="@namingcontainer" update="@form:output" listener="#{rangeBean.onChange}" />
    </e:inputRange>

    <h:panelGrid id="output" columns="1">
        <h:outputText value="#{rangeBean.from}"/>
        <h:outputText value="#{rangeBean.to}"/>
        <h:outputText value="#{rangeBean.text}"/>
    </h:panelGrid>
</h:form>

with this backing component:

@FacesComponent("rangeComponent")
public class RangeComponent extends UINamingContainer
{
    @Override
    public void processUpdates(FacesContext context)
    {
        Objects.requireNonNull(context);

        if(!isRendered())
        {
            return;
        }

        super.processUpdates(context);

        try
        {
            Date from = (Date) getValueExpression("from").getValue(context.getELContext());
            Date to = (Date) getValueExpression("to").getValue(context.getELContext());

            ValueExpression ve = getValueExpression("text");
            if(ve != null)
            {
                ve.setValue(context.getELContext(), from + " - " + to);
            }
        }
        catch(RuntimeException e)
        {
            context.renderResponse();
            throw e;
        }
    }
}

with this backing bean:

@ManagedBean
@ViewScoped
public class RangeBean implements Serializable
{
    private static final long serialVersionUID = 1L;

    private Date from = new Date(1000000000);
    private Date to = new Date(2000000000);
    private String text = "range not set";

    public void onChange(SelectEvent event)
    {
        Messages.addGlobalInfo("[{0}] changed: [{1}]", event.getComponent().getId(), event.getObject());
    }

    // getters/setters
}