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

2

8

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.

Amphoteric answered 30/12, 2014 at 13:47 Comment(3)
#5461024 i think this is will help you. ;)Katinakatine
It's not so clear what you want to accomplish. Are from and to inputs and is text an output string representation of the range?Royal
@MicheleMariotti, no from and to should be to Calendar objects. When I change Range in a composite component, it should change these values in backing bean too. Text is just for testing purposes. Actually I already know how to perform this but if I encapsulate from and to in a single object, and set it via getSubmittedValue, but I doubt can it be done in two objects separately.Amphoteric
R
5

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
}
Royal answered 8/1, 2015 at 12:36 Comment(7)
Hi, it not exactly what do I need, I've written: 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}"/>, it's useful when I need to encapsulate some business logic inside backing component.Amphoteric
You probably are misusing the word inject, which is commonly related to container-managed annotations like @ManagedProperty, @Inject, @EJB, ... clarify inject and maybe I'll can help :)Royal
Perhaps you're right. I meant to mutate objects which I pass from backing bean to composite component inside backing component of composite component. Perhaps this definition is more correct, I will edit my post.Amphoteric
Hi, Now I agree with you now that it's conceptually incorrect. I just didn't know it before this. The right solution will to encapsulate datamodel object in a single object and work with it by getSubmittedValue. Anyway, I've learnt a lot from your code, so thank you very much for you effort. Btw, your solution only partially works, it does update text field, and does update from field. But to field doesn't updated, but I get correct value in SelectEvent inside RangeBean. And It didn't update 'pangelGrid', only <h:outputText> one at time. Again, thank you for your effort!Amphoteric
JSF sometimes can be very counterintuitive... I think a better solution is to use the approch of PrimeFaces range slider (bottom of page) rendering two calendars instead of a slider, though it can be a lot of work... I'd avoid DataModel (it's for Collections iteration) and either get/setSubmittedValue (should be used in Custom Components, not in Composite, generally). However, in my environment (JDK 1.7.0_51, Glassfish 4.1, Mojarra 2.2.8-r3, PrimeFaces 5.1), it works completely.Royal
Can you advice when I should make Composite Component and when Custom. Making a Composite Component much simpler than Custom Component, even @BalusC promotes this approach here: http://balusc.blogspot.co.il/2013/01/composite-component-with-multiple-input.html In my case I have specific requirement for **Range Chooser**, so I couldn't use Primefaces's components. I implemented it by using JQuery Datepicker` with customization and wrapped it in Composite Component. I think that general approach here: "if you can make it by using Composite Component, do it with it. Am I right?Amphoteric
Using an advanced composite like @BalusC in his article is a far better idea, see the other answer. Too bad you can't use PrimeFaces... so you are forced to reinvent the wheel!Royal
R
4

I rewrote the code using BalusC tecnique (and without PrimeFaces):

the form:

<h:form>
    <e:inputRange value="#{rangeBean.range}">
        <p:ajax event="change" process="@namingcontainer" update="@form:output"
            listener="#{rangeBean.onChange}" />
    </e:inputRange>

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

the composite:

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

<cc:implementation>

    <div id="#{cc.clientId}">
        <h:inputText id="from" binding="#{cc.from}">
            <f:convertDateTime type="date" pattern="dd/MM/yyyy" />
        </h:inputText>
        <h:inputText id="to" binding="#{cc.to}">
            <f:convertDateTime type="date" pattern="dd/MM/yyyy" />
        </h:inputText>
    </div>

</cc:implementation>

the backing component:

@FacesComponent("rangeComponent")
public class RangeComponent extends UIInput implements NamingContainer
{
    private UIInput from;
    private UIInput to;

    @Override
    public String getFamily()
    {
        return UINamingContainer.COMPONENT_FAMILY;
    }

    @Override
    public void encodeBegin(FacesContext context) throws IOException
    {
        String value = (String) getValue();
        if(value != null)
        {
            String fromString = StringUtils.substringBefore(value, "-");
            String toString = StringUtils.substringAfter(value, "-");

            try
            {
                from.setValue(from.getConverter().getAsObject(context, from, fromString));
            }
            catch(Exception e)
            {
                from.setValue(new Date());
            }

            try
            {
                to.setValue(to.getConverter().getAsObject(context, to, toString));
            }
            catch(Exception e)
            {
                to.setValue(new Date());
            }
        }

        super.encodeBegin(context);
    }

    @Override
    public Object getSubmittedValue()
    {
        return (from.isLocalValueSet() ? from.getValue() : from.getSubmittedValue()) + "-" + (to.isLocalValueSet() ? to.getValue() : to.getSubmittedValue());
    }

    @Override
    protected Object getConvertedValue(FacesContext context, Object submittedValue)
    {
        return from.getSubmittedValue() + "-" + to.getSubmittedValue();
    }

    public UIInput getFrom()
    {
        return from;
    }

    public void setFrom(UIInput from)
    {
        this.from = from;
    }

    public UIInput getTo()
    {
        return to;
    }

    public void setTo(UIInput to)
    {
        this.to = to;
    }
}

and the managed bean:

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

    private String range = "01/01/2015-31/12/2015";

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

    public String getRange()
    {
        return range;
    }

    public void setRange(String range)
    {
        this.range = range;
    }
}

Note that managed bean only retains range property for get/set. From and to are gone, the backing component derives and rebuilds them itself.

Royal answered 12/1, 2015 at 2:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.