Client Side Event Bus in Rich ADF Web Applications - for easier, faster decoupled interaction across regions image 1

Client Side Event Bus in Rich ADF Web Applications – for easier, faster decoupled interaction across regions

The challenge is well known: our ADF Faces Web application is composed of various taskflows that have been developed as stand alone, encapsulated units that can be used and reused in various contexts. These taskflows can have come from third parties, remote development teams, other applications are we may have developed them ourselves. No matter their origin: they are encapsulated. However, things may happen inside these taskflows that should have an effect outside of them – for example when a selection is made inside the taskflow, another component on the page may need to be synchronized. The reverse will also occur: when something takes place outside a taskflow – for example the user entering data or performing a navigation – the UI rendered by the taskflow should be refreshed accordingly.

The ADF Faces framework has a built in solution for this challenge: Contextual Events (see for example my earlier article on these events). Using contextual events, you can certainly do the trick. And conceptually, I like them. In reality – not so much. The steps required for configuring contextual events are cumbersome, not intuitive and not well supported by the IDE. They are simply too complex to have been embraced in main stream ADF development. Additionally, ADF is a very server oriented framework and contextual events are always handled through the server. However, there are plenty of situations where the server oriented approach is too heavy handed.

So following up on some of the thinking introduced in the OTN article on Marrying the Worlds of ADF and HTML 5 (with Paco van der Linden) and prompted by a concrete challenge on one of our projects this week (and also inspired by the Client Side Event Bus I described for Oracle JET in this article Introducing: The Client Side Event Bus in Oracle JET for decoupled interactions), I decided to try an come up with a model for a client side event bus in ADF that allows simple, intuitive, client side event exchange across regions and fragments. This article describes the results of that search.

The Concrete Challenge

The challenge at hand is quite far fetched – and may seem very simple: when one of the colored buttons is pressed, the radio group is updated accordingly:

image

 

This seems like a very simple thing. However, the underlying structure of the page is like this:

image

 

Something happening inside a page fragment in a taskflow that is in a region in a fragment inside a second taskflow in the main page should have an effect on a select one radio component inside a fragment in a completely different taskflow included in the same main page. The origin of the event and the place where it should have an effect are completely unrelated. Through contextual events exposed and consumed at taskflow level and always via server roundtrips we can get the desired effect. This article demonstrates an easier way of achieving that same effect without sacrificing the encapsulation and decoupling.

The figure below shows the approach in broad strokes: JavaScript functions can be registered as event consumers with the event bus. These functions are called whenever an instance of the indicated event type is published to the bus. Any fragment can contain standard ADF Faces client listeners that respond to user actions and client events; a client listener invokes a JavaScript function that can invoke the event bus to publish an instance of an event type. Most events will carry a payload – that further adds meaning and context to the event. The event bus invokes all consumers and hands them the payload of the event. The event consuming callback function processes the payload and does with it whatever it thinks it should do.

 

image

Any fragment can register for specific event types – whether publishers of the event are included in the page or not – with the event bus. Any fragment can contain JavaScript code that publishes events to the event bus. If an event type is published for which a consumer exists – the event bus takes care of forwarding the event so it can have the desired effect.

Below you will this mechanism in detail, applied to the color selection user experience.

 

Detailed Implementation of Color Selection With Event Subscription, Publication and Consumption

All source code for the demo ADF application can be found in this GitHub Repository: https://github.com/lucasjellema/ADFClientSideEventBus.

The event bus itself is implemented in a straightforward JavaScript library that is imported into the main page:

function initializeEventBus(evt) {
    alert('event bus reports for pubsub  duty');
}

var subscriptions = {};

function publishEvent( eventType, payload) {
   console.log('Event published of type '+eventType);
   console.log('Event payload'+JSON.stringify(payload));
    // find all subscriptions for this event type
   if (subscriptions[eventType]) { 
    // loop over subscriptions and invoke callback function for each subscription
    for (i = 0; i < subscriptions[eventType].length; i++) {
       var callback = subscriptions[eventType][i];
       try {
         callback(payload);
       }
       catch (err) {
           console.log("Error in calling callback function to handle event. Error: "+err.message);
       }
    }//for 
   }//if     
    
}// publishEvent


// register an interest in an eventType by providing a callback function that takes a payload parameter
function subscribeToEvent( eventType, callback) {
   if (!subscriptions[eventType]) { subscriptions[eventType]= [ ]};
   subscriptions[eventType].push(callback);
   console.log('added subscription for eventtype '+eventType);
}//subscribeToEvent

And the import into the main page:

<f:view xmlns:f="http://java.sun.com/jsf/core" xmlns:af="http://xmlns.oracle.com/adf/faces/rich"
        xmlns:h="http://java.sun.com/jsf/html">
  <af:document title="main.jsf" id="d1">
  <af:resource type="javascript" source="/resources/js/eventbus.js"/>
  <af:clientListener method="initializeEventBus" type="load"/>
<af:form id="f1">

The bus can accept subscriptions, receive events and route events to subscribed callback functions. Subscriptions for event types that will never be published are no problem as are events of types that no subscriptions exist for. As it should be.

The page fragment colorPicker.jsff in the taskflow color-picker-task-flow-definition.xml that is nested inside the dynamic container contains the three color buttons. Each button has a client listener that invokes a JavaScript function that calls out to the event bus to publish the event of type color-picker-tf-colorSelectionEvent:

    <af:resource type="javascript">
      function publishColorSelection(color) {
          publishEvent("color-picker-tf-colorSelectionEvent", 
          {
              "selectedColor" : color
          });
      }

      function clickRed(evt) {
          evt.cancel();
          publishColorSelection('red');
      }

      function clickYellow(evt) {
          evt.cancel();
          publishColorSelection('yellow');
      }

      function clickBlue(evt) {
          evt.cancel();
          publishColorSelection('blue');
      }
    </af:resource>
    <af:panelGridLayout id="pgl1">
        <af:gridRow height="100%" id="gr1">
            <af:gridCell width="100%" halign="stretch" valign="stretch" id="gc1">
                <!-- Content -->
                <h:panelGroup id="pg1">
                    <af:button text="Red" id="b1" inlineStyle="color:red;" partialSubmit="false">
                        <af:clientListener type="action" method="clickRed"/>
                    </af:button>
                    <af:button text="Yellow" id="b2" inlineStyle="color:yellow;" partialSubmit="false">
                        <af:clientListener type="action" method="clickYellow"/>
                    </af:button>
                    <af:button text="Blue" id="b3" inlineStyle="color:blue;" partialSubmit="false">
                        <af:clientListener type="action" method="clickBlue"/>
                    </af:button>
                </h:panelGroup>
            </af:gridCell>
        </af:gridRow>
</af:panelGridLayout>

The page fragment menu.jsff contains a JavaScript statement to subscribe function handleColorSelection() with the client event bus to the color-picker-tf-colorSelectionEvent events. This function receives the event, gets the selected color from the payload and continues to manipulate the select one radio component (after having to jump through some hoops to get a handle to that component).

<af:resource type="javascript">
    subscribeToEvent("color-picker-tf-colorSelectionEvent" , handleColorSelection);

    function handleColorSelection(payload) {
      console.log("ColorSelectionEvent consumed "+JSON.stringify(payload));
      var color = payload.selectedColor;
      console.log("selected color "+color);
      // get hold of ADF Faces Client Side AdfRichSelectOneRadio object
      // we use a trick that assumes only one element is rendered in the entire page with a css style class colorRadio
      // (that also means that page fragment can be included only once and so can the taskflow that this fragment is included in;
      // for this simple example, that is an acceptable limitation)
      var htmlElementForRadio = document.getElementsByClassName("colorRadio");
      // from the HTML element we get the element id and use that as the input for the findComponentByAbsoluteId call that returns the actual ADF Faces AdfRichSelectOneRadio object
      var radio = AdfPage.PAGE.findComponentByAbsoluteId( htmlElementForRadio[0].id);
      // the radio group has multiple select items; their labels and values can be retrieved 
      var sis = radio.getSelectItems();
      // now we can lookup the value of the select item with the label corresponding to the selected color:
      for (i = sis.length-1; i>=0; i--) {
       var si= sis[i];
       if (si.getLabel().toLowerCase() == color) {
           radio.setValue(si.getValue());     
           break;
       }//if
      }//for
    }//handleColorSelection
</af:resource>

The overall structure of the ADF resources is shown in the picture below. Three taskflows, three page fragments, all independent of one another – and all aware of the color-picker-tf-colorSelectionEvent  that allows them to mutually synchronize.

 

image

 

When the page is loaded, the event subscription is created:

image

When the blue button is clicked, the event gets published and routed to the callback handler that manipulates the select one radio component. The console logging is clear about what happens:

image

Note that the server is not involved in any of this! And why should it be?

Using ServerListener to Leverage Server for Response to Client Event Consumption

A typical action taken from a fragment in some taskflow upon receiving a client side event will be to send an event of its own to the server – to trigger a partial refresh after executing some server side logic. This can be done using a serverListener, a call to AdfCustomEvent.queue() and a server side managed bean that handles the client event. The server side logic can then make use of addPartialTarget on the current instance of ADFFacesContext to specify which client side components should be partially refreshed.

 

Resources

Oracle White Paper: ADF Design Fundamentals – Using JavaScript in ADF Faces Rich Client Applications – http://www.oracle.com/technetwork/developer-tools/jdev/1-2011-javascript-302460.pdf 

Pattern for obtaining client side component- blog article by Duncan Mills – https://blogs.oracle.com/groundside/entry/pattern_for_obtaining_adf_component

Presentation by Steven Davelaar on SlideShare on ADF Faces/JSF lifecycle including Partial Page Refresh – http://www.slideshare.net/stevendavelaar/18-invaluable-lessons-about-adfjsf-interaction