Writing and using SuperHelpers within Sakai

Some documentation on the state of "Helpers"/Gadgets/Widgets etc. is at Helpers and Gadgets within Sakai. This page describes a particular implementation strategy for the markup-based "AHAH"-style helpers which are described there, which make use of RSF and the EntityBroker. This strategy for navigation interception is also known as Hijax.

Neither RSF nor the EntityBroker are required to adopt this strategy, nor need it particularly refer to "helpers" which are hosted within the Sakai server at all. RSF is illustrated because it is a technology which is now popular for new development in Sakai, and in which these techniques are quite easy. The EntityBroker is used, since as described on the Helpers, it is a good scheme for achieving short, semantically meaningful and architecturally decoupled URLs for application segments in Sakai, which are also meaningful when dispatched outside it. Use of the EntityBroker does not imply the use of RSF, but there is a standard framework integration between these technologies.

All of the code described here is present in contrib SVN at entity-broker-test. In order to be run correctly, it requires the EntityBroker and TestRunner to both be deployed into the Sakai installation. These should run in any Sakai later than 2.2.0, but will only be particularly well supported in 2.4.x versions.

Setting up an entity for the helper

To take control of part of the global URL space for the helper, you need to define an entity to represent it. Since this particular entity has no function other than being the target for a URL dispatch, this only requires 2 lines of code in the impl. It is conventional to use a "tag API" at the Sakai shared API level to provide easy access to the name of your entity. TestHelperProvider looks like this:

public interface TestHelperProvider extends EntityProvider {
  public static final String ENTITY_PREFIX = "testhelper";
}

The implementation TestHelperProviderImpl for this interface simply represents that any entity ID will be permitted for dispatch (typical for a helper) and that it registers this prefix:

public class TestHelperProviderImpl implements TestHelperProvider, 
 CoreEntityProvider, AutoRegisterEntityProvider {
 
  public boolean entityExists(String id) {
    return true;
  }

  public String getEntityPrefix() {
    return ENTITY_PREFIX;
  }

}

These parts of the process are independent of the presentation technology used.

Creating a webapp to host access to the helper

The helper webapp will be used to be the target of HTTP accesses to the helper. This need not be a dedicated webapp - it may be a webapp which is a traditional Sakai tool, as well as simply being a "pure Servlet" as in this case. It may also be a webapp which is being used to host any number of other helpers.

In RSF, the Spring definitions used to register a handler for a particular helper access look as follows (in applicationContext.xml):

  <!-- All beans implementing entityVPInferrer are aggregated automatically -->
  <bean class="org.sakaiproject.entitybrokertest.helper.TestHelperVPInferrer"/>

where the implementing bean appears as

public class TestHelperVPInferrer implements EntityViewParamsInferrer {

  public String[] getHandledPrefixes() {
    return new String[] { TestHelperProvider.ENTITY_PREFIX };
  }

  public ViewParameters inferDefaultViewParameters(String reference) {
    return new SimpleViewParameters(TestHelperProducer.VIEW_ID);
  }

}

There is nothing special about this registration, since a "helper" of this form is really identical with any other form of Entity URL host within RSF. A special page on this topic is at Entity URLs in RSF.

The views forming the "helper space" are then defined as normal within the webapp, with the default view being the one nominated above as TestHelperProducer.

Invoking the helper from a tool or other helper

This test application demonstrates accessing the helper from a different Entity space (/direct/testentity which in itself is essentially another helper. If it were invoked directly from a tool, the Sakai portal CSS and JS would correctly resize the overall frame. The crucial "glue" needed to perform the client-side processing is in the file ahah.js which invokes standard JS functions within the RSF framework file rsf.js. More of ahah.js will be folded into the framework with RSF 0.7.3, but its essential code currently looks as follows:

   initAHAH: function (targetId, URL) {
         var target = document.getElementById(targetId);
         
         var callback = function(results) {
            // callback function defines what to do when the ahah response is received,
            // the response will be placed in the "results" variable
            target.innerHTML = results; // put the html in the target div

            RSF.transformActionDomToAJAX(target); // navigation and forms will retarget via AHAH
            RSF.getDOMModifyFirer().fireEvent(); // notify portal that DOM height has changed
         }
         // set up the function which initiates the AJAX form submission request
         var updater = RSF.getAJAXLinkUpdater({href:URL}, callback, URL);
         // run the updater function immediately to get the current data into the results fields
         updater();
      },

This function accepts a DOM element ID and a URL (which need not form part of Sakai), and will initiate an initial AHAH fetch request at the URL whose contents will be written into the DOM node, with all navigation primitives "AJAXified" to themselves perform further submits via AHAH etc. Thus this DOM node from here on forms a self-contained "AHAH navigation world" in which navigation primitives rather than performing top-level navigation, simply rewrite the contents of the node.

Setting up use of this Javascript block in RSF is a straightforward use of the standard framework init block component. The user of the helper is the view TestEntityProducer. Firstly we need to compute the URL of the helper we wrote above, using the EntityBroker API:

String helperRef = new IdEntityReference(TestHelperProvider.ENTITY_PREFIX, "5").toString();
String helperURL = entityBroker.getEntityURL(helperRef);

Then we simply set up the peer for the target DOM node (the <div>} and write an init block invoking the Javascript call above:

UIInitBlock.make(tofill, "helper-init", "EntityBrokerHelpers.initAHAH", 
        new Object[] {helperHolder, helperURL});
UIOutput helperHolder = UIOutput.make(tofill, "helper-holder");

The peering markup in testentity.html (which also has some limited behavioural preview functionality) in the markup template looks as follows:

<div rsf:id="helper-holder" id="helper-holder">
  <!-- Material in here, including script, will be replaced -->
  <script rsf:id="helper-init">
    EntityBrokerHelpers.initAHAH("helper-holder", "../../../../helper/src/webapp/templates/testhelper.html");
  </script>
</div>

Note that since as with all RSF templates, this "preview" definition is essentially functional, it also serves as a template for those who want to achieve the same effect in other presentation technologies (e.g. Velocity, JSPs, XSLT, etc.)

Limitations and future work

Right now, in line with the general Helpers discussion, it is difficult to host helpers which themselves contain Javascript definitions, or invoke others via a similar AHAH scheme. We will be working together with the Fluid project to draw up criteria and good working practices for enabling this use over the next few months.

Currently the use of AHAH helpers within Sakai involves multiple server requests on the initial page load, in order to populate each helper's <div>. In RSF version 0.7.3, a new dedicated component UIDimensionalLink, as well as general framework reentrancy, will enable complex helper views, so long as they are dispatched solely within Sakai, to be aggregated on initial load within a single request, if the originating request is from an RSF webapp.