Search code examples
jsfjsf-2faceletscomposite-component

How to set the value of a composite component from outside the composite component


So, I have a composite component. Inside it, I have a regular text input, using one of the composite component's attributes for a value.

<h:inputTextarea id="#{cc.attrs.id}Text" value="#{cc.attrs.value}">
    <f:ajax event="blur" listener="#{someBean.onBlur}" />
</h:inputTextarea>

As you can see, I have an event inside the text box. This event opens a popup and populates it with the text on the composite control. It also saves a reference to the composite control that invoked it. Let me show you:

public void onBlur(AjaxBehaviorEvent event) {
    this.fieldReference = (UIInput) event.getSource();

    this.formula = this.fieldReference.getValueExpression("value")
            .getValue(FacesContext.getCurrentInstance().getELContext())
            .toString();

    this.displayPopup = true;
}

So far, so good. Now, the problem comes when I try to close the popup and then update the value on the composite component with the one entered on the popup. I try to do this:

public void accept(ActionEvent event) {
    this.fieldReference
            .getValueExpression("value")
            .setValue(FacesContext.getCurrentInstance().getELContext(), this.formula);
    this.displayPopup = false;
}

When I try that, I get:

javax.el.PropertyNotFoundException: //C:/myProject/Path/compositeComponentPage.xhtml at line 22 and column 183 value="#{cc.attrs.value}": Target Unreachable, identifier 'cc' resolved to null

Seems to me that the EL context on that request is different, and thus cannot resolve the variables in the composite component's expressions... but if I try to also store a reference to the ELContext object from the composite component's request (on the onBlur() method), then when I try to use it in accept(), I get:

javax.faces.event.AbortProcessingException: java.lang.IllegalStateException: Error the FacesContext is already released!

Using MyFaces 2.0.2 (the version that comes with WebSphere 8.5, I believe they modify it), and RichFaces 4.2.3.

Any ideas?


Solution

  • Well, I seem to have found a solution. Looking around, I found this small piece of knowledge in a totally unrelated article on BalusC's blog:

    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, saving a reference to the component is a very bad thing to do. Instead, I saved the client ID of the component and looked it up when closing the popup, then used setValue, which previously hadn't worked. Like this:

    public void onBlur(AjaxBehaviorEvent event) {
        UIInput component = (UIInput) event.getSource();
        this.componentId = component.getClientId(FacesContext.getCurrentInstance());
    
        this.formula = component.getValueExpression("value")
            .getValue(FacesContext.getCurrentInstance().getELContext())
            .toString();
    
        this.displayPopup = true;
    }
    
    public void accept(ActionEvent event) {
        UIInput component = (UIInput) FacesUtil.findComponent(this.componentId);
        component.setValue(this.formula);
    
        this.displayPopup = false;
    }
    

    So... I guess thanks, BalusC, you saved the day again!! :)