JSF Job Flow – a new development model for JSF Single Page Applications with dynamic tabs

JSF Job Flow – a new development model for JSF Single Page Applications with dynamic tabs

-Professional diary of one team leader-

I believe you have never heard of JSF Job Flow. And that’s not surprising, because I just gave it a name ;-) So what is it about? Some time ago, I developed UI shell for developing JSF applications, based on dynamic tabs

This is a powerful mechanism, which enables application user to work with multiple application module at the same time, all that within one, and the only one, Single JSF Page. There is also a small demo app, which you could see in action. However, since we are within one page, it is necessary to establish a certain way of development, which is suitable for such a scenario. That's what Job Flow is for, and that's the subject of this article.

Inspiration and Technology stack used

Ideally, what would be appropriate for this type of application is  one already existing JSF artifact, a Faces Flow The only problem here is, you guess, the fact that JSF Faces Flow utilizes a full facelet pages. All we have, however, is a single page, and unfortunately, (at least at time this writting) there is no page fragments based flows.  Actually, what we need, already exists, it's about Oracle’s ADF fragment based Task Flow but this is proprietary technology stack, which we cannot use in a pure JSF application. So, the only solution, is to make something similar. That's how the idea came about JSF Job Flow. In essence, JSF Job Flow is  JSF Faces Flow, which uses page fragments instead of full pages, and which is customized for execution in context of one dynamic tab in the JSF Single Page Application:

No alt text provided for this image

So, it is a way of developing applications, which provides similar possibilities as JSF Faces Flow. Job Flow is not tied  to a certain browser tab or window (to be honest, I never liked that concept), rather it is tied to one dynamic tab. There is no dedicated Flow Scope, it is used standard ViewScope. Like with standard JSF Flow, there is Initializer and Finalizer (in Job Flow terms – Access Point Method and Exit Point Method), View nodes, Method call nodes, a return node, flow call node…well, at least, you can easily have the same functionalities. Job Flow is reusable components, can send parameters to them. As a serious tool, it must also offer a solution for authorization (think about this – we are in the SPA scenario – how to deny or allow a user to access one Job Flow (ie, application module)? There is also communication mechanism between Job Flows, event handlers for dynamic tab events (dyn tab added, removed, selected). There is no, however, nothing like a  @FlowDefinition annotation, nor <flow-definition> element in the faces-config.xml (this would require a change in the JSF specification). Instead, everything is achieved with the help of already existing JSF artefacts. But still powerful enough to support the development of robust SPA applications. To develop this concept, I used just JSF (with PrimeFaces component library) and CDI

Download and User Guide

If you want to see how it works, or want to develop such applications, you can download everything you need FROM HERE. What you got, is a simple Eclipse project, which is an example of one such application. There is also ready-to-deploy jsf_dyntab_demo.war, you can deploy him to GlassFish/Payara,  Tomcat, .. where you normally place your JSF applications. The browser URL for deployed application is localhost:8080/jsf_dyntab_demo/dyntab_master_layout.xhtml (of course, change the port if need)

Log into application as: admin@mail.com /ADMIN123 or user@mail.com/USER123

I did not (yet) single out 9 Java classes (from dyntabs and dyntab.interfaces packages) in a separate .jar archive. These are the infrastructure classes that govern the entire mechanism so, it is a good idea to transfer them to a separate archive, and use that archive in your applications. As usual, just put him to the /WEB-INF/lib folder.

faces-config.xml settings , page template, launching application module, closing work spaces

It is the same as described in previous article, so I will not repeat here. The same goes for the page template. There is, however, yet another facelet template, used to be base for every UI fragment (or View node). This is \WEB-INF\templates\dynTabLayout.xhtml All you need, is to define mainContent. For one example, see employees.xhtml from provided demo app.

The Job Flow definition

You have already met the simplest Job Flow definition. The Job Flow definition consists al least of one “view activity”. View activity is just another name for facelet UI fragment, displaying in one dynamic tab. You define a Job Flow in the faces-config.xml, like a following managed bean:

<managed-bean>
      <managed-bean-name>EmployeesDynTab</managed-bean-name>
      <managed-bean-class>dyntabs.DynTab</managed-bean-class>
      <managed-bean-scope>request</managed-bean-scope>
      <managed-property>
            <property-name>uniqueIdentifier</property-name>
            <value>Employees</value>
        </managed-property>
      <managed-property>
          <property-name>title</property-name>
          <value>The Employees</value>
      </managed-property>
      <managed-property>
          <property-name>includePage</property-name>
          <value>/WEB-INF/include/employees/employees.xhtml</value>
      </managed-property>
      <managed-property>
           <property-name>closeable</property-name>
           <value>true</value>
      </managed-property>
</managed-bean>


Pay attention to ‘includePage’ managed property. This is actually a  initial Job Flow view activity, the first thing the user sees in the dynamic tab.  However, that does not mean that this is the first activity in the Job Flow. If necessary, you can have a initializer, or, in therms of Job Flow – Access Point Method

Access Point Method and Exit Point Method, user authorization solution

Those are  counterparts of standard Faces Flow Initializer and Finalizer. You can do whatever you want before initial ‘ includePage’  fragment is displayed. To do that, all you need, is to add another important property in the Job Flow managed bean definition – the cdiBean:

<managed-property>
    <property-name>cdiBean</property-name>
    <property-class>dyntabs.interfaces.DyntabBeanInterface</property-class>
    <value>#{employeesBean}</value>
</managed-property>

The property-class in always the same: dyntabs.interfaces.DyntabBeanInterface. The value points to one of your application specific CDI bean, in this case, the employeesBean:

import dyntabs.BaseDyntabCdiBean;
....
@Named
@javax.faces.view.ViewScoped
public class EmployeesBean extends BaseDyntabCdiBean {
    .....
}

The class should extend dyntabs.BaseDyntabCdiBean infrastructure class. In order to have access point method, just override the

protected void accessPointMethod(Map parameters)

method.As I said, in that method, you can do whatever you want . In demo application, I used that method to grant or restrict access to Employees application module. To see access point method in action, log in to the demo application as user@mail.com/USER123 , and try to access Employees module:

No alt text provided for this image

The source code for that method is in the provided demo application, EmployeesBean.java class. In the same way, you can define a Exit Point Method – just override the

protected void exitPointMethod(Map parameters)

method. NOTE: ‘parameters’ Map in above methods, are actually parameters passed on Job Flow, so  in these methods you can access them, I'll talk about them later.

View nodes and Method call nodes. Navigation between view nodes within the same dynamic tab

As I already mentioned, the initial view node is defined via  ‘includePage’ managed property of Job Flow definition. What, however, if you want, in the wizard-like manner, to navigate to another view node (ie, facelet fragment) within the same dynamic tab, and in that process, call some Java method (method call node) between two view nodes? Consider following  example from demo application: -Log into application as admin@mail.com,  launch Employees module from the menu on he left side, and click on the ‘Create new employees’ button:

No alt text provided for this image

This will launch another Job Flow defined in the faces-config.xml, the EmployeeCRUDDynTab:

No alt text provided for this image

This Job Flow consist of two View activities (ie, facelet fragments):

No alt text provided for this image

As you can see, there is ‘Next step >>’ button, which navigates from employees_step1.xhtml to employees_step2.xhtml. The source code for that button is as follows:

<p:commandButton rendered="#{!dynTab.parameters.createAndReturn}"
                 value="Next step >>"
                 process="@form"
                 action="#{cdiBean.callViewActivity('/WEB-INF/include/employees/employees_step2.xhtml')}"/>

The key part for navigation between view nodes is action property, which calls callViewActivity() method of the Job Flow’s cdiBean (EmployeeCRUDBean in this case). The method accepts a String argument, which is simply a path to the next facelet fragment, in this case, '/WEB-INF/include/employees/employees_step2.xhtml'.

You can put a method call node/activity between two view activities, simply by overriding the

public void callMethodActivity(String oldViewActivity, String newViewActivity)

method in the Job Flow’s cdiBean class:

/**
 * Override this method in order to call Java code between two View nodes.
   @param oldViewActivity - the path of the node from which navigation is performed
   @param newViewActivity - the path of the node to which it is navigated
 */

public  void callMethodActivity(String oldViewActivity, String newViewActivity) {
    super.callMethodActivity(oldViewActivity, newViewActivity);

    if ("/WEB-INF/include/employees/employees_step1.xhtml".equalsIgnoreCase(oldViewActivity) &&
            "/WEB-INF/include/employees/employees_step2.xhtml".equalsIgnoreCase(newViewActivity)) {
        //  employees_step1.xhtml --> employees_step2.xhtml navigation, do some usefull stuff here..
        // you can access to the Job Flow parameters via getParametes() method
        
        FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("callMethodActivity()", "Doing some stuff before proceed to employees_step2.xhtml... " ));
    } else  if ("/WEB-INF/include/employees/employees_step2.xhtml".equalsIgnoreCase(oldViewActivity) &&
            "/WEB-INF/include/employees/employees_step1.xhtml".equalsIgnoreCase(newViewActivity)) {
                    //  employees_step2.xhtml --> employees_step1.xhtml navigation, do some usefull stuff here..
            FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("callMethodActivity()", "Doing some stuff before proceed to employees_step1.xhtml... " ));
   }
}

See this code in action by clicking ‘Next step >>’ button:

No alt text provided for this image

Communication between Job Flows. Sending and receiving  application messages

Consider ‘Save and create new’ button, on the employees_step2.xhtml

No alt text provided for this image

The source code for that button is:

<p:commandButton value="Save and create new"
                 process="@this"
                 actionListener="#{cdiBean.sendMessageToEmployees}"
                 action="#{cdiBean.callViewActivity('/WEB-INF/include/employees/employees_step1.xhtml')}"/>

In order to inform Employees Job Flow about creating a new employee (ie, communicating with another Job Flow), button contains the actionListener which calls sendMessageToEmployees() method of the cdiBean:

/**
  Send message to Employees Job Flow.
  The first parameter of the sendMessageToAppModule() method is a Job Flow uniqueIdentifier of target Job Flow,
  as defined in the faces-config.xml
*/
public void sendMessageToEmployees(ActionEvent ae) {
    sendMessageToAppModule("Employees", "New employees were added");
}

On the other hand, target Job Flow  (in this case, Employees) can react to application message (received from another Job Flow):

No alt text provided for this image

All you need to do, is to override the

protected void onApplicationMessage(String senderId, Object payload)

method in  receiving Job Flow cdiBean (in this case, EmployeeBean.java):

/**
   Application message event handler
   @param senderId - uniqueIdentifier of sender Job Flow's (as defined in faces-config.xml)
   @param payload - a message received from the sender
*/
protected void onApplicationMessage(String senderId, Object payload) {
     super.onApplicationMessage(senderId, payload);
     FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(getClass().getSimpleName() + ".onApplicationMessage()", payload.toString() ));
     PrimeFaces.current().ajax().update("mainGrowl");
}

Job Flow call and return value.  Consuming return value. Job Flow parameters

Exactly like in standard JSF Faces Flow, you can call another Job Flow from within current Job Flow. Each Job Flow can return value back to the caller. Consider ‘Create one employee and return’ button within Employees Job Flow:

No alt text provided for this image

This button calls the CreateOneEmployee Job Flow (registered in the faces-config.xml). The button source code is as follows:

<p:commandButton process="@this"
                 value="Create one employee and return"
                 action="#{cdiBean.callCreateOneEmployeeJobFlow}"/>

Take a look at action method source code (from the caller Job Flow cdiBean, EmployeesBean.java):

/**
  This action will launch the CreateOneEmployee Job Flow from within Employees Job Flow, passing parameters to them.
  The key parameter here is the "callerID".
  Based on it, the called Job Flow knows that it has been called by another flow, and will use that value in order
  to send back return value to the caller.
  The value of the "callerID" param is actually a uniqueIdentifier of the caller, as defined in faces-config.xml
 
*/
public String callCreateOneEmployeeJobFlow() {
    DynTab empTab = (DynTab) JsfUtils.getExpressionValue("#{CreateOneEmployeeDynTab}");
    Map params = new HashMap();
    params.put("callerID", this.getUniqueIdentifier());
    params.put("createAndReturn", true);
    empTab.setParameters(params);
    return "uishell:CreateOneEmployee";
}

As you can see, you can send parameters to the called Job Flow. The key parameter, whics makes called Job Flow to be aware of caller (and thus be able to send back return value to the caller) is a predefined parameter named “callerID”

No alt text provided for this image

Now, how to return value to calling Job Flow (Employees)? Consider ‘Save and return’ button from the above image. The button source code is as follows:

<p:commandButton rendered="#{dynTab.parameters.createAndReturn}"
                 value="Save and return"
                 update="@form" process="@form"
                 actionListener="#{cdiBean.closeAndReturnValueToCaller}"/>     

What closes the flow, and what returns the value to the calling flow, is actionListener method, from associated flow’s cdiBean (in this case, CreateOneEmployeeBean.java):

/**
  The key part here is a closeAndReturnValueToCaller() method, whics accept any Object, as Job Flow return value
*/
public void closeAndReturnValueToCaller(ActionEvent ae) {
     closeAndReturnValueToCaller("A new employee were added: " + getFirstName());
}

The last part, is how to consume flow return value, in the caller Job Flow. All you need to do, is to override the onJobFlowReturn() method, in the caller’s cdiBean (in this case, EmployeeBean.java):

/**
 This method will respond on job flow return.
  @param senderId - the uniqueIdentifier of called Job Flow, as defined in faces-config.xml
  @param jobFlowReturnValue - this is a returned value from called Job Flow
*/
protected void onJobFlowReturn(String senderId, Object jobFlowReturnValue) {
     super.onJobFlowReturn(senderId, jobFlowReturnValue);
     // do some fancy with jobFlowReturnValue
}

Event handlers for dynamic tab events

Dynamic tabs generates a 4 type of events: when dyn tab added, when dyn tab removed, when dyn tab selected, and when ‘this’ tab selected. The first 3 refer to the other tabs in the application, the fourth refers to dyn tab itself:

No alt text provided for this image

If you want to respond to those events, in the flow’s cdiBean class, override following methods:

protected void onDynTabAdded(DynTab addedTab);
protected void onDynTabRemoved(DynTab removedTab);
protected void onDynTabSelected(DynTab selectedTab);
protected void onThisTabSelected();

For one example, take a look at the HomeBean.java in the provided demo application.

Note about dialogs, opened at runtime

A key feature for JSF SPA application with dynamic tabs, is to allow web app user to work simultaneously with multiple application modules, where the state of each application module must be preserved when switching to other tabs. Consider ‘Open the dialog’ button, from Departments flow:

No alt text provided for this image

We want for that dialog, to survive any tab event, like adding new tab, closing some tab, select some other tab, so that user can continue with this flow. The button source code is:

<p:commandButton process="@this" value="Open the Dialog"  update="#{dynTab.tabSubviewClientId}:dlg1">
        <f:setPropertyActionListener target="#{dynTab.dynTabVisibleMap.dlg1}" value="#{true}" />
</p:commandButton>

In doing so, the dialogue is placed at the very bottom of ui:define tag (departments.xhtml), just before end of this tag. The key parts is button’s update property, as well as the f:setPropertyActionListener. Pay attention to ‘dlg1’, which is a id of the dialog that should be opened. The dialog source code is:

<p:dialog header="This dialog will survive any DynTab event"  resizable="false" id="dlg1"  
          showEffect="fade" hideEffect="explode" dynamic="true"
          visible="#{dynTab.isComponentVisibleById(component.id)}">
          <p:ajax event="close" listener="#{dynTab.handleDlgClose}"/>
                         
          <h:outputText value="...unless you explicitly close him...."/>

          <f:facet name="footer">
               <p:commandButton  value="Close dialog"  process="@this" update="@parent">
                      <f:setPropertyActionListener target="#{dynTab.dynTabVisibleMap.dlg1}" value="#{false}"/>
               </p:commandButton>
          </f:facet>
</p:dialog>              

The key parts is visible property (same for all dialogs), ajax for close event, with one the same listener method for all dialogs, and f:setPropertyActionListener of the button which closes the dialog (target depends of dialog id, which is dlg1 in this case).

Instead of a conclusion...

Try it, and tell me your impressions. Sincerely yours,...

Gonzalo Casadevall

Desarrollador Java - Datalogic Software - IMPO Diario Oficial

7mo

Muy buena idea saludos

Like
Reply

Great ! Undoubtedly a great JSF project! You nailed it!

To view or add a comment, sign in

Insights from the community

Others also viewed

Explore topics