Creating master-detail pages for entities, how to link them and which bean scope to choose
Asked Answered
M

2

31

I have started learning JSF, but sadly most tutorials out there present only a log in or a register section.

Can you point me to some more in depth examples? One thing I'm interested in is a page presenting a list of products. I'm on page home and I press on page products so that I can see the latest products added. And every time I visit the page, the product list will be created from the latest entries in the database. How can I handle this?

One way to solve this would be to create a session scoped managed bean in which I would place different entities updated through other managed beans. I found this kind of approach in some tutorials, but it seems quite difficult and clumsy.

Which would be the best approach to solve a thing like this? What is the correct usage of session scope in two-page master-detail user interface?

Misdoubt answered 10/12, 2011 at 21:10 Comment(4)
OK thanks. In this case where should I store the list of products?. Now I have a Controller Bean named Products Controller (with all the actions which are called for the Product Entity) and A Product Model (A Java Bean with the same attributes as the according database table). Where should I store the productList attribute so that I can access it form the product pageMisdoubt
Well in need the products list on the product page only. And every time I visit the page, the product list will be created from the latest entries in the database. How can I handle this?Misdoubt
I wish there were more questions and answers like this for correct backing bean usage. Most examples you see out there are just plain wrong.Blabbermouth
The question title doesn't represent your content. The master-detail concept is totally different from a simple product list page.Nefen
H
58

What is the correct usage of session scope

Use it for session scoped data only, nothing else. For example, the logged-in user, its settings, the chosen language, etcetera.

See also:


And every time I visit the page, the product list will be created from the latest entries in the database. How can I handle this?

Typically you use the request or view scope for it. Loading of the list should happen in a @PostConstruct method. If the page doesn't contain any <h:form>, then the request scope is fine. A view scoped bean would behave like a request scoped when there's no <h:form> anyway.

All "view product" and "edit product" links/buttons which just retrieve information (i.e. idempotent) whould be just plain GET <h:link> / <h:button> wherein you pass the entity identifier as a request parameter by <f:param>.

All "delete product" and "save product" links/buttons which will manipulate information (i.e. non-idempotent) should perform POST by <h:commandLink>/<h:commandButton> (you don't want them to be bookmarkable/searchbot-indexable!). This in turn requires a <h:form>. In order to preserve the data for validations and ajax requests (so that you don't need to reload/preinitialize the entity on every request), the bean should preferably be view scoped.

Note that you should basically have a separate bean for each view and also note that those beans doesn't necessarily need to reference each other.

So, given this "product" entity:

@Entity
public class Product {

    @Id
    private Long id;
    private String name;
    private String description;

    // ...
}

And this "product service" EJB:

@Stateless
public class ProductService {

    @PersistenceContext
    private EntityManager em;

    public Product find(Long id) {
        return em.find(Product.class, id);
    }

    public List<Product> list() {
        return em.createQuery("SELECT p FROM Product p", Product.class).getResultList();
    }

    public void create(Product product) {
        em.persist(product);
    }

    public void update(Product product) {
        em.merge(product);
    }

    public void delete(Product product) {
        em.remove(em.contains(product) ? product : em.merge(product));
    }

    // ...
}

You can have this "view products" on /products.xhtml:

<h:dataTable value="#{viewProducts.products}" var="product">
    <h:column>#{product.id}</h:column>
    <h:column>#{product.name}</h:column>
    <h:column>#{product.description}</h:column>
    <h:column>
        <h:link value="Edit" outcome="/products/edit">
            <f:param name="id" value="#{product.id}" />
        </h:link>
    </h:column>
</h:dataTable>
@Named
@RequestScoped
public class ViewProducts {

    private List<Product> products; // +getter

    @EJB
    private ProductService productService;

    @PostConstruct
    public void init() {
        products = productService.list();
    }

    // ...
}

And you can have this "edit product" on /products/edit.xhtml:

<f:metadata>
    <f:viewParam name="id" value="#{editProduct.product}" 
        converter="#{productConverter}" converterMessage="Unknown product, please use a link from within the system."
        required="true" requiredMessage="Bad request, please use a link from within the system."
    />
</f:metadata>

<h:messages />

<h:form rendered="#{not empty editProduct.product}>
    <h:inputText value="#{editProduct.product.name}" />
    <h:inputTextarea value="#{editProduct.product.description}" />
    ...
    <h:commandButton value="save" action="#{editProduct.save}" />
</h:form>
@Named
@ViewScoped
public class EditProduct {

    private Product product; // +getter +setter

    @EJB
    private ProductService productService;

    public String save() {
        productService.update(product);
        return "/products?faces-redirect=true";
    }

    // ...
}

And this converter for <f:viewParam> of "edit product":

@Named
@RequestScoped
public class ProductConverter implements Converter {

    @EJB
    private ProductService productService;

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        if (value == null || value.isEmpty()) {
            return null;
        }

        try {
            Long id = Long.valueOf(value);
            return productService.find(id);
        } catch (NumberFormatException e) {
            throw new ConverterException("The value is not a valid Product ID: " + value, e);
        }
    }

    @Override    
    public String getAsString(FacesContext context, UIComponent component, Object value) {        
        if (value == null) {
            return "";
        }

        if (value instanceof Product) {
            Long id = ((Product) value).getId();
            return (id != null) ? String.valueOf(id) : null;
        } else {
            throw new ConverterException("The value is not a valid Product instance: " + value);
        }
    }

}

You can even use a generic converter, this is explained in Implement converters for entities with Java Generics.

See also:

Hilaryhilbert answered 11/12, 2011 at 14:19 Comment(5)
+1 really good pattern for doing master/detail editing in JSF. Just this should be put into a blue-print somewhere.Octo
Thank you! Your blog post gave me all the answers I needed.Misdoubt
@Hilaryhilbert how would you incorporate your suggestion to use the same page for edits and views in this post stackoverflow.com/questions/8768117/… when that post has a backing bean method that sets the edit boolean but here you are suggesting a simple GET link? I like the above, it is simple but I also would like to say 'hey you are not authorized to edit' and redirec/fwd to read only if someone guesses the URL 'edit.xhtml?product_id=###' I know this post is old, maybe you have some updated way to do things nowDisney
thank you so much for this great example, you saved my day :)Lockyer
ProductService.save() method, referenced in EditProduct.save(), seems to be missing from ProductService EJB class.Indecent
A
0

As a small improvement to what BalusC recommended, sometimes you can remove the required / requiredMessage part from the <f:viewParam> of your "details" screen and instead use the conditional rendering of the editing form (as BalusC did) with a reverse condition for recommending a specific link for the "list/master" screen or, even use a viewAction that would test the param and force a redirect to that list.

Aricaarick answered 30/6, 2016 at 17:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.