Search code examples
jsf-2crudmanaged-bean

JSF 2 basic @RequestScoped CRUD


i have these simple pages:

list.xhtml

<h:form id="form">  
    <h:dataTable value="#{testBean.model}" var="elem">
        <h:column>
            <f:facet name="header">code</f:facet>
            #{elem.code}
        </h:column>
        <h:column>
            <f:facet name="header">description</f:facet>
            #{elem.description}
        </h:column>
        <h:column>
            <f:facet name="header">action</f:facet>
            <h:commandButton action="#{testBean.edit(elem)}" value="edit"/>
        </h:column>
    </h:dataTable>
</h:form>

edit.xhtml

<h:form id="form">
    <h:panelGrid columns="2">
        <h:outputLabel value="code"/>
        <h:inputText value="#{testBean.selection.code}"/>

        <h:outputLabel value="description"/>
        <h:inputText value="#{testBean.selection.description}"/>
    </h:panelGrid>

    <h:commandButton action="#{testBean.update}" value="update"/>
</h:form>

and this bean:

@ManagedBean
public class TestBean implements Serializable
{
    private static final long serialVersionUID = 1L;

    @EJB
    private PersistenceService service;
    private Object selection;
    private List<UnitType> model;

    @PostConstruct
    public void init()
    {
        model = service.findAll(UnitType.class);
    }

    public String edit(Object object)
    {
        System.out.println(Tracer.current(object));
        setSelection(object);
        return "edit";
    }

    public String update()
    {
        System.out.println(Tracer.current(selection));
        return "list";
    }

    // getters and setters
}

so the table is rendered, when i click one of the "edit" buttons it navigates to "edit.jsf" showing filled input, but when i click the "update" buttons it gives me this error:

javax.el.PropertyNotFoundException: /test2/edit.xhtml @27,54 value="#{testBean.selection.code}": Target Unreachable, 'null' returned null

note that i know how to implement a @ViewScoped interface to manage CRUD operations, but this is a simple proof of concept that i need to better understand JSF lifecycle.

so i want "testBean" to be @RequestScoped


UPDATE trying with f:viewParam, still not understanding...

list.xhtml

<?xml version="1.0" encoding="ISO-8859-1"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core">
    <h:head>
        <title>test list</title>
    </h:head>

    <h:body>
        <h:messages/>

        <h:form id="form">
            <h:dataTable value="#{testBean2.model}" rows="10" var="elem">
                <h:column>
                    <f:facet name="header">converterString</f:facet>
                    #{elem.converterString}
                </h:column>
                <h:column>
                    <f:facet name="header">first name</f:facet>
                    #{elem.firstName}
                </h:column>
                <h:column>
                    <f:facet name="header">last name</f:facet>
                    #{elem.lastName}
                </h:column>
                <h:column>
                    <f:facet name="header">action</f:facet>
                    <h:commandButton action="#{testBean2.edit}" value="edit">
                        <f:param name="entity" value="#{elem.converterString}"/>
                    </h:commandButton>
                    <h:commandButton action="#{testBean2.edit2}" value="edit2">
                        <f:param name="entity" value="#{elem.converterString}"/>
                    </h:commandButton>
                </h:column>
            </h:dataTable>
        </h:form>
    </h:body>
</html>

edit.xhtml

<?xml version="1.0" encoding="ISO-8859-1"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core">
    <f:metadata>
        <f:viewParam id="entityParam" name="entity" value="#{testBean2.selection}" converter="entityConverter" required="true"/>
    </f:metadata>

    <h:head>
        <title>test edit</title>
    </h:head>

    <h:body>
        <h:messages/>

        <h:form id="form">
            <h:panelGrid columns="2">
                <h:outputLabel value="selection"/>
                <h:outputText value="#{testBean2.selection.converterString}"/>

                <h:outputLabel value="firstName"/>
                <h:inputText value="#{testBean2.selection.firstName}"/>

                <h:outputLabel value="lastName"/>
                <h:inputText value="#{testBean2.selection.lastName}"/>
            </h:panelGrid>

            <h:commandButton action="#{testBean2.update}" value="update" ajax="false">
                <f:param name="entity" value="#{testBean2.selection.converterString}"/>
            </h:commandButton>
        </h:form>
    </h:body>
</html>

testBean2.java

@ManagedBean
public class TestBean2 implements Serializable
{
    private static final long serialVersionUID = 1L;

    @EJB
    private PersistenceService service;

    private Object selection;
    private List<Person> model;

    @PostConstruct
    public void init()
    {
        Tracer.out();
        model = service.queryAll(Person.class);
    }

    public String edit()
    {
        JsfUtils.addSuccessMessage("edited");
        return "edit";
    }

    public String edit2()
    {
        JsfUtils.addSuccessMessage("edited");
        return "edit?faces-redirect=true&amp;includeViewParams=true";
    }

    public void update()
    {
        Tracer.out(selection);
        JsfUtils.addSuccessMessage("updated");
    }

    // getters and setters
}

if i press "edit" button it goes to edit page, but selection is null and no message is showed.

if i press "edit2" button it goes to edit page, but selection is null, showing required message and the url is edit.jsf?entity=

what am i doing wrong?


Solution

  • Finally I found a way: data will be in a long scope, bean in short.

    Note that this is just a proof of concept, not a real use case. And be aware, when dealing with @RequestScope beans, of PrimeFaces lazy DataTable model scope

    @ManagedBean
    public class TestBean
    {
        @EJB
        private PersistenceService service;
    
        @ManagedProperty("#{viewScope.item}")
        private Item item;
    
        @ManagedProperty("#{sessionScope.model}")
        private EntityDataModel<Item> model;
    
        @PostConstruct
        public void init()
        {
            if(model == null)
            {
                model = new EntityDataModel<Item>(Item.class);
                Faces.setSessionAttribute("model", model);
            }
        }
    
        public String update()
        {
            Faces.getFlash().setKeepMessages(true);
    
            try
            {
                item = service.update(item);
                Faces.setViewAttribute("item", item);
    
                JsfUtils.addSuccessMessage("updated");
    
                return "view?faces-redirect=true&includeViewParams=true";
            }
            catch(Exception e)
            {
                e.printStackTrace();
                JsfUtils.addErrorMessage(e);
            }
    
            return null;
        }
    
        // getters and setters, other actions, ...
    }
    

    list.xhtml

    <p:dataTable value="#{testBean.model}" var="elem" ...>
        ...
    
        <p:column exportable="false" toggleable="false" headerText="#{bundle.actions}">
    
            <!-- two techniques available for navigation -->
    
            <p:button outcome="view?id=#{elem.converterString}" icon="#{icons.view}" />
    
            <p:commandButton rendered="#{user.isEditAllowed(elem)}"
                action="edit?faces-redirect=true&amp;includeViewParams=true" process="@form"
                icon="#{icons.edit}">
                <f:param name="id" value="#{elem.converterString}" />
            </p:commandButton>
        </p:column>
    </p:dataTable>
    

    view.xhtml

    <f:metadata>
        <o:viewParam id="itemId" name="id" value="#{viewScope.item}" required="true"
            converter="entityConverter" />
    </f:metadata>
    
    <ui:composition template="/WEB-INF/templates/template.xhtml">
        <ui:define name="content">
            <p:panelGrid columns="2">
                <h:outputLabel value="#{bundle.name}" />
                <h:outputText value="#{item.name}" />
    
                ...
            </p:panelGrid>
    
            <p:button outcome="list" value="#{bundle.list}" icon="#{icons.list}" />
    
            <p:button rendered="#{user.isEditAllowed(item)}" 
                outcome="edit?id=#{item.converterString}" value="#{bundle.edit}"
                icon="#{icons.edit}" />
        </ui:define>
    </ui:composition>
    

    edit.xhtml

    <f:metadata>
        <o:viewParam id="itemId" name="id" value="#{viewScope.item}" required="true"
            converter="entityConverter" />
    </f:metadata>
    
    <ui:composition template="/WEB-INF/templates/template.xhtml">
        <ui:define name="content">
            <p:panelGrid columns="2">
                <h:outputLabel value="#{bundle.name}" />
                <h:panelGroup>
                    <p:inputText id="name" value="#{item.name}" />
                    <p:message for="name" />
                </h:panelGroup>
    
                ...
            </p:panelGrid>
    
            <p:commandButton process="@form" update="@form" action="#{testBean.update}" 
                value="#{bundle.update}" icon="#{icons.update}" />
    
            <p:button outcome="view?id=#{item.converterString}" value="#{bundle.cancel}" 
                icon="#{icons.cancel}" />
        </ui:define>
    </ui:composition>