4

I am interested to know what are some good patterns for implementing single-page master-detail views in a JSF-PrimeFaces / JPA stack. It seems that most of the material and tutorials on the web are only discussing the rather trivial one table per view pattern.

But I am interested in things such as having CUSTOMER and ORDER tables in the database and one xhtml page where you can view the customers (e.g. as a p:datatable) on the top half and the orders for the currently selected customer on the lower half (again as a p:datatable). It is not clear to me how to best organize JSF/PrimeFaces/backing beans and facades / entities / JPA code to achieve the above in a generally applicable way with maximum code reuse. E.g.

  1. should I define one backing bean for the whole xhtml view or two backing beans, one for each component of the view (master / detail)?
  2. can the pattern be generalized to more than one detail tables (at the same level)?. E.g. have a view of the CUSTOMER table at the top half and a tabbed view at the lower half, consisting of two views for the ORDER and PAYMENT detail tables respectively (both in a N-1 relationship with table CUSTOMER
  3. can the pattern be generalized to more than one level of detail (e.g. have on the same page a view of the CUSTOMER, the INVOICE, and the INVOICELINE tables.
  4. How easily the proposed pattern can accommodate modifications as well. E.g. using editable datatables one could change the customer details and delete an order and finalize both changes, in one go, using a commit changes button.
Marcus Junius Brutus
  • 26,087
  • 41
  • 189
  • 331
  • I want to know if you have found a way to acheive this. – faissal Nov 16 '12 at 23:47
  • I am currently using one xhtml page with two separate backing beans: one for the master and one for the detail. The issue is thornier than I thought as JPA also comes into play (how to deal with lazy initialization exceptions, which JPA collections to define as lazy etc). You also have two separate concepts of laziness: JPA lazy collections and PrimeFaces lazy data tables. I also want to allow users to update the master row and create / delete / update rows on the detail table and commit everything in one transaction. I haven't yet come up with easily generalizable patterns that always work. – Marcus Junius Brutus Nov 17 '12 at 13:26
  • @MarcusJuniusBrutus Have you found a way of doing this? I am too looking for something quite similar to this? Did you find any tutorials about this? Thanks – Jacob Dec 25 '12 at 15:00
  • I don't think the bounty text has anything to do with the original question. And a 'new' question similar to the bounty text is created: https://stackoverflow.com/questions/33027890/multiple-cdi-viewscoped-in-same-page-reuse-of-details-view-and-passing-paramete So I think this bounty should be retracted (although the answer below is right for the original question and as can be seen, the 'bounty text' is added as a comment to the answer. – Kukeltje Oct 09 '15 at 10:27

1 Answers1

4

For a recent training course we build the following proofOfConcept. We created a single view with two tables, a master (departements) and a detail (employees) table. The view was backed by two controller beans which communicate using cdi events. The proofOfConcept was deployed to a jee6-container (glassfish 3.1.1).

The idea is whenever you click on a departements row, the ajax-Listener fires an cdi-Event that updates the detail-Controller and in return updates the detail table. This pattern can be extended to multiple detail tables or multiple master-detail-detail levels.

For editing your entites I suggest to open an editor-dialog, eg by adding an editor-button in each row of your tables. To add new details use add-Actions in the footer of the tables and open the editor-dialog with a new entity. On "ok" in your editor-Dialog you again fire an cdi-event with the new entity to update depending detail tables. To save your work use a single "commit"-Button, which saves the master-Entity. In the training we used jpa with proper defined entities, esp using the orphanRemoval=true attribute in the @OneToMany relations.

The view (scott.xhtml):

<p:panel id="deptPanel" header="Departements">
    <p:dataTable id="deptTable" var="dept" value="#{deptUiController.departements}"
                 selectionMode="single" rowKey="#{dept.id}">
        <p:ajax event="rowSelect" listener="#{deptUiController.onRowSelect}" update="@form"/>
        <p:column headerText="Name">
            <h:outputText id="name" value="#{dept.dname}"/>
        </p:column>
        <p:column headerText="Location">
            <h:outputText id="loc" value="#{dept.loc}"/>
        </p:column>
        <p:column headerText="# of Emps">
            <h:outputText id="size" value="#{dept.emps.size()}"/>
        </p:column>
    </p:dataTable>
</p:panel>
<p:panel id="empPanel" header="Employees in departement #{deptUiController.currentDept.dname}">
    <p:dataTable id="empTable" var="emp" value="#{empUiController.employees}">
        <p:column headerText="Name">
            <h:outputText id="name" value="#{emp.ename}"/>
        </p:column>
        <p:column headerText="Job">
            <h:outputText id="job" value="#{emp.job}"/>
        </p:column>
        <p:column headerText="Hiredate">
            <h:outputFormat id="hiredate" value="{0,date,dd.MM.yyyy}">
                <f:param value="#{emp.hiredate}"/>
            </h:outputFormat>
        </p:column>
    </p:dataTable>
</p:panel>

Master-Controller:

@Named
@SessionScoped
public class DeptUiController implements Serializable {

    private boolean initialized = false;

    @EJB
    private ScottCRUD crudEJB;

    private List<Departement> departements;

    private Departement currentDept;

    public void populateData() {
        if ( !initialized ) {
            departements = crudEJB.findAllDepartements();
            currentDept =  departements.isEmpty() ? null : departements.get(0);
            initialized = true;
            fireDeptChange();
        }
    }

    @Inject
    private Event<Departement> deptChangeEvt;

    private void fireDeptChange() {
        deptChangeEvt.fire( currentDept );
    }

    public void onRowSelect(SelectEvent event) {
        currentDept = (Departement) event.getObject();
        fireDeptChange();
    }

    ... getter, setter, more actions...

}

Detail-Controller

@Named
@SessionScoped
public class EmpUiController implements Serializable {

    private List<Employee> employees;

    private Employee currentEmp;

    private void populateData(Departement master) {
        if ( master==null ) {
            employees = Collections.emptyList();
        } else {
            employees = master.getEmps();
        }
        currentEmp =  employees.isEmpty() ? null : employees.get(0);
    }

    public void observeDeptChanged( @Observes Departement master ) {
        populateData( master );
    }

    ... getter, setter, more actions...

}
frifle
  • 810
  • 5
  • 24