Pattern: Managing a List of Items via a Dialog

…using a Java EE container with JSF and Primefaces

Challenge Overview

Imagine we’ve got a list of things from persistence provider – that is, a database, or text file, or the some kind of RESTful webservice. We don’t care. What we do care about is presenting this list to our users in the GUI – a web app. That GUI must allow our users to both view the list and edit it in the same view. So the use cases are:

  • View a list of things
  • Open each thing to see further details
  • When in the context of having something open, edit it and save it
  • Add new items using a dialog

TL/DR

The code’s all on Github, and here’s a video of the thingamagig in action:

A Bit of Context

  • We will use JSF – The Java EE web front-end API, and Primefaces – A toolbox of nice GUI widgets for JSF.
  • It is assumed that the container is a Java EE 7-supporting environment such as Glassfish or Wildfly.
  • Areas out of scope include testing, actually implementing the persistence layer, and validation. Not that they aren’t important, just, ya know, one thing at a time.

Approach

This solution is really just a coherent set of little individual tricks which I find work again and again. Here they are:

Choosing a Scope

There is an increasingly broad range of ‘scopes’ on can choose depending on context in JSF. In our case, whilst the user is on the page where they see the list and manage it, there are certain variables we want to store – the currently selected item, the state of the page (e.g. Add, Edit) – for the duration of their visit to the page. After they leave the page, we can disregard data that was not explicitly saved. Further, each user should have their own distinct store of this information. So we want one store of data per user session per page visit.

This is what a View Scoped Bean was designed for. In an @ViewScoped-annotated bean, invocation takes place once a user navigates to a view within which that Bean’s name is referenced – e.g. through Expression Language (EL) -, and is discarded once they navigate away from that view. In the intervening time, the state of the bean is maintained.

Dealing with Page State

Pages in this environment usually have three states:

  • View State: You’re viewing the list and clicking on items to see their details – nothing is in editable text boxes and everything is presented as ‘output’ text.
  • Edit State: You’ve opened an existing item and have opted to edit it – items which are allowed to be edited are now rendered in ‘input’ mode- i.e. text fields are editable, discrete choices are presented among arrays of radio buttons or sliders or tick boxes etc.
  • Add State: You’ve opted to add a new item and everything is in the same display mode as edit state, except instead of updating an existing item when you click save, you are creating new one.

What’s that? Something with a discrete set of known possible values? Sounds like an enumeration to me. I call this enum PageState, and place an instance of it in the backing bean. Initially declared with the value ‘VIEW’, to correspond with navigating to the page originally, it is then used by Expression Language within the Facelet to ‘pivot’ the view depending on the state of the page. For example the ‘Edit’ button shouldn’t be enabled after the user clicks it. Instead set the page state variable to ‘EDIT’ after the user clicks the button, and update the buttons involved, all of which have their enabled/disabled state coupled to the enumeration.

The enum’s very simple:

public enum PageState
{
VIEW,
ADD,
EDIT;
}

.. and we might define a parameter in the UI to capture what ‘editable’ means:

<ui:param name="editable" value="#{indexUI.pageState == 'ADD' || indexUI.pageState == 'EDIT'}"/>

.. and we can use it to control presentation. So in the XHTML we might see this to pivot a button state:

public void clickEdit()
{
pageState = PageState.EDIT;
}

public void clickCancel()
{
pageState = PageState.VIEW;
}

Giving the User Feedback

When data is being saved, display a ‘pending’ notice to the user which renders the components un-editable until complete. This helps because:

  • It tells them that their request is being dealt with
  • It prevents further updates to the page that would be out of order until the update request is being submitted.

In this example we’re using the Primefaces ajaxStatus component. This lets us hook a javascript call to the beginning and end of the ajax request. We use these hooks to show & hide a dialog to give feedback to the user.

To tie the starting & stopping of the ajax event to a javascript call, simply insert the ajaxStatus component, erm, ‘thusly’:

<p:ajaxStatus onstart="PF('pendingDialog').show();" oncomplete="PF('pendingDialog').hide();"/>

In our case we’re using a simple Primefaces dialog positioned at the top of our page:

<p:dialog showHeader="false" modal="false" widgetVar="pendingDialog" position="top">
<h:outputText value="Working..."/>
</p:dialog>

… manifesting itself, naturally enough, at the top of the page when the page is fulfilling a request:

dialog.PNG

Re-Using the Same GUI Components

Only model one dialog for the view, add & edit scenarios. Have this dialog pivot its view depending on the page state. Point the dialog towards the selectedItem variable. This has the combined benefits of:

  • Requiring less XHTML
  • Having less items in the DOM and thus speeding up page load times as there is only one dialog.
  • Making it easier to maintain the same look in the three different use cases (add, view, edit), as it’s all the same, single component.

In our example, this means that there are the components for both the view and edit -state versions of the dialog. In view state, the InputText components are not rendered, and in edit state, the OutputText components aren’t, e.g:

<h:panelGrid columns="2" id="fieldGrid">
<h:outputLabel value="First Name" style="margin-right: 10px;"/>
<h:inputText size="23" value="#{indexUI.selectedInvite.firstName}" rendered="#{editable}"/>
<h:outputText size="23" value="#{indexUI.selectedInvite.firstName}" rendered="#{!editable}"/>

<h:outputLabel value="Surname"/>
<h:inputText size="23" value="#{indexUI.selectedInvite.surname}" rendered="#{editable}"/>
<h:outputText size="23" value="#{indexUI.selectedInvite.surname}" rendered="#{!editable}"/>

<h:outputLabel value="Email"/>
<h:inputText size="23" value="#{indexUI.selectedInvite.emailAddress}" rendered="#{editable}"/>
<h:outputText size="23" value="#{indexUI.selectedInvite.emailAddress}" rendered="#{!editable}"/>

<h:outputLabel value="Invite Sent"/>
<p:selectBooleanCheckbox value="#{indexUI.selectedInvite.inviteSent}" disabled="#{!editable}"/>
</h:panelGrid>

… note the content of the rendered=”” statements here.

This creates a nice sense of consistency between the view state:

view state.PNG

And the edit state:

edit state.PNG

Tracking the Selected and Edited Item

Store the selected item – the item chosen from the list, or the new item to be added – in its own single variable and have the dialog point to that. This goes through the following assignations:

  1. When an item is selected from the list, assign the value of this this to the selectedItem variable, and show and update the dialog.
  2. When the ‘Add’ button is clicked, create a new item object, and assign that to the selectedItem variable, and show and update the dialog.
  3. When ‘Save’ is clicked, use the selectedItem variable when handing it over to your persistence layer.

This approach has the distinct advantage of reducing the number of variables.

So here we are are assigning the value of the selected item variable upon, erm, selection of a row:


<p:commandLink value="#{invite.firstName} #{invite.surname}" action="#{indexUI.selectInvite(invite)}" oncomplete="PF('inviteDialog').show();"/>

public void selectInvite(InviteModel selectedInvite)
{
this.selectedInvite = selectedInvite;
}

… and when the add button is clicked, it’s just as simple:

<p:commandButton style="margin-top: 20px;" value="Add Invite" action="#{indexUI.clickAdd()}" oncomplete="PF('inviteDialog').show();" update="inviteDialog"/>
public void clickAdd()
{
selectedInvite = new InviteModel();
pageState = PageState.ADD;
}

Using CDI

For reasons that I am not qualified to go into, there were two classes of beans in Java EE: Managed Beans for JSF, and EJBs for everything else. EJBs can be injected into Managed Beans, however the converse is not true. There’s more to it than that, but the TL/DR is that Oracle has resolved this dichotomy with the introduction of CDI (Context Dependency Injection) beans, which can be injected into one another, and which they recommend using rather than @EJB or @ManagedBean. Clear? Yeah I know. Let’s just go with it.

The only prerequisite for using CDI is to have beans.xml located alongside your standard web.xml and faces config. This can be a bare-bones file like this:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd" version="1.1" bean-discovery-mode="all">
</beans>

path.PNG

.. the presence of this ‘activates’ CDI for your application, letting you use those annotations.

Thinking Ahead: Isolating Persistence Model Changes

There is a lot we could do to improve and expand upon this basic Separate the persistence layer model – e.g. a JPA entity or Restful Data Transfer Object (DTO) of some kind – from the view model. Translate to/from these two models using a Translator. This has at least a couple of advantages:

  • Decoupling means changes to the persistence layer model will only break the Translator and not the entire page. Isolate what changes!
  • Unnecessary communication is minimised between those team members working on the view and those working on the persistence. For example, if I want a new transient (i.e. non-saved) variable on the view model in order to assist with a GUI concern (e.g. ‘getPrettyEnumName()’), I only need to make that change to the view model and not ask the person taking care of the database to ‘do a pull’ in Git or what have you.

The Source

All the code for this application is on Github.

Advertisements