Engine for Web Applications Configuration

Author: Stephen W. Cote

Introduction

Engine for Web Applications is a robust client-side framework. Where the API documentation addresses the available objects and members, this overview describes the XML configuration formats. There are five XML formats, four of which are more or less two-dimensional. There isn't really much to say about #1, #2, and #3.

[ top ]

Configuration

The ConfigReader structure is as follows:

<root>
   <node>
      <param name = "test" value = "test" />
   </node>
</root>

[ top ]

Validation

Validation patterns structure is a flat list and used by the XHTMLValidator utility.

[ top ]

Application Components

The Application Component structure is also a flat list, and has the following syntax:

<application-components>
   <application-component id = "{app-id}" participant-id = "{participant-id}">
      <[CDATA[
         component_init:function(){
            // startup code
         },
         component_destroy:function(){
            // shutdown code
         }
      ]]>
   </application-component>
</application-components>

The only child nodes are CDATA sections, and the format of the CDATA section are hash structures. The API used within this script is defined in the Application Component API document, and can also be found throughout the examples. The most commonly used methods are ones such as this.getContainer() to return the host object (ie: an HTML object associated with the component), and component_init function which is invoked when the component is loaded.

Demonstrations #10 and demonstration #15 delve into how components can be have a transactional relationship; ie: how they can intercommunicate without being specifically aware that the other exists.

[ top ]

Tasks

Tasks have three dimensions and have the following structure:

<tasks>
   <task id = "{task-id}">
      <task rid = "{reference-id}" auto-execute = "{1|0}" />
      <depends rid = "{name}" />
   </task>
</tasks>

The task child node uses the id, action, action-type, handler, and handler-type attributes to identify the type of actions to perform. Possible action/handler types are: xml, task, import-task, function, script, and event.

  • xml - loads the specified xml document
  • task - executes the specified task
  • import-task - loads the specified task file
  • function - executes the specified function
  • script - evaluates any script defined in the handler, or if the handler is "#cdata", in an child CDATA block.
  • event - publishes the specified psuedo event through the message service.

Grandchild task nodes define a rid attribute that refers to a task id attribute, creating a dependency, as well as an optional auto-execute="1" attribute used to automatically cause the dependent task to be executed if it hasn't been executed yet.

Grandchild depends nodes define a rid attribute tha refers to a task id, or to any type of id. This allows you to create an unspecified dependency name that is returned at some later time, using the returnDependency method.

You can see an example of a task file at tasks.xml. And you can see a comparison between the XML file and how the same would be done in script here: Tasks background. In most of the demonstrations that use the tasks file, you'll notice most of the files do the same thing: load some external XML files asynchronously, and then raise a callback when finished.

[ top ]

Engine

Engine configuration is by far the most complex, but is also largely generic so the complexity comes from how much a developer decides to abstract within the configuration. The configuration is divided up into two parts: attributes specified on a <div /> element for loading the engine, and the XML configuration.

The attributes are:

  • is-engine : Attribute instruction used to identify this element as being an engine element. Note: it is possible to load an engine without using an HTML element
  • engine-id : Friendly id that is exposed through the engine API to obtain a pointer to the engine object and through the engine_element property, the HTML element.
  • engine-action : An arbitrary value whose purpose is determined by the action-type.
  • engine-action-type :A preset execution type defined by the Task Service. Available types are xml, task, import-task, function, event, and script.
  • engine-handler : Arbitrary response whose values is determined by the handler-type.
  • engine-handler-type: a preset execution type.
  • engine-onload : A function or script to evaluate when the engine has been loaded.
  • engine-config-task : The name of the internal task used to load the engine configuration. A built-in task is included named "engine_loader" that can be used to avoid using a task list and go straight to the engine config file.
  • engine-config: The name of the engine configuration to process for this engine object. When a configuration is loaded for a specific engine, the onengineconfigload message (not event) is published. To make use of the message, you must subscribe to it and specify a handler.

There are three typical configuration methods:

  1. using the engine XML configuration to import content,
  2. using the child HTML as the content and using the engine XML configuration to process that content, and,
  3. using a task list to load the engine XML configuration plus any dependencies (ie: the ValidationPatterns file, application level configuration, etc). You can see each of these examples in Demonstrations #1 (no tasks), Demonstrations #13 (self config), and Demonstrations #2 (tasks).

An example engine declaration like the one used in Demonstrations #1 is:

<div
   is-engine = "1"
   engine-action = "/path/to/engine.config.xml"
   engine-action-type = "xml"
   engine-config-task = "engine_loader"
   engine-config = "ConfigurationIdToLoad"
>
   <p>Downlevel Version</p>
   <p>This content is erased when engine loads</p>
</div>
 

This example can be described as follows:

  • It's a normal HTML DIV element with some extra attributes.
  • When the engcomp.js file is loaded, and when window.onload fires, the EngineService looks for DIV elements with the is-engine attribute.
  • The engine-action and engine-action-type are translated internally into tasks. In this case, the action is to load the xml file /path/to/engine.config.xml No handler is needed in this case and the engine service takes care of the asychronous request.
  • The engine-config-task attribute specifies the name of the task used to handle loading this engine. "engine_loader" is a built-in task responsible for loading engine configuration, so, in this case, engine-action becomes the central config file for the Engine service. Successive requests for this file wind up being buffered, so you can have many duplicate objects on a page without worrying about having to load the file multiple times.
  • The engine-config attribute specifies an id of a element in the engine XML file (see below for description). This instruction means that the contents of the specified element are processed through the engine service, and those outputs are copied into the engine object.

Alternately, and example of using a self configuration like the one used in Demonstrations #13 is:

<div
   is-engine = "1"
   engine-action = "/path/to/engine.config.xml"
   engine-action-type = "xml"
   engine-config-task = "engine_loader"
   engine-config = "self"
   class = "engine_demonstration"
>
   <h2>inline engine configuration</h2>
   <p>This is a test</p>
   <import-xml
      id = "import-demoxml-1"
      src = "/path/to/import.xhtml.xml"
   />
</div>

The difference in this example is that instead of copying any outputs from a <configuration /> section, the existing child nodes are the inputs/outputs, and only subordinate actions wind up being processed. This begs a more detailed description of what engine is doing, and how the configuration file is layed out.

More than anything else, the Engine service is meant to abstract content from functionality. You can read more about the motivation behind this in the following article I wrote: http://www.imnmotion.com/servlet/connector?file=documents/html/technical/design/functionalityFromContent.html Since this behavior or degree of abstraction is not always desirable, the engine service is strictly optional.

The Engine service works by using content as its own configuration. Raw XHTML (because it has to be valid XML in the XML file) becomes its own programmatic description language. Optional content attributes include "acid", which provides auto-binding to Application Components, and "rid", which provides friendly contextual identifiers. The process drills through the configuration XML, or if "self" configuration is used, the XHTML DOM of the child nodes, and seeks to match elements with object definitions. If an element is not found, it is considered abstract HTML and all child nodes are treated as abstract HTML. On the other hand, if an element is found, it can be declared abstract so it is processed but not copied though children are in this case processed; it can switch contexts to another node so it is not copied but the other node is then copied and/or also abstracted; it can cause a JavaScript function or object constructor to be fired where in that constructor attributes and context information can be referenced using the ora-parameters. The contextual shifting may sound bizarre at the moment, but I'll address that in a minute. First, here is the engine configuration file structure, where {} values are used to identify the expected data types or values; note the {}'s shouldn't be used:

<engine-configurations>
   <configuration id = "{config-id}">
   </configuration>
   <object-definitions>
      <definition id = "{node-name}">
         <matdef rid = "{node-name}" />
         <implementation use-parent = "{1|0}"
            context-switch = "{1|0}" context-path = "{/xpath}"
            abstract = "{0|1}"
         >
            <package pid = "{object.namespace.reference}" />
            <constructor name="{constructor-name}">
               <param value = "{value|ora-value}/>
            </constructor>
         </implementation>
      </definition>
   </object-definitions>
<engine-configurations>

Now, consider the following use-case:

Suppose someone wants to load the contents of an external XML file into a Web page. They could use script to load the XML, then use innerHTML to copy the XML text into the page, or traverse the XML DOM and recreate each node. Or, consider the previous HTML example with the "import-xml" node. You might be wondering who defines that node and where. The answer is it is an Engine XML configuration - a custom element that matches an object definition, where that definition describes how to process the content, and in this case, how to load the external XML. Either this person, or someone else, could create the object definition, and then the only thing this person needs to do to perform all the example steps - load the xml and copy it into the Web page - is to use the custom element. And, because Engine starts out in a downlevel mode, browsers that do not support this action would have some default content to fall back on.

Except in the case where "self" is used, each <configuration /> section corresponds to the body of an Engine. As mentioned above, the contents of a "self" or <configuration /> node are processed by comparing each node against an object definition.

The object-definition id attribute should map to an XML/XHTML node that is expected to be processed. Additional nodes that should also be matched by the definition can be specified using the <matdef /> nodes. An XML/XHTML node can be processed by only one object definition.

The <implementation /> node describes how the matching definition should be processed. First and foremost it is important to understand the context. The node context belongs to the parent, and this means in the "self" case the parent is an HTML node belonging to the browser document, in the engine XML the context belongs to an external XML document. When loading another external XML file as content to be processed, it is necessary to switch the context over to that document so that the children will be found. Otherwise, the context remains on the current node which most likely has no childern.

The <implementation /> includes an optional <package /> node that describes the namespace, and an optional <constructor> node that describes the function to invoke. The <constructor /> node may include optional <param /> nodes.

Consider the import-xml example again, which uses an "id' and "src" attribute. In order for the "import-xml" node to do anything, it must match an "object-definition". Within the <object-definition /> for "import-xml", the <implementation /> node doesn't write the code for you, but it allows you to associate an XML/XHTML node with the code. The engine library includes an xml utility to load an xml document, accessed by the method org.cote.js.xml.getXml. Another way to look at this is the package/namespace is "org.cote.js.xml" and the method name is "getXml". The getXml method has five possible arguments: path, function handler, load_asynchronously, identifier, and a should_cache bit. In order to map the "id" and "src" attribute values from the custom "import-xml" element to the function call, specialized parameters known as ora-params are used. The Engine service supports ora-params as a convenience so that you can specify primitive datatypes, contextual attribute values, object pointers, etc. Common ora-params (there are a couple more complex ones) are:

  • ora:{n}_attr : uses attribute value {n} from the context node.
  • ora:integer_{d} : uses intereger value {d}.
  • ora:node_context : the input XML/XHTML context node.
  • ora:element_context : the output XML/XHTML element node.
  • ora:parent_reference : the parent of the XML/XHTML context node.
  • ora:xml_document : the xml document to which the input context node belongs.
  • ora:engine_object : the script object representing the Engine.
  • ora:engine_element : the element for which the Engine was created.
  • ora:engine_id : the identifier used to register the Engine with the ObjectRegistry.

The objective of the import-xml example is to load an external XML file and copy its contents into the engine. The Engine service assumes that any non-abstract content will be copied, so the "import-xml" implementation needs to change the context from itself to the output of the implementation. Also, if we don't want to include the root node of the imported data (in this example, "import-data"), the import-data node is declared abstract. It isn't copied, but children nodes are processed and copied.

The object definiton for "import-xml" is as follows:

<object-definitions>
   <definition id="import-data">
      <implementation abstract="1"/>
   </definition>
   <definition id="import-xml">
      <implementation context-switch="1" context-path="/import-data">
         <package pid="org.cote.js.xml"/>
         <constructor name="getXml">
            <param value="ora:data_source"/>
            <param value="ora:integer_0"/>
            <param value="ora:integer_0"/>
            <param value="ora:id_attr"/>
            <param value="ora:integer_1"/>
         </constructor>
      </implementation>
   </definition>
</object-definitions>

In the previous example definition, the implementation for <import-xml src = "/path/to/file.xml" id = "test-xml" /> gets translated internally into:

org.cote.js.xml.getXml("/path/to/file.xml",0, 0, "test-xml", 1)

If you check the API docs, what this says is to load the specified XML file sychronously - async won't work in this context because we need the result right away - don't use a handler, give the xml request the id "test-xml", and cache the result so that any other requests using the same id will return the cached value.

By including this in an engine configuration file, you can then use <import-xml id = "xxx" src = "yyy.xml" /> references to load any external XML content.

Another example to help describe the engine configuration is associating XHTML nodes with Application Components. This is done by using the org.cote.js.xhtml.XHTMLComponent to make registered Engine framework objects out of XHTML nodes, and the XHTMLComponent class will automatically load any Application Component defined using an "acid" attribute. In this case, an object definition could be written to match one or more XHTML nodes and create XHTMComponent objects for them. First, here is the object-definition.

<object-definitions>
   <definition id="div">
      <matdef rid="input"/>
      <implementation>
            <package pid="org.cote.js.xhtml.XHTMLComponent"/>
            <constructor name="newInstance">
                  <param value="ora:parent_element"/>
                  <param value="ora:node_context"/>
                  <param value="ora:rid_attr"/>
                  <param value="ora:engine_id"/>
                  <param value="org.cote.js.xhtml.form.XHTMLFormComponent"/>
                  <param value="ora:integer_0"/>
                  <param value="/path/to/application_components.xml"/>
                  <param value="ora:engine_config"/>
            </constructor>
      </implementation>
   </definition>
</object-definitions>

Note: in this example that both "div" and "input" elements are matched and become XHTMLComponent objects.

Note: in this example the path to the application component file is static. This could be set in an application configuration file, or specified in several other places as well.

Now, suppose someone wanted to create a button that mapped to an Application Component that described functionality for that button. Here is how this could be done using the above engine object definition, and the following engine HTML:

<div
   is-engine = "1"
   engine-action = "/path/to/engine.config.xml"
   engine-action-type = "xml"
   engine-config-task = "engine_loader"
   engine-config = "self"
   class = "engine_demonstration"
>
   <h2>inline engine configuration</h2>
   <div>
      <input type = "button" value = "Test" acid = "myComponent" />
   </div>
</div>

And, in the application_components.xml file:

<application-components>
   <application-component id = "myComponent"><![CDATA[
      component_init : function(){
         // fired when component is created, let's change something 
         //
         this.getContainer().value = "AppComp Button";
      },
      // The click event is hooked up automatically simply be creating a function for it
     _handle_click : function(){
         // do something when the button is clicked
         alert('Click!');
      }
 
   ]]>
   </application-component>
</application-components>

[ top ]

Conclusion

In conclusion, this overview touched on config, task, validationpattern, engine, and application component configuration formats. The engine configuration format is mostly described here, and any complexity comes from the types of associations a developer elects to make. None need be made at all, or sometimes just a few will suffice.

[ top ]