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?

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
TGM
  • 1,659
  • 10
  • 30
  • 45
  • 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_ page – TGM Dec 11 '11 at 09:56
  • 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? – TGM Dec 11 '11 at 14:03
  • 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. – anger Jul 23 '13 at 02:16
  • The question title doesn't represent your content. The master-detail concept is totally different from a simple product list page. – Gilberto Mar 13 '16 at 20:24

2 Answers2

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:

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • +1 really good pattern for doing master/detail editing in JSF. Just this should be put into a blue-print somewhere. – Mike Braun Dec 12 '11 at 09:51
  • Thank you! Your blog post gave me all the answers I needed. – TGM Dec 12 '11 at 14:58
  • @BalusC how would you incorporate your suggestion to use the same page for edits and views in this post http://stackoverflow.com/questions/8768117/displaying-read-only-forms-values-are-shown-as-text-instead-of-disabled-input-c 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 now – jeff Feb 27 '14 at 00:23
  • thank you so much for this great example, you saved my day :) – kelvan Jan 06 '17 at 13:52
  • `ProductService.save()` method, referenced in `EditProduct.save()`, seems to be missing from `ProductService` EJB class. – MestreLion Jul 16 '20 at 11:23
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.

bruno
  • 2,213
  • 1
  • 19
  • 31