Skip to main content

Adding Dynamic Faces Support to a jMaki widget

Posted by edburns on December 7, 2006 at 9:09 AM PST

In this entry, I show how I added Dynamic Faces support to the Dojo
Inline Editor widget in jMaki.

Background and History

If you've read my blog before, you know that I think JSF is a good
fit for handling the href="http://weblogs.java.net/blog/edburns/archive/2006/11/motherhood_appl.html">motherhood
and apple pie requirements of web applications. What you may not
know is that jMaki intentionally
leaves the work of satisfying these requirements as an exercise for the
developer. No problem, just use jMaki and JSF together. This is indeed
a valid answer, but there is a subtlety that bears clarification.

When using jMaki + JSF under J2EE 1.4 (the default target audience
for jMaki), you miss out on the following features:

  • Ability to have the jMaki widget POST back to the faces
    page while preserving the JSF view state.

  • Ability to use jMaki widgets that represent components
    that accept user input within an iterating component such as
    dataTable.

  • Ability to attach valueChangeListeners to jMaki
    components in the view.

  • Ability to attach converters and validators to jMaki
    components in the view.

  • Ability to attach action and/or actionListeners to
    jMaki components in the view.

These deficiencies stem both from missing features in the built-in
jMaki JSF support and from missing features in JSF 1.1 commonly used
with J2EE 1.4.

The story about jMaki and Dynamic Faces

By using jMaki with href="https://jsf-extensions.dev.java.net/">Dynamic Faces on top of
JavaEE 5, however, all of these deficiencies can be addressed (with the
exception of the last two, and issues href="https://ajax.dev.java.net/issues/show_bug.cgi?id=57">have been
filed
for these features). This section discusses how using jMaki and Dynamic
Faces addresses all of the deficiencies mentioned above.

Ability to have the jMaki widget POST back to the faces page while
preserving the JSF view state.

This is the big one, and it was at the heart of the href="http://weblogs.java.net/blog/edburns/archive/2006/05/javaone_video_a_1.html">JavaOne
presentation in which I participated. The lack of this feature in
many Ajax + JSF solutions is what prompted us to title the session,
"Ajax done right". When you do not have the JSF view state, you
cannot do any of the things that JSF does well. Including
the JSF View state in the Ajax transaction is essential.

By using Dynamic Faces to send and receive Ajax transactions from
the browser to the JSF view, the JSF view state is correctly
preserved, allowing the entire JSF lifecycle to be used, including
conversion, validation, events, etc.

Ability to use jMaki widgets that represent components
that accept user input within an iterating component such as
dataTable.

This one is near and dear to href="http://weblogs.java.net/blog/jhook/">Jacob Hookom's heart.
Not only do you need the JSF view state, but you need a way to apply
individual values to an individual components within a composite
component (such as a table or tree). This capability did not exist in
JSF until version 1.2, and Dynamic Faces takes full advantage of it.

Ability to attach valueChangeListeners to jMaki
components in the view.

This didn't exist in jMaki until href="https://ajax.dev.java.net/issues/show_bug.cgi?id=3">issue 3
was fixed. However, even though you can add a valueChangeListener to a
jMaki component now, you still need to preserve the JSF view state in
order for it to ever have a chance of being invoked. Again, Dynamic
Faces is the answer.

Ability to attach converters and validators to jMaki
components in the view.

Ability to attach action and/or actionListeners to
g jMaki components in the view.

As with valueChangeListener, these two need to be first
implemented in jMaki, then they need to be used with Dynamic Faces.

How To

The whole point of jMaki is to put today's hot JavaScript widget
libraries in the hands of Java web application developers. Each widget
library has different ways of doing things, and indeed, each widget
within each library does too. jMaki solves this by having specific
files with each widget. Therefore, adding Dynamic Faces support to each
widget involves making some modifications to the widget specific files.
Currently, the only widgets that have been modified are the href="http://sunapp1.whardy.com:8090/jsf-jmaki/fisheye.jsf">Dojo FishEye and Inline Editor, and the href="http://sunapp1.whardy.com:8090/jsf-jmaki/home.jsf">Scriptaculous Inplace Editor. This section shows some high
level guidelines for how to add Dynamic Faces support to an arbitrary
jMaki widget.

The widget specific files for a jMaki widget are:

File Name Purpose
component.css Any CSS style associated with an instance of this widget.
component.htm The markup that will be included in the page an instance of this
widget.
component.js The JavaScript adapter file for this widget. This adapts between
the widget library specific way of doing things and the jMaki server
side framework. This is also the easiest place where Dynamic Faces
support can be added.
widget.json Any name/value pairs that aid in the configuration of an instance of
this widget.

In general, you'll probably just need to modify component.js to add
Dynamic Faces support. Please follow Jennifer's Tutorial for how to get
started with jMaki and Dynamic Faces. Once you've done that, you need
to make sure to include the Dynamic Faces taglib and script tag in the
page on which you are using the jMaki widgets.

Syntax highlighting courtesy of http://www.nopaste.com/.

Listing 1, the JSP Page

  1. <%@ taglib prefix="a" uri="http://java.sun.com/jmaki-jsf" %>
  2. <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
  3. <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
  4. <%@ taglib prefix="jsfExt" uri="http://java.sun.com/jsf/extensions/dynafaces"%>
  5.  
  6. Editor Using JSF


  7.  
  8.  
  9. >
  10.  
  11. "false">
  12.   id="table" rows="10" binding="#{ResultSetBean.data}"
  13.                value="#{ResultSetBean.list}" var="customer">
  14.  
  15.    
  16.       name="header">
  17.         value="Account Id"/>
  18.       </f:facet>
  19.       id="accountId"
  20.                      value="#{customer.accountId}"/>
  21.     </h:column>
  22.  
  23.    
  24.       name="header">
  25.         value="Customer Name"/>
  26.       </f:facet>
  27. :ajax name="dojo.inlineedit" value="#{customer.name}" />
  28.     </h:column>
  29.  
  30.    
  31.       name="header">
  32.         value="Symbol"/>
  33.       </f:facet>
  34.       id="symbol"
  35.                      value="#{customer.symbol}"/>
  36.     </h:column>
  37.  
  38.    
  39.       name="header">
  40.         value="Total Sales"/>
  41.       </f:facet>
  42.       id="totalSales"
  43.                      value="#{customer.totalSales}"/>
  44.     </h:column>
  45.  
  46.   </h:dataTable>
  47.  
  48. </h:form>
  49.  
  50. </f:view>
  51.  

Line 4 has the taglib for Dynamic Faces, line 11 uses the scripts
tag. Line 29 has the use of the jMaki ajax component.

Let's take a look at the component.js for the Dojo Inline Edit
widget, before Dynamic Faces support was added.

Listing 2, the component.js before Dynamic Faces is added

  1. dojo.require("dojo.widget.*");
  2. dojo.require("dojo.widget.InlineEditBox");
  3. dojo.require("dojo.event.*");
  4.  
  5. var container = document.getElementById(widget.uuid);
  6. var w = dojo.widget.createWidget(container);
  7.  
  8. w.getValue = function() {
  9.     return w.textValue;
  10. }
  11.  
  12. // add a saveState function
  13. if (typeof widget.valueCallback != 'undefined') {
  14.     w.onSave = function(newValue, oldValue) {  
  15.         // we need to be able to adjust this
  16.         var url = widget.valueCallback;
  17.         dojo.io.bind({
  18.                 url: url + "?cmd=update",
  19.                 method: "post",
  20.             content: { "value" : newValue  },
  21.             load: function (type,data,evt) {
  22.                 // do something if there is an error
  23.             }
  24.         });
  25.     }
  26. }
  27. w.saveState = w.onSave;
  28. jmaki.attributes.put(widget.uuid, w);

On lines 1 - 3 we have the dojo includes: uninteresting. Line 5 of
listing 2 is interesting. The "widget" refererred to is this instance
of this widget in the page. In this case, it's the widget on line 29 of
Listing 1. However, keep in mind that line 29 of listing 1 is inside of
a dataTable, therefore, there will be 10 instances of this widget in the
page, each corresponding to an actual JavaScript object in the
JavaScript VM. Therefore, the value of widget.uuid will be
different for each widget, and will, most importantly, correspond to the
JSF Client Id for the widget in the JSF view. This is important because
in order to address the widget in the JSF view, you need its client
id.

For example, the widget.uuid values for this page happen
to be table:n:j_id_id34 where n is the
numbers 0 to 9.

Returning to line 5 of listing 2, the actual DOM element returned by
the call to document.getElementById is what jMaki renders
based on the content of the component.htm file. For the
dojo inline editor, this file is:

  1. id="${uuid}" dojoType="inlineEditBox" class="dInlineEdit">${value}

The jMaki AjaxWrapperRenderer will cause the
component.htm file to be rendered like this in the page
from listing 1.

  1. id="table:0:j_id_id34" dojoType="inlineEditBox" class="dInlineEdit">name_0

Therefore, when line 5 of listing 2 executes, the value of
container is the div element with id
table:n:j_id_id34, where n, of course, is the
current row in the table.

Line 6 of listing 2 uses the dojo createWidget to create
an instance of the inline edit box inside of the container
element.

Lines 14 - 25 of listing 2 declares the JavaScript function that will
be called when the user presses the "save" button in the widget UI.

Now, let's take a look at the changes to component.js necessary to
add Dynamic Faces support, expressed as a diff.

Listing 3, the diffs to component.js to add Dynamic Faces support

  1. --- component-pre-DF.js 2006-12-07 10:32:25.000000000 -0500
  2. +++ component.js 2006-12-07 10:31:19.000000000 -0500
  3. @@ -12,6 +12,16 @@
  4. // add a saveState function
  5. if (typeof widget.valueCallback != 'undefined') {
  6.      w.onSave = function(newValue, oldValue) {  
  7. +
  8. +      if (typeof _globalScope.DynaFaces != 'undefined') {
  9. +   DynaFaces.fireAjaxTransaction(container,
  10. + {
  11. + execute: widget.uuid,
  12. + render: "none",
  13. + postBody: widget.uuid + "=" + newValue
  14. + });
  15. +      }
  16. +      else {
  17.          // we need to be able to adjust this
  18.          var url = widget.valueCallback;
  19.          dojo.io.bind({
  20. @@ -22,7 +32,8 @@
  21.                  // do something if there is an error
  22.              }
  23.          });
  24. +      }
  25.      }
  26. }
  27. w.saveState = w.onSave;
  28. -jmaki.attributes.put(widget.uuid, w);
  29. \ No newline at end of file
  30. +jmaki.attributes.put(widget.uuid, w);

The only change is a conditional within the onSave
function. jMaki defines a _globalScope global variable
that allows you to access all things globally defined in the page. It
so happens that the scripts included by virtue of line 11 of listing 1
define a global variable called DynaFaces. This JavaScript
object has all the functions exposed by the Dynamic Faces JavaScript
library. Therefore, you can safely test for the existing of Dynamic
Faces in a page as shown on line 8 of listing 3. If Dynamic faces is
not being used in the page, then the normal jMaki operation of the
widget takes place, as shown on lines 17 - 23 of the diff.

Lines 9 - 14 of listing 3 cause a Dynamic Faces Ajax transaction to
be made back to the JSF page currently being viewed in the browser.
This transaction includes all the view state for the current
f:view. The first argument to
fireAjaxTransaction is the DOM element from which the
transaction should be fired. It is important that this be the DOM
element that corresponds to the actual JSF UIComponent in
the view. The second argument to fireAjaxTransaction is a
JavaScript associative array. In this case, we're saying three things:

  • do the 'execute' portion of the JSF lifecycle just on
    the JSF UIComponent for this widget instance.

  • do not re-render anything to the browser (in this case,
    Dojo handles it).

  • In the HTTP POST body of the request, include the name
    value pair for the currently entered value of the widget.

Note that newValue, on line 6 of listing 3, is an
argument to the onSave function. It is the new value
entered by the user.

For more information on the JavaScript functions exposed from Dynamic
Faces, see href="https://jsf-extensions.dev.java.net/nonav/mvn/reference-ajax.html">the
Dynamic Faces Reference.

Summary

jMaki gives you cool widgets. Dynamic Faces + jMaki gives you cool
widgets you can use in a real enterprise web application.

Technorati Tags:

Related Topics >>