jsfrichfacesjboss-seam

Seam 2.3 + JSF + AJAX. When re-rendering one panel methods in other panel get called too


my project is built using JBoss Seam 2.3, JSF 2.1 and richfaces. There's something I don't fully understand when re-rendering components using richfaces aj4 calls.

When some action is executed using a4j and the value of the render attribute points to some h:panelGroup id, methods in other panel are called too.

I prepared some code to demonstrate this.

BeanA.java. Simulates data access.

package test;

import java.util.Arrays;
import java.util.List;

import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.log.Log;

@Scope(ScopeType.CONVERSATION)
@Name("beanA")
public class BeanA {

    @Logger
    private Log log;

    public List<Integer> list() {
        log.info("beanA.list()");
        // DB Query simulation
        Integer[] result = {1,2,3};
        return Arrays.asList(result);
    }

}

BeanB.java. Simple clicks counter.

package test;

import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.log.Log;

@Scope(ScopeType.CONVERSATION)
@Name("beanB")
public class BeanB {

    @Logger
    private Log log;

    private int counter = 0;

    public void dumbAction() {
        // do nothing
        log.info("beanB.dumbAction()");
        counter++;
    }

    public int getCounter() {
        return counter;
    }

    public void setCounter(int counter) {
        this.counter = counter;
    }

}

test.xhtml

<f:view xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:a="http://richfaces.org/a4j"
    xmlns:richext="http://java.sun.com/jsf/composite/richext"
    xmlns:s="http://jboss.org/schema/seam/taglib" contentType="text/html"
        xmlns:c="http://java.sun.com/jsp/jstl/core">
    <h:html>
    <h:head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>TEST</title>
    </h:head>
    <h:body>

        <h:panelGroup id="listPanel">
            <ul>
                <li>
                    <h:outputText value="a:repeat" />
                </li>
                <a:repeat value="#{beanA.list()}" var="i">
                    <li>
                        <h:outputText value="#{i}" />
                    </li>
                </a:repeat>
            </ul>
<!--            <ul> -->
<!--                <li> -->
<!--                    <h:outputText value="ui:repeat" /> -->
<!--                </li> -->
<!--                <ui:repeat value="#{beanA.list()}" var="i"> -->
<!--                    <li> -->
<!--                        <h:outputText value="#{i}" /> -->
<!--                    </li> -->
<!--                </ui:repeat> -->
<!--            </ul> -->
<!--            <ul> -->
<!--                <li> -->
<!--                    <h:outputText value="c:foreach" /> -->
<!--                </li> -->
<!--                <c:forEach items="#{beanA.list()}" var="i"> -->
<!--                    <li> -->
<!--                        <h:outputText value="#{i}" /> -->
<!--                    </li> -->
<!--                </c:forEach> -->
<!--            </ul> -->
        </h:panelGroup>

        <h:form>
            <a:commandLink value="Dumb action" action="#{beanB.dumbAction()}"
                execute="@this" render="timesDumbActionPanel" />
        </h:form>

        <h:panelGroup id="timesDumbActionPanel">
            <h:outputText value="#{beanB.counter}" />
        </h:panelGroup>
    </h:body>
    </h:html>
</f:view>

When I load the page /test.seam beanA.list() is called once. When I click "Dumb action" link the following is logged:

19:28:21,461 INFO  [test.BeanA] (http--0.0.0.0-80-1) beanA.list()
19:28:21,461 INFO  [test.BeanB] (http--0.0.0.0-80-1) beanB.dumbAction()
19:28:21,461 INFO  [test.BeanA] (http--0.0.0.0-80-1) beanA.list()

beanA.list() is called twice and I don't understand why. When using ui:repeat instead of a:repeat beanA.list() is called like 11 times. I also tried to change scopes but the results were the same.

The behaviour I expect is just call beanB.dumbAction() and re-render the panel to refresh counter.

I do know getters are called multiple times, but does this apply to all methods too?

What I do to avoid this is just create a field for the list and modify the getter to initialize it if it's not. Something like this:

@Name("beanA")
public class BeanA {

    @Logger
    private Log log;

    private List<Integer> list;

    public void initList() {
        log.info("beanA.list()");
        // DB Query simulation
        Integer[] result = { 1, 2, 3 };
        setList(Arrays.asList(result));
    }

    public List<Integer> getList() {
        if (list == null)
            initList();
        return list;
    }

    public void setList(List<Integer> list) {
        this.list = list;
    }

}

The getter is still called but not initList() which is just OK. This way I avoid executing queries more than necessary. The only problem is when data changes. I need to manually call initList() again and it's not enough with just re-rendering it's panel.

Maybe I'm missing something with JSF lifecycle or something, could you please give me some hint on this? Thank you!!


Solution

  • I do know getters are called multiple times, but does this apply to all methods too?

    It's not like JSF knows that a specific method is a getter. If you're using in a place that asks for data, e.g. @value then it's a getter.

    The reason you're seeing multiple calls to getters is because when JSF/RichFaces is looking for a certain element it has to go through all the elements before it. In your case in order to find timesDumbActionPanel it has to go through listPanel and check inside the a4j:repeat.

    The only problem is when data changes. I need to manually call initList() again and it's not enough with just re-rendering it's panel.

    You can call initList() from whatever method is changing the data.