csvan
csvan

Reputation: 9464

Binding kills backing beans...what am I doing wrong?

I have the following 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:p="http://primefaces.org/ui"
  xmlns:h="http://java.sun.com/jsf/html">

<body>

    <ui:composition template="./templates/fireLeftMenuTemplate.xhtml">

        <ui:define name="left">
            <h:form>
                <p:menu model="#{gradingBean.courseMenu}"/>
            </h:form>
        </ui:define>

        <ui:define name="content">
            <h:form>
                <p:accordionPanel binding="#{gradingBean.assignmentView}"/>         
            </h:form>
        </ui:define>

    </ui:composition>

</body>

The GradingBean:

@Named("gradingBean")
@ViewScoped
public class GradingBean {

@EJB
private AssignmentManager assignmentManager;

/*
 * The assignmentMenu, listing all assignments for each course currently
 * assisted by this grader
 */
private final DefaultMenuModel courseView = new DefaultMenuModel();
private final AccordionPanel assignmentView = new AccordionPanel();

public GradingBean() {
    FireLogger.logInfo("Created GradingBean for user {0}", FireUtil.getLoggedinUserEmail());
}

@PostConstruct
private void constructBean() {
    constructAssignmentView();
    constructCourseMenu();
    FireLogger.logInfo("Constructed bean");
}

private void constructAssignmentView() {

    Tab tab = new Tab();
    tab.setTitle("Hello");

    assignmentView.getChildren().add(tab);
    assignmentView.setRendered(true);

    FireLogger.logInfo("Constructed assignmentView");
}

private void constructCourseMenu() {

    /*
     * For now we default to just one course at a time, since we have not
     * implemented support for multiple courses as of yet.
     */
    Submenu defaultCourse = new Submenu();
    defaultCourse.setLabel("Objekt Orienterad Programmering IT");

    /*
     * add each assignment associated with this course
     */
    ExpressionFactory expressionFactory =
            FacesContext.getCurrentInstance()
            .getApplication()
            .getExpressionFactory();

    for (Assignment assignment : assignmentManager.getAll()) {

        MenuItem menuItem = new MenuItem();
        menuItem.setValue(assignment.getTitle());

        MethodExpression expression = expressionFactory.createMethodExpression(
                FacesContext.getCurrentInstance().getELContext(), "#{gradingBean.printstuff('yay!')}", String.class, new Class[0]);

        menuItem.setActionExpression(expression);
        defaultCourse.getChildren().add(menuItem);
    }
    courseView.addSubmenu(defaultCourse);

    FireLogger.logInfo("Constructed courseMenu");
}

public String printstuff(String stuff) {
    FireLogger.logInfo("Printing! " + stuff);
    return "hej";
}

public DefaultMenuModel getCourseMenu() {
    return courseView;
}

public AssignmentManager getAssignmentManager() {
    return assignmentManager;
}

public DefaultMenuModel getCourseView() {
    return courseView;
}

public AccordionPanel getAssignmentView() {
    return assignmentView;
}

public void setAssignmentManager(AssignmentManager assignmentManager) {
    this.assignmentManager = assignmentManager;
}

/**
 * Custom menuitem for the purpose of storing associated assignments and
 * information related to them.
 */
private class AssignmentMenuItem extends MenuItem {

    private static final long serialVersionUID = 1L;
    private Assignment assignment;

    public AssignmentMenuItem(Assignment assignment) {
        super();
        this.assignment = assignment;
        setValue(assignment.getTitle());
    }

    /**
     * Convenience method
     *
     * @param component
     */
    public void addChild(UIComponent component) {
        getChildren().add(component);
    }
}
}

Please do not mind the code quality, it is for debugging. The problem is this: whenever I enable the accordionPanel tag on the xhtml page, all other beans associated with this page stop working: for example, clicking any of the menuitems on the courseView menu (which DOES work perfectly in the absence of the accordion), does nothing but to reload the page. The same goes for ALL other bean action bindings (including those generated in the header).

What am I missing here? As soon as I remove the accordionPanel tag, as mentioned, it works just fine. I am guessing it has something to do with the request cycle, but I am at a loss as to just what is going wrong.

EDIT:

Logging output for pressing the menuitems (which one does not matter) 2 times:

INFO: se.gu.fire.backend.GradingBean: Created GradingBean for user [email protected]
INFO: se.gu.fire.backend.GradingBean: Constructed assignmentView
INFO: se.gu.fire.backend.GradingBean: Constructed courseMenu
INFO: se.gu.fire.backend.GradingBean: Constructed bean

INFO: se.gu.fire.backend.GradingBean: Created GradingBean for user [email protected]
INFO: se.gu.fire.backend.GradingBean: Constructed assignmentView
INFO: se.gu.fire.backend.GradingBean: Constructed courseMenu
INFO: se.gu.fire.backend.GradingBean: Constructed bean

Notice how the cycle seems to get reset, and the bean reloaded whenever this happens...this happens for all other bindings to other beans on this page as well.

Upvotes: 1

Views: 585

Answers (1)

BalusC
BalusC

Reputation: 1109570

The code posted so far does not indicate that (using @Named @ViewScoped makes no sense), but the symptoms are recognizeable in case of using the binding attribute on a property of a fullworthy JSF view scoped managed bean (with @ManagedBean @ViewScoped).

The binding attribute is (like as id attribute and all taghandlers) evaluated during the view build time. The view is by default (with partial state saving enabled) built on every HTTP request. When the view is built, then the view scope is ready for use. View scoped managed beans are stored in there.

However, the view scope is by default not available during view build time. This means that any EL expression referencing a view scoped bean which is evaluated during building the view will create a brand new and completely separate instance of the view scoped bean, with all properties set to default. This is then reused instead.

This chicken-egg issue can be solved by using the binding attribute exclusively on request scoped beans, or to turn off partial state saving by setting the web.xml context parameter javax.faces.PARTIAL_STATE_SAVING to false. This has been reported as JSF issue 1492 and is fixed in the upcoming JSF 2.2.

See also:

Upvotes: 5

Related Questions