Engine Demonstration #15

[ engine demonstrations | engine overview | imnmotion.com | Stephen W. Cote ]

Demonstration #15 shows two approaches to making an image slideshow widget using Engine for Web Applications. An image slideshow widget is a control that can be added to a Web page to scroll through a predefined list of images. The first part of this demonstration will show how the widget is constructed using Application Components. The second part of this demonstration will show how the Engine Service is used to reduce the amount of code required, and provide a better downlevel experience.

A Note About Browser Support: Many demonstrations, including this one, use the EngineService module of Engine for Web Applications, which relies to some degree on XPath. Some browsers, including Opera up to 8.5, and Safari up to 1.3, do not support XPath and therefore the module will not operate. The EngineService module is entirely optional and is used in the demonstrations for its convenience.

Prerequisites

The files required for this demonstration are as follows. It is necessary to change the path references if attempting to directly implement these files.

The APIs referenced in this demonstration are:

Part 1

In Part 1, the widget is defined on the page, and includes three controls. The three controls are: Move back one image, Start/Stop the SlideShow, and Move forwards one image. Note that the IMG element includes a few custom attributes. These will be referenced by the ApplicationComponent. The source follows.

   <p>
      <input type = "button" value = " < " id = "oPrev" />
      <input type = "button" value = " Stop " id = "oStartStop" />
      <input type = "button" value = " > " id = "oNext" />
   </p>
   <p>
      <img
         src = "/reference/2005/10/10/single_interaction_report_1.jpg"
         base = "/reference/2005/10/10/"
         list = "single_interaction_report_1.jpg;single_interaction_report_2.jpg" 
         delay="5000"
         id = "oSlideShow"
         width = "300"
         height = "300"
      />
   </p>

A central objective of Engine for Web Applications is to separate functionality from content. Therefore, no event handlers need to be defined here. However, the HTML must be bound to the ApplicationComponents that define the functionality. The following script shows how this binding takes place. This isn't always desirable, or even easy to maintain, but outlining the long-hand version will help explain how the ApplicationComponents interoperate.

<script type = "text/javascript"><!--
window.onload = init;
function init(){
   // Create a random id.  This is used to give these four components a common and unique transaction name.
   var UniqueId = org.cote.js.guid();

   // The path to the ApplicationComponent definitions.
   var sPath = "/projects/engine/demonstrations/demonstration_15/application_components.xml";
	
   var AC = org.cote.js.appcomp.ApplicationComponent;

   // Bind the oPrev button to the slide-show-prev ApplicationComponent
   AC.bindComponent(
      document.getElementById("oPrev"),
      "slide-show-prev",
      sPath,
      UniqueId
   );

   AC.bindComponent(
      document.getElementById("oStartStop"),
      "slide-show-startstop",
      sPath,
      UniqueId
   );
   AC.bindComponent(
      document.getElementById("oNext"),
      "slide-show-next",
      sPath,
      UniqueId
    );
   AC.bindComponent(
      document.getElementById("oSlideShow"),
      "slide-show-image",
      sPath,
      UniqueId
   );
}
//--></script>

So far, the HTML for the slide show widget has been laid out, and the individual controls have been bound to as-yet-defined ApplicationComponents. The important thing to note about ApplicationComponents is that they automatically register as TransactionParticipants to any specified transaction name. In Engine, a Transaction is an intra-object communication channel. The two aspects of the TransactionService that will be directly referenced are the TransactionPacket, which is the object that holds the transaction information, and the serveTransaction method on the ApplicationComponent class, which wraps the serveTransaction on the TransactionService class. By participating in the same transaction, which was done by using the UniqueId in the previous code sample, the four application components are able to communicate with each other through the serverTransaction method and the TransactionPacket object.

The majority of the script for this slide show widget is in the slide-show-image ApplicationComponent. This component is responsible for interpreting the custom attributes, changing the image source, and implementing a Thread to manage the interval. The code for this component follows.

  <application-component id ="slide-show-image">
    <![CDATA[
      component_init: function(){
        // Create a new thread for this object
        var oThread = org.cote.js.thread.newInstance(this);
       
        // Status is an embedded object that holds status or config information
        var oData = this.getStatus();

        // getContainer() returns the HTML Node bound to this component
        var oSource = this.getContainer();
        
        // Copy the custom attributes to this object, and put them on the status object.
        oData.delay = parseInt(oSource.getAttribute("delay"));
        oData.images = oSource.getAttribute("list").split(";");
        oData.path = oSource.getAttribute("base");
        oData.index = 0;

        // Start the thread
        oThread.run(oData.delay);
        
        // Set a pointer to the thread on the TransactionPacket
        // This will allow other components in this transaction to reference the thread object
        this.getPacket().data = oThread;
        
        // Serve the TransactionPacket as "slidethreadready"
        // This is done to tell other components on this transaction that the thread object is available
        // The ApplicationComponent manages the implementation of TransactionService
        // And translates the custom packet type into a virtual method call: _handle_slidethreadready
        this.serveTransaction("slidethreadready");
      },

      // Invoked by the thread object
      handle_thread_run: function(){
		this.moveNext();
      },

      // Served by the transaction to move the image
      _handle_next:function(s,p){
        this.moveNext();
      },

      // Served by the transaction to move the image
      _handle_prev:function(s,p){
        this.movePrev();
      },      

      // Slide to the next image
      // If the Thread is running, restart it, so if some other process calls this method,
      // The thread delay won't seem unexpected.
      moveNext:function(){
        var oData = this.getStatus();
        oData.index++;
        if(oData.index >= oData.images.length) oData.index = 0;
		this.paint();      
		if(this.getPacket().data.getIsRunning()) this.getPacket().data.restart();
      },
      
      // Slide to the previous image
      movePrev:function(){
        var oData = this.getStatus();
        oData.index--;
        if(oData.index < 0) oData.index = oData.images.length - 1;
		this.paint();
		if(this.getPacket().data.getIsRunning()) this.getPacket().data.restart();
      },
      
      // Set the image source
      paint:function(){
        var oData = this.getStatus();
        var oSource = this.getContainer();
        oSource.src = oData.path + oData.images[oData.index];
      
      }

    ]]>
  </application-component>

The slide-show-image component is, in a nutshell, the slideshow code. The other component definitions follow:

  <application-component id ="slide-show-startstop">
    <![CDATA[
      // When clicked, start or stop the thread, and repaint the button text
      _handle_click:function(){
         var oThread = this.getPacket().data;
         if(!oThread) return;
         if(oThread.getIsRunning()) oThread.stop(1);
         else oThread.run(oThread.getLastInterval(),1);
         this.paint();
      },
      // When the thread is available, paint the button text
      _handle_slidethreadready:function(s,p){
         this.paint();	
      },
      // Set the button text based on the thread state
      paint:function(){
         var oThread = this.getPacket().data;
         if(!oThread) return;
			
         if(oThread.getIsRunning()) this.getContainer().value = " Stop ";
         else this.getContainer().value = " Start ";
      }
    ]]>
  </application-component>
  <application-component id ="slide-show-prev">
    <![CDATA[
      _handle_click:function(){
        // Serve the TransactionPacket back as prev
        // Which will invoke _handle_prev on any component in this transaction
        this.serveTransaction('prev');
      }
    ]]>
  </application-component>
  <application-component id ="slide-show-next">
    <![CDATA[
        // Serve the TransactionPacket back as next
        // Which will invoke _handle_next on any component in this transaction
      _handle_click:function(){
        this.serveTransaction('next');
      }
    ]]>
  </application-component>

Part 1 Demo

What follows should be a live demo of the code outlined in Part 1. The slideshow interval is 5 seconds.

Part 1 Conclusion

No matter the DHTML widget, some degree of HTML, CSS, and scripting is going to come into play. However, having to declare script to bind HTML nodes to other script, no matter how defined, is not desirable. In Part 2, the ApplicationComponents created in Part 1 will be used from within an Engine to automatically make these associations.

Part 2

Part 2 refines Part 1 to circumvent having to add script to link the HTML with the widget components.

The following HTML is used to define an Engine on a Web page that will load the SlideShow configuration from the SlideShowConfig.xml file. Note that the default contents are the downlevel contents.

<div
   is-engine = "1"
   engine-action = "/projects/engine/demonstrations/demonstration_15/SlideShowConfig.xml"
   engine-action-type = "xml"
   engine-config-task = "engine_loader"
   engine-config = "SlideShow"
>
   <img
      src = "/reference/2005/10/10/single_interaction_report_1.jpg"
      width = "300"
      height = "300"
   />
</div>

In SlideShowConfig.xml, the SlideShow configuration is very similar to the HTML used in Part 1. The primary difference is that the ID attribute is gone, and the ACID attribute is included. The ACID attribute is used to instrument the resulting HTML node with the corresponding ApplicationComponent.

<configuration id = "SlideShow">
   <p>
      <input type = "button" value = " < " acid = "slide-show-prev" />
      <input type = "button" value = " Stop " acid = "slide-show-startstop" />
      <input type = "button" value = " > " acid = "slide-show-next" />
   </p>
   <p>
      <img
         src = "/reference/2005/10/10/single_interaction_report_1.jpg"
         base = "/reference/2005/10/10/"
         list = "single_interaction_report_1.jpg;single_interaction_report_2.jpg" 
         delay="5000"
         acid = "slide-show-image"
         width = "300"
         height = "300"
      />
   </p>
</configuration>

There are several ways to load the application_components.xml file, including using a task list to load additional engine configuration data. In this example, the file is defined within the SlideShowConfig.xml file where the HTML nodes are mapped to XHTMLComponents. The XHTMLComponent class resolves the ACID attribute and binds the HTML Node to the component.

Part 2 Demo

What follows should be a live demo of the code outlined in Part 2. The slideshow interval is 5 seconds.

Downlevel Version