Tuesday Dec 08, 2009

Client-side Eventing Example

In the previous blog, Eventing in AJAX portlets, we saw an overview of client side eventing mechanism in OSPC and WebSpace, which works like a simple yet powerful extension of JSR-286 eventing.

Lets look at an example to see how this all works.

EventGeneratorAjaxPortlet in this example, takes a zip code through a text field and has a button "Get GeoCode".

<input name="getbtn" id="getbtn" value="Get Geocode" type="button" onclick="<portlet:namespace/>PortletObj.processAction('<portlet:resourceURL/>')"/>

The onClick of the button calls processAction method on a javacript object called PortletObj which is prefixed with portlet's namespace. So first define a processAction javascript function just like one would have done while overriding the GenericPortlet's processAction method in the Portlet's Java class on server side.

<portlet:namespace/>PortletObj = { portletReq : null, processAction : function(updateURL) { portletReq = new XMLPortletRequest("<portlet:namespace/>"); portletReq.onreadystatechange = function() { <portlet:namespace/>PortletObj.render(); }; //collect information and prepare data to POST to server ... ... portletReq.open("POST", updateURL, true); portletReq.send(data); }, render : function() { var result = JSON.parse(portletReq.responseText, null); // process the response and modify the DOM ... ... var qName = {uri: "http://www.example.com/clientevents", name : "MyEvent" }; var eventPayload = {}; eventPayload.x = result.x; eventPayload.y = result.y; portletReq.setEvent(qName, eventPayload); } };

The processAction method, uses XMLPortletRequest which is initialized using the portlet's namespace. Portlet container automatically makes this available by adding the javascript file to the portlet during deployment. It is only required to include the script in the jsp from under <context-path>/js/ like this,

<script type="text/javascript" src="<%=renderRequest.getContextPath()%>/js/XMLPortletRequest.js"> </script>

In the example above, function names processAction and render are only a matter of following the JSR-286 pattern. The names of these functions could have been anything. More important is to note how "setEvent" is called on the XMLPortletRequest and an arbitrary event payload can be passed as a JSON object.

It is possible to use qName without the uri. The new RFE makes it possible to call setEvent with a string as an event name instead of requiring QName.

Show/Hide JSP

The serveResource method of the portlet computes latitude and longitude for the zip code, using Yahoo geocode service and returns a JSON object from its serveResource method.

Show/Hide serveResource

EventConsumerAjaxPortlet in this example, consumes the event generated by EventGeneratorAjaxPortlet, by implementing a PortletObj.processEvent javascript function. This function is called in response to the XMLPortletRequest.setEvent call in the processAction of event generator.

The wiring is taken care by Portlet Container at runtime as we will see later, but is done in portlet.xml by the developer as shown below.

<portlet> <portlet-name>EventGeneratorAjaxPortlet</portlet-name> ... ... <supported-publishing-event xmlns:x='http://www.example.com/clientevents'> <qname>x:ZipEvent</qname> </supported-publishing-event> </portlet> <portlet> <portlet-name>EventConsumerAjaxPortlet</portlet-name> ... ... <supported-processing-event xmlns:x='http://www.example.com/clientevents'> <qname>x:ZipEvent</qname> </supported-processing-event> </portlet> <event-definition xmlns:x='http://www.example.com/clientevents'> <qname>x:ZipEvent</qname> <value-type>com.sun.portlet.ClientEvent</value-type> </event-definition>

The portlets could have been packaged separately and may have separate portlet.xml. Also, since this is same as server-side eventing, it is possible to use tools like NetBeans PortalPack, and to use the eventing storyboard to wire the portlets visually instead of handcoding the xml.

Typically, this is how a consumer is implemented.

<portlet:namespace/>PortletObj = { portletReq : null, processEvent : function(eventObj) { portletReq = new XMLPortletRequest("<portlet:namespace/>"); portletReq.onreadystatechange = function() { <portlet:namespace/>PortletObj.render(); }; //process the event payload and prepare data to POST to server ... portletReq.open("POST", '<portlet:resourceURL/>', true); portletReq.send(data); }, render : function() { var response = JSON.parse(portletReq.responseText, null); // process response and modify the DOM ... } };

The namespaced PortletObj.processEvent function is mandatory and is the only forced naming convention required to be followed. It is not required to implement render function. It was possible to include all render code, instead of a call to render, in the callback function. But again, as a matter of convention and pattern, its much cleaner to implement render.

Show/Hide JSP

The processEvent function gets zip, latitude and longitude in the event payload. It in turn calls serveResouce of the portlet to get all the pictures recently uploaded in 10 mile radius of the given latitude/longitude, using Flickr's REST service.

Show/Hide serveResource

How it works
Portlet Container reads portlet.xml during deployment and recognizes client-side events from the special marker class com.sun.portlet.ClientEvent. While rendering the portlet, it generates a namespaced javascript event queue, <portlet:namespace/>EventQueue, for the generator and populates it with a list of consumers on the page. When XMLPortletRequest.setEvent is called, it in turn calls setEvent on the generator's event queue. Generator's event queue loops through consumer list and calls <portlet:namespace/>PortletObj.processEvent on each. This is why it is mandatory to implement processEvent function to be able to consume events.

Monday Nov 30, 2009

Eventing in AJAX Portlets

Also known as client side eventing, it becomes a necessity when you have multiple AJAX portlets on a portal page, and since the whole page is never submitted, the JSR-286 Portlet Spec 2.0 eventing (server-side) is not any useful.

We implemented a new eventing extension, for client side events, in Open Source Portlet Container (OSPC), through which it made its way into WebSpace 10.0.

A developer, who is writing a Portlet Spec 2.0 compliant portlet, follows a pattern, certain conventions and implements specific interfaces. Then s/he creates a deployment descriptor, packages the portlet as a webapp and deploys it.
These are the steps that a typical 2.0 portlet development process will involve:

  • implement processAction method for generator- generating portlet calls setEvent method here, to generate an event
  • implement processEvent method for consumer - called by the portlet container on a portlet which consumes this event
  • implement render method (both) - renders content
  • create portlet.xml - defines elements for eventing

For client side eventing, the developer follows exact same steps, albeit on client-side, for example in the jsp.
In javascript, the developer implements a processAction and render functions for an event generating portlet. In processAction or render function, setEvent function is called by the developer. For event consumer portlet, javascript functions processEvent and render are implemented. Then the developer writes the deployment descriptor in the same way as defined in the Portlet 2.0 spec, except that the value-type of the event is identified by a special marker class com.sun.portlet.ClientEvent.

When portlets are deployed, portlet container reads the deployment descriptors. If portlet supports publishing event and if event's value type is the special marker class, then container automatically generates a javascript "EventQueue".
When portlet container reads the deployment descriptor for a portlet that supports processing the event and if event value type is the special marker class, then container automatically puts a call to the processEvent function of this portlet in the event queue of the generating portlet.

When generating portlet calls setEvent function, with the event payload as an argument, EventQueue of the generator in turn calls processEvent function of all the portlets which support processing this event.
Thus, wiring of the portlets is handled by container in the exact same way as it would have been done for server-side eventing, from the deployment descriptor. This happens transparently to the developer. 

Salient Features of this mechanism

  • Follows convention over configuration paradigm
  • Follows Portlet 2.0 spec defined pattern and conventions, but mimics it on the client side
  • Works like an extension of the specification
  • Eliminates developer learning curve
  • Auto wiring of portlets, transparent to the developer (handled by OSPC).
  • Arbitrary Event payload (similar as on server side)
  • Each generator portlet has its own namespaced event queue (auto generated)
  • Event generator portlet remains agnostic of the consumers.
  • Since the event queue of the generator portlet is populated by the container at runtime, no other code needs to change when new consumer portlets are deployed or added to the portal page.
  • Easily toolable by existing tools, because it follows the same conventions as the tools expect for server side eventing
  • Well defined name spacing and type definitions allows for deploy time checking
  • Use of W3C defined QName allows for standard name spacing
  • XMLPortletRequest (XPR) provides a wrapper over XMLHttpRequest (XHR), thus easing development further
In my next blog, we will take a closer look through an example. In the meantime, you can also take a look at this document as well as check out Deepak Gothe's blog on XPR.

Prashant Dighe


« July 2016