Tuesday, 15 December 2009

Master-Detail on One Page

With all Seam examples of CRUD for Master-Detail relationships that I've seen so far, we always rely on Modal Panels to edit the Detail entities. (My own example posts are here, here, here, and here) However the Modal Panel approach has it's own limitations, mostly around navigation when the user starts to create a new Detail and then changes their mind (as they do).

In any case, popup modal panels tend to disrupt the flow of the page. What would be good is to be able to edit both Master and Detail entities on a single page. The following example produces a screen that looks like:


So to the code. This uses exactly the same backing bean as the other examples - a conversation-scoped edit manager - with no changes at all. Code for that is available here.

The page.xml file looks like:

<?xml version="1.0" encoding="UTF-8"?>
<page xmlns="http://jboss.com/products/seam/pages"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.1.xsd"
no-conversation-view-id="/MasterList.xhtml"
login-required="true">

<begin-conversation join="true" flush-mode="MANUAL"/>

<action execute="#{masterEditor.wire}" on-postback="false"/>

<param name="masterFrom"/>
<param name="masterId" value="#{masterEditor.masterId}"/>


<navigation from-action="#{masterEditor.persist}">
<rule>
<end-conversation/>
<redirect view-id="/OnePageEdit.xhtml"/>
</rule>
</navigation>

<navigation from-action="#{masterEditor.update}">
<rule>
<end-conversation/>
<redirect view-id="/OnePageEdit.xhtml"/>
</rule>
</navigation>

<navigation from-action="#{masterEditor.remove}">
<rule>
<end-conversation/>
<redirect view-id="/MasterList.xhtml"/>
</rule>
</navigation>

</page>


The XHTML file is:

<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:s="http://jboss.com/products/seam/taglib"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:a="http://richfaces.org/a4j"
xmlns:rich="http://richfaces.org/rich" template="layout/template.xhtml">

<ui:define name="body">

<h:form id="master" styleClass="edit">

<rich:panel>
<f:facet name="header">Master Details</f:facet>

<s:decorate id="masterDescriptionField" template="layout/edit.xhtml">
<ui:define name="label">Master description</ui:define>
<h:inputText id="masterDescription" size="100" maxlength="100"
value="#{masterEditor.instance.masterDescription}">
<a:support event="onblur" reRender="masterDescriptionField"
bypassUpdates="true" ajaxSingle="true" />
</h:inputText>
</s:decorate>

<div style="clear: both">
<span class="required">*</span>
required fields</div>

</rich:panel>

<a:region>

<h:panelGrid id="detailEdit" columns="2" columnClasses="detailList,detailDetail" width="100%">

<h:panelGroup>

<rich:dataTable id="detailListTable" var="_detail" value="#{detailListFromMaster}"
width="200px" styleClass="scriptPagesList"
onRowMouseOver="this.style.backgroundColor='#EAEAEA';this.style.cursor='pointer'"
onRowMouseOut="this.style.backgroundColor='#{a4jSkin.tableBackgroundColor}'">
<h:column>
<f:facet name="header">
<ui:include src="layout/sort.xhtml">
<ui:param name="propertyLabel" value="Id" />
</ui:include>
</f:facet>
<h:outputText value="#{_detail.id}" />
</h:column>
<a:support event="onRowClick"
reRender="detailEditDetail"
action="#{masterEditor.select()}">
</a:support>
</rich:dataTable>

<div style="clear: both" />

<div class="actionButtons">
<a:commandButton id="NewDetailButton"
value="New Detail"
action="#{masterEditor.createNewDetail}"
reRender="detailEdit"
styleClass="buttonNoBorders">
<s:conversationId />
</a:commandButton>
<a:commandButton id="DeleteDetailButton"
value="Delete Detail"
action="#{masterEditor.removeDetail(currentDetail)}"
enabled="#{currentlySelectedDetail!=null}"
reRender="detailEdit"
styleClass="buttonNoBorders">
<s:conversationId />
</a:commandButton>
</div>

</h:panelGroup>

<h:panelGroup>
<rich:panel id="detailEditDetail" styleClass="panelNoBorder">
<f:facet name="header">Detail Details</f:facet>

<s:div rendered="#{currentlySelectedDetail!=null}">

<s:decorate id="detailDescriptionField" template="layout/edit.xhtml">
<ui:define name="label">Detail description</ui:define>
<h:inputText id="detailDescription"
required="true"
size="100"
maxlength="100"
value="#{currentlySelectedDetail.detailDescription}">
<a:support event="onblur" reRender="detailDescriptionField" bypassUpdates="true" ajaxSingle="true" />
</h:inputText>
</s:decorate>

</s:div>
</rich:panel>
</h:panelGroup>

</h:panelGrid>

</a:region>

<rich:panel styleClass="panelTopBorderOnly"
bodyClass="panelTopBorderOnlyBodyClass">
<div class="actionButtons">
<h:commandButton id="save"
value="Save" action="#{masterEditor.persist}"
disabled="#{!masterEditor.wired}"
rendered="#{!masterEditor.managed}" />

<h:commandButton id="update"
value="Save" action="#{masterEditor.update}"
rendered="#{masterEditor.managed}" />

<h:commandButton id="delete"
value="Delete"
action="#{masterEditor.remove}"
immediate="true"
rendered="#{masterEditor.managed}" />

<s:button id="cancelEdit"
value="Cancel"
propagation="end"
view="/MasterList.xhtml" />
</div>

<div style="clear: both">
<span class="required">*</span>
required fields
</div>

</rich:panel>

</h:form>

</ui:define>

</ui:composition>



You'll note a number of CSS classes which modify the richfaces appearance:

.panelTopBorderOnly {
margin-top:10px;
border-left:none;
border-right:none;
border-bottom:none;
}
.panelTopBorderOnlyBodyClass {
padding-top:0px;
}

.detailList {
vertical-align:top;
width:200px;
height:200px;
overflow:scroll;
}

.detailDetail {
vertical-align:top;
}

input.buttonNoBorders {
margin:1px 1px 1px 0;
padding:0px;
}


Two key points:
  • The ajax region was required to make the insert and delete of the Detail entities work when dealing with a freshly created Master entity.
  • Note the buttons - these are done differently to the seam-gen buttons, because other the creation of a new Detail lost changes to the Master entity - if any.
Really interested to see what people think.

No comments:

Post a Comment