Accessing dynamic UIComponents in JSF Managed Bean

Jini Samuel picture Jini Samuel · Sep 2, 2013 · Viewed 10k times · Source

I have a JSF page in which I am iterating a list within <h:dataTable> to display some rows containing checkbox, some text and a textbox.

I have to validate the <h:dataTable> so that when a user checks the checkbox, he has to enter some text inside the textbox.

This is my JSF page.

 <h:form prependId="false" id="form">
    <h:dataTable id="rm" width="100%" cellspacing="4"
    value="#{controller.alertTriggers}" var="alt"
        columnClasses="c1,c2,c3,c4">                    


            <h:column>
              <h:selectBooleanCheckbox value="#{alt.checkValue}" id="checkbox"/>
           </h:column>
            <h:column>
               <h:outputText value="#{alt.id}" />                                       
            </h:column>
            <h:column>
              <h:outputFormat value="#{alt.msg1}" />                              
            </h:column>
            <h:column>                                              
                 <h:message for="emailID" id="email" styleClass="validation-error"/>
                 <h:inputText value="#{alt.mailId}" id="emailID" style="width: 87%;" />

            </h:column>                                         

    </h:dataTable>                                          
</h:form>                                       

I have given the id of all the checkboxes as checkbox and id of all textboxes as emailID. When the page is rendered, on checking the page source, I found that the ids of the checkboxes are 'rm:0:checkbox','rm:1:checkbox'... and those of the textboxes are 'rm:0:emailID','rm:1:emailID'..

In the controller, I want to access these dynamic text boxes and check boxes for which I use the following code:

 FacesContext context = FacesContext. getCurrentInstance();


                  for (int i=0;i<9;i++){
                       UIInput u=(UIInput) FacesContext.getCurrentInstance().getViewRoot().findComponent( "form:rm:" +i+":checkbox" );
                        if ((Boolean) u.getValue()){
                              UIInput ui=(UIInput) FacesContext.getCurrentInstance().getViewRoot().findComponent( "form:rm:" +i+":emailID" );

                               //code

                       }
                 }

But this is giving java.lang.NullPointerException

Even using the code:

  UIInput u=(UIInput) FacesContext.getCurrentInstance().getViewRoot().
   findComponent( "form:rm:0:checkbox" ); gives the same exception.

But if I use

     UIInput u=(UIInput) FacesContext.getCurrentInstance().getViewRoot().
       findComponent( "form:rm:checkbox" );

it doesn't give a Null Pointer Exception but I don't know whether which checkbox's value is it giving.

So, in summary,

JSF generates the ids as rm:1:checkbox,rm:2:checkbox etc., but when I try to access this UI component in JSF page, I am not able to do it.

Am I missing something ?

Answer

BalusC picture BalusC · Sep 2, 2013

The FacesContext#getViewRoot() returns the JSF component tree. This is exactly the same tree as represented by XHTML source code after thinking away all taghandlers (JSTL, <ui:include>, etc). You need to realize that there's only one <h:selectBooleanCheckbox id="checkbox"> in there. It's available in UIViewRoot#findComponent() by exactly that ID "form:rm:checkbox".

It's just its HTML representation which get regenerated multiple times depending on the current iteration round of the parent <h:dataTable>. This generated HTML representation has in turn client IDs with the current row index inlined. This HTML representation is obviously not available in the component tree.

The component's state (the submitted values, etc), is also only available during iterating the <h:dataTable> and not before or after. Essentially, you're trying to access the value of the component in bean's action method while the <h:dataTable> component isn't iterating over it, so the values will always return null.

In order to programmatically simulate a <h:dataTable> iteration so that you can collect the desired values, you need to visit the <h:dataTable> by UIComponent#visitTree() and collect the information of interest in the VisitCallback implementation.

UIData table = (UIData) viewRoot.findComponent("form:rm");
table.visitTree(VisitContext.createVisitContext(FacesContext.getCurrentInstance()), new VisitCallback() {
    @Override
    public VisitResult visit(VisitContext context, UIComponent target) {
        if (target instanceof HtmlSelectBooleanCheckbox) {
            HtmlSelectBooleanCheckbox checkbox = (HtmlSelectBooleanCheckbox) target;
            System.out.println("id: " + checkbox.getId());
            System.out.println("value: " + checkbox.getValue());
            // Collect them in an arraylist orso.
        }

        return VisitResult.ACCEPT;
    }
});

However, you're going in completely the wrong direction as to solving the concrete problem. You should be performing the validation in a validator associated with the input component you'd like to validate, not in the action method. Here's how you could solve the particular concrete functional requirement of validating the input field as required only when the checkbox in the same row is checked:

<h:column>
    <h:selectBooleanCheckbox binding="#{checkbox}" ... />
</h:column>
<h:column>                                              
    <h:inputText ... required="#{checkbox.value}" />
</h:column>

That's all. Additional advantage is, the validators run while the <h:dataTable> is iterating, so you don't need all that visitTree() code as well.