1
votes

I've searched and tried various things from around here and the web; prependId, using full id paths (:form:panelid:componentid kinda thing), and others.

I'm still confused. I'm still a JSF noob :)

My problem is that whatever I specify in the f:ajax render part isn't the only parts the get "executed". What I mean is, the id specified in render won't get rerendered on screen, but the value of that component do get called.

So:

inputText's f:ajax render=outA event=blur

when blur happens, outA's getter is called and it rerendered, but also the getter of other components are called, but they're not rerendered.

Specifically, in light of the listed code below:

A) When val1 loses focus, it's blur event fires, which has it call getValue1() and rerender outval1. Happy. But, getStuff() is ALSO called (it happens for the other blur and btnCalc also) BUT the result from getStuff() is NOT rendered to tblStuff dataTable. Why?

How can I fix it, so that on the blur event, only the getters relevent to the render=".." component is executed?

Should I maybe use different sections?

B) when the refresh button is clicked, then it will call getStuff AGAIN, and now show the new dataTable / ie that new data with ALL THE MANY ValueTO's that was added during the blur events and the btnReload click event.

C) For any one event named in A, the getStuff method is called exactly 8 times. ie, click inside inputbox, click outside input box, getStuff() * 8. Yet, getValue1 is called only twice?

D) Any good JSF2 books out there?

Here is the page:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">

<h:head>
    <title>Stuff</title>
</h:head>
<h:body>
        <h:form id="frmTrxMain" prependId="true">

            <h1>
                Stuff
            </h1>

            <h:dataTable title="Stuff" id="tblStuff" var="s" value="#{bean.stuff}" border="1">

                <h:column>
                    <f:facet name="header">
                        <h:outputText value="ID" />
                    </f:facet>
                    <h:outputText value="#{s.id}"/>
                </h:column>

                <h:column>
                    <f:facet name="header">
                        <h:outputText value="Name" />
                    </f:facet>
                    <h:outputText value="#{s.name}" />
                </h:column>

            </h:dataTable>

            <h:outputText value="No stuff to display" rendered="#{empty bean.stuff}"/>
            <h:commandButton value="Refresh" id="btnReload">
                <f:ajax render="tblStuff" event="click"/>
            </h:commandButton>

            <hr/>

            <h:panelGrid columns="3" id="pnlTwo">
                <h:outputLabel value="Value1"/>
                <h:inputText value="#{bean.value1}" id="val1">
                    <f:ajax event="blur" render="outVal1"/>
                </h:inputText>
                <h:outputText id="outVal1" value="Entered: #{bean.value1}" />

                <h:outputLabel value="Value2"/>
                <h:inputText value="#{bean.value2}" id="val2">
                    <f:ajax event="blur" render="outVal2"/>
                </h:inputText>
                <h:outputText id="outVal2" value="Entered: #{bean.value2}" />

                <h:commandButton value="Calc" id="btnCalc"> 
                  <f:ajax event="click" render="outSum"/> 
                </h:commandButton> 
            </h:panelGrid>

            <h:outputLabel id="outSum" value="#{bean.sum}"/>
        </h:form>
</h:body>
</html>

And the backing bean:

import java.util.ArrayList;
import java.util.List;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;

@ManagedBean(name = "bean")
@SessionScoped
public class TestStuffBean {

private int id=1;
private String name="a";
//test.xhtml
private String value1;
private String value2;

private List<TestVO> stuff = new ArrayList<TestVO>();

public String getValue1() {
    return value1;
}

public void setValue1(String value1) {
    this.value1 = value1;
}

public String getValue2() {
    return value2;
}

public void setValue2(String value2) {
    this.value2 = value2;
}

public String getSum() {

    String result = "";

    int sum;
        try {
            sum = Integer.parseInt(value1) + Integer.parseInt(value2);
            result = Integer.toString(sum);
        } catch (NumberFormatException e) {
            result = "Enter two integers";
        }

    return result;
}

public List<TestVO> getStuff() {
    //the VO is just a POJO with a contructor that takes 
            //two strings and a getter/setter for each id and name
        stuff.add(new TestVO(Integer.toString(id), name+id));
        id++;

    return stuff;
}
}
1

1 Answers

0
votes

I'm not a JSF 2 expert since we're still using 1.2 but I guess they have the same properties here: the getters are called often, e.g. when reconstrucing the view in the restore-view phase. Thus you can't rely on the getters only being called for the actually rendered part. In fact, you shouldn't do any costly operations (like db access) in your getters - in most cases you don't know how often they're actually called in one cycle.

Especially your getStuff() method should be changed, it's also almost always bad design to have a getter cause side effects like something being added to an internal list, like in this case.

Edit: To clarify the comment on getters:

Normally a getter should not do costly operations, since you don't know how often JSF will call them. Since you still have to get some data from some source (like a database) you might provide methods to only do that (like loadDataFromDB()) and fill some data structure in the managed bean. The getter might then just deliver that data structure.

You might add a check for the data structure not being initialized and if not call the load method in the getter, but I'd only use that as a last option. Generally you should be able to separately call the load method, e.g. by clicking a refresh button or when navigating to the page - that's what we do.