Understanding XForms

Viewing and creating "Adventure" scenarios

With XML-based technologies such as XForms, XQuery, and XSLT, it is possible to create complex multi-user applications, from interactive help systems to custom "game" applications in which multiple users can interact with at once. This article shows how to create you fairly simple scenario games.

Kurt A. Cagle (kurt.cagle@gmail.com), Chief Architect, Metaphorical Web

Kurt Cagle is an author and systems architect concentrating on XML- and Web 2.0-related technologies. He is also the Webmaster for The XForms Portal (http://news.xforms.org), a news portal and code resource base for XForms-related code, and The XForms Community Forum (http://portal.xforms.org), a forum for exploring questions about XForms technologies.



05 June 2007

All right, I'll admit it ... I started my programming career back in the mid-1970s for a very simple reason -- I was tired of thumbing through Dungeons and Dragons books trying to find the right table for determining whether the 14 I had rolled on a twenty-sided die was sufficient for my goblin (I was the dungeon master, usually) to take out some player's knight. Quick show of hands -- who among you also honed your programmatic skills on trying to determine whether Finneas the Fingers was able to pickpocket Odo the Officious Official? Come on, I know you're out there!

Seriously (well, somewhat more seriously, anyway), gaming and programming have long been linked together, from the first games of Dungeon and Star Trek through to the present 3D interactive "worlds" (not to mention the curvacious Beziers of Ms. Lara Croft).

I don't think this is at all accidental; both are ultimately involved in building models of the world (or at least a world), and it is consequently not that surprising that role playing games in particular should end up becoming one of the most significant areas of computational entertainment on the planet.

I wanted to launch this series on XForms application developments by focusing on these role-playing games, working my way up from fairly simple scenario games (the focus of this article) to more complex simulations that not only showcase the inner workings of XForms, but also hopefully provide the basis of other applications, especially in the arenas of testing and teaching.

I also wanted to showcase some of my favorite implementations in this space: the eXist XML database, the Saxon 8.9 processor (for XSLT2 support), and the Mozilla XForms plugin for Firefox. It should be possible to adapt this same technology for use with other XML databases and XForms implementations (and some suggestions to this effect will be given later in the article), but these tools provide a quick way to experiment with these technologies and to work with XML bindings.

Setting up the system

The eXist server (http://exist.sourceforge.net) is a Java™-based XML database that runs as a servlet within Jetty. Installation is straightforward: download the eXist 1.1.1 .jar file (eXist-1.1.1-newcore.jar) and run the .jar file using the statement java -jar exist-1.1.1-newcore.jar in order to launch the installation dialog. This will automatically install the eXist database on port 8080.

As an alternative, if you already have a servlet engine active (Jetty or Tomcat), you can also install eXist as a WAR resource by downloading eXist-1.1.1-newcore.war, then using your Servlet engine to upload the WAR into the system. In this case, eXist will run under the same socket port as your servlet engine. This is especially useful for running eXist in conjunction with other servers.

Please note: For purposes of this article, I am assuming that eXist will be installed in its default mode, using socket 8080.

Installing the Mozilla XForms extension is similarly straightforward. From within Mozilla Firefox (2.0 or above), navigate to the Mozilla XForms Extension and click the Install Now for Windows/Linux/Macintosh button (depending upon the flavor of your operating system), then click the Install Now button on the Addons dialog. You will then need to restart Firefox, which can be done from the Restart Firefox button in the Addons dialog.


Modeling the scenario

One critical difference between imperative and declarative programming methodologies is the notion of completeness. In most procedural languages, it is perfectly permissible to start writing code without having anything designed up front, as the code can work on snippets and pieces and then get factored together over time with other code.

With XML in general, and XForms in particular, you have to have at least a minimal data model in place before you can see anything working, but once the model exists, then the presentation of that model (the form elements) usually progresses very quickly. As such, XForms tends to enforce a more disciplined methodology of programming where you need to spend more time up front designing in order to gain faster development efforts in the back end.

The data model in this case is what I'm calling a "scenario," which can be thought of as a collection of scenes, connected to one another through links, as shown in Figure 1.

Figure 1. Structure of a scenario
Structure of a scenario

The scenario head contains the kind of metadata that you would expect of an RSS feed: title, subtitle, author, etc. (indeed, it should be possible to use Atom or RSS2 as the "substrate" for the model, though in the case of this article I won't do that, for clarity). Additionally, the startScene property contains the reference ID of the first scene to be displayed (with an implicit assumption that if no such property is included, then the first scene listed in the scenario is the starting scene, which will usually be the case).

Each scene in turn has a unique ID within the document. When I was first designing the model, the thought of using the position in the document was tempting, but such positional offsets can get corrupted easily if a new scene is inserted within the body of the document, and by using IDs, it also opens up the possibility of referencing scenes from external documents, as well.

The primary "descriptive" content of a scene is divided into four sections, as shown in Listing 1. Please note that you will have to open XML file theProject which you can download at the end of this article to view the links contained in Listing 1.

Listing 1. A typical scene
<scene id="sc7">
    <title>A Less Than Sterling Demo</title>
    <content>
        <p>A couple of weeks pass, and you put together an initial demo - 
        at which point it becomes pretty obvious that there's some fairly 
        major holes that weren't specifically covered by your initial assignments. 
        Your manager isn't happy that time is already slipping away and 
        there's very little to show for your efforts, and your development team is 
        beginning to become frustrated 
        because there's no real coordination going on.</p>
        <p>Do you ...</p>
    </content>
    <links>
        <link ref="sc4">Set up an architectural review and reassign the tasks 
        to insure that the core works gets done?</link>
        <link ref="sc8">Assess your best programmers and assign them 
        the additional tasks, knowing that they should be able to handle the 
        additional load (especially if you pull a 
        few all nighters)?</link>
        <link ref="sc8" minvalue="-3" maxvalue="0">Go to your manager 
        and tell her that there were some unexpected delays in the coding, 
        due to the slackness of your developers, and that if you could have 
        a little more time, you'd have an incredible demo to show them?</link>
        <link ref="sc10" minvalue="1" maxvalue="3">Go to your manager and 
        tell her that there were some unexpected delays in the coding, 
        due to your own learning curve and that if you could have a little 
        more time, you'd have an incredible demo to show them?</link>
    </links>
    <score action="add" value="-1"/>
</scene>

The first, the <title> block, is a title for the section that is used primarily for editing, and may actually be turned off in an adventure viewer.

The second block, the <content> element, holds a collection of <p> (paragraph) elements. It is possible to bypass this approach and go with a more generalized "rich text" component, but this becomes dependent upon the specific XForms implementation and will be covered more in the next article. This content is the meat of the scene; it establishes the particular scene, from a monster's den to a boss's office (which may in fact look remarkably similar).

The mechanism that connects one scene together is, not surpringly, a <link> tag, held together in the <links> collection as the third block in a scene. The link tag's inline content in this initial case is a simple text string that describes a given action to be taken, while the <link>'s ref attribute is the ID of the next scene in the scenario to go to if the action is selected. You can have an unlimited number of links, but there will always be at least one link in a given scene.

In the case where you're at an end-point (you've found the treasure, been eaten by the monster, or been fired by the boss), there will still be a link. However in this case, the link's ref attribute will be set to the value "null," and the link's text content will be hidden unless a flag to show null-link content is set to true. This can actually be useful for setting up scenarios where you know the link text, but don't have all of the scenes defined yet as a debugging tool.

Finally, associated with each scene is a <score> element, with an associated @value attribute. This score element is useful because it provides one way of determining how "well" you did in the scenario, but more importantly, it also displays how to introduce a mechanism for handling conditional expressions (through the minvalue and maxvalue attributes) , so that your progress through the maze can vary depending upon some external condition.

Each <score> element has an associated action attribute, which, in this particular case, can take on the values "set" or "add", along with a corresponding value attribute. When a given scene is reached, the action determines whether the score is set to this value or the value is added to the score. Obviously, you can set more complex operations this way as well, depending upon what your XForms looks like.

The score is then used by links to help determine action. For instance, in the third and fourth links of Listing 1, you actually have two links with nearly identical text, differentiated primarily because the first as minvalue and maxvalue attributes of -3 and 0, respectively, while the second link has minvalue and maxvalue attributes of 1 and 3. What this means is that if the score is between -3 and 0, then only the first link will be displayed, while if it is between 1 and 3, then only the second link will be shown. If the ranges overlapped, then both links would be shown at the overlap points.

The interpretation of this is then pretty straightforward. In the given scenario, negative values are given for bad actions, while positive values are given for good ones. If you ended up in the "bad demo" stage through taking continued bad actions, then you're not likely to manage to finesse the final demo, but if you did mostly the right things and just had one or two minor lapses, then the manager is likely to be more forgiving and you have a better chance of correcting mistakes. Thus the simulation becomes "aware" of how well you're doing and offers you alternatives based upon that.

This is, admittedly, a very crude filtering mechanism, and any real scene engine would likely have a range of variables that would affect the direction of a person through the scenario. This will be discussed in greater detail in the next article.


XForms as bindings

Designing a good model is a major part of putting together declarative applications, but it is not the only part. An XML document is just so much markup unless there is some way of providing a "view" of the data that turns the XML into the engine for running the application. This is where XForms comes in.

To a certain extent, this is what a browser does for certain XML (and XML-like) formats, such as HTML or SVG. In essence, the browser interprets each element and attribute (and in many cases aggregates of elements) as an instruction to render some output or perform some action. It is possible, with XSLT transformations or CSS (or both) to take XML and transform it back into HTML or SVG, but in the end the rendering is still a static HTML or SVG page, and even adding JavaScript code doesn't really change the fact that you're still dealing with content that has had a signficant amount of useful information transformed out of it.

XForms changes that relationship. In essence, with XForms you are creating a specialized "interpreter" that will process your XML data content in the same way that a browser processes HTML. For instance, in this game application, the scenario document is an XML "language" that's just as valid as HTML. Normally, however, the only real path to doing something useful with that scenario content is to write a lot of scripting code, and the more complex the code, the more work that needs to be done handling the port to differing platforms.

With XForms, on the other hand, the XML instance is expressed visually (and in other ways) through the various XForms components that are directly bound to the data model; you change the data model, you end up changing the presentation layer, and vice versa. This is the document object model principle in a nutshell, and it is in fact one of the most useful aspects of DOM

Thus, as you look at this application, think less of the XForms as being a way to "build forms" and more as a way to create "mini-browsers" capable of viewing and manipulating custom XML formats without having to wait years (if ever) for someone to create a special viewer for scenarios or any other business objects. As the number of XML standards (and formats) explode, the proposition of building "one-off" viewers for each of them becomes daunting in the extreme, which is why the declarative binding approach offered by XForms becomes such a viable technology to consider.

A screenshot of one scene in the scenario for this article can be seen in Figure 2.

Figure 2. Screenshot of a sample scene
Screenshot of a sample scene

Building the scenario viewer

Building the viewer involves what is a fairly classic division common to most XForms -- an XForms model that is split into the instance data, which is the scenario in question, and local state data. This distinction may seem odd, but it's actually quite useful; there is often information in an XForm (just as there is in most imperative applications) that are relevant to the running of the application but which are not necessarily germane to the data model itself, and as such really shouldn't be kept with the instance data. The specific data model is shown in Listing 2.

Listing 2. The data model declaration
<xf:model id="scenario_model">
    <!-- The data instance contains the relevant scenario -->
    <xf:instance id="data" src="theProject.xml"/>
    <!-- The localState instance contains structure that 
            holds the local state variables -->
    <xf:instance id="localState">
        <state>
            <activeScene/>
            <activeLink/>
            <previousScene/>
            <nextScene/>
            <count>0</count>
            <displayControls>false</displayControls>
            <displayNullLinks>false</displayNullLinks>
            <score>0</score>
            <sequence>
                <scene ref="" title=""/>
            </sequence>
        </state>
    </xf:instance>
    <!-- multiple xf:bindings and actions... covered later -->
</xf:model>

The "data" instance just contains a pointer to the scenario XML in question (in this particular case a scenario called "theProject.xml," one scene of which was discussed previously in this article). Because this is also the first instance so given in the first data model, it is possible to refer to this particular instance without qualifying it with an instance('data')/ in ref or nodeset attributes.

The second instance, "localState", not surprisingly contains the local state information. Developing this particular state description is often a matter of trial and error, in much the same way that you build private variables into objects as you're coding then periodically refactor the code to eliminate redundant variables. In most cases, the activeScene, activeLink, and related variables point to the IDs of the various scenes and associated links in the scenario itself.

Some variables, such as <displayControls> and <displayNullLinks> serve to indicate to the application whether given interface elements. In the first case, <displayControls> hides or displays previous/next buttons that permit walkthrough of the scenes in order rather than by link, while <displayNullLinks> is used to indicate whether null links should be displayed. These properties in turn may be controlled by buttons or toggles themselves.

Meanwhile, the <score> element is used to provide the "storage register" for the accumulated score as the person traverses the document, while the sequence contains a running record of the scenes that a person traverses over the course of the game, using the initial node as the template for generating subsequent ones.

It can be argued with some validity that this state information should legitimately be contained within the data instance, but I also believe that there are some significant benefits for creating this as a separate entity.

For starters, the second instance contains just enough information to recreate the entire traversal of the scenario graph without having to actually contain the graph's text and descriptions itself. This makes processing much more efficient. Considering that quite often to both human and automated evaluators of such scenarios the path taken is frequently far more important than where the scenario takers end up, this information can in turn be used for everything from performing psychological evaluations to generating software.

All XForms contexts (those things in ref and nodeset attributes) are XPath expressions. However, because it is possible for there to be multiple models and model instances, XForms also provides the instance() extension for XForms XPath. This returns the topmost element node of the given instance. Thus, instance('localState') returns the <state> node from the localState instance. Thus, to reference the <activeScene>, you would use the expression instance('localState')/activeScene.

This can be seen in the header display for the scenario (Listing 3).

Listing 3. Referencing the data instance
<h:body>
  <xf:group ref="instance('data')/head">
    <h:h1>
      <xf:output ref="title"/>
    </h:h1>
    <h:h2> By <xf:output ref="author"/>
    </h:h2>
  </xf:group>
  <!-- more follows -->
</h:body>

In this case, the group serves two purposes: it provides a generalized container for the elements within and it also sets the XPath context, which in this case is the <head> element from the data instance. The advantage to this is that by setting the context in the container, everything within the group shares that particular context, so every subsequent reference is relative to the group's context. Thus, title and author are now both provided in the context of instance('data')/head.

Things get a little more complex with the next section (Listing 4).

Listing 4. Displaying the Previous/Next buttons
<xf:group bind="showControls">
    <xf:trigger bind="previousScene">
        <xf:label>&lt;</xf:label>
        <xf:dispatch 
             name="go-previous-scene" 
             target="scenario_model" 
             ev:event="DOMActivate"/> 
    </xf:trigger>
    <xf:output ref="instance('localState')//activeScene"/>
    <xf:trigger bind="nextScene">
        <xf:label>&gt;</xf:label>
        <xf:dispatch 
            name="go-next-scene" 
            target="scenario_model" 
            ev:event="DOMActivate"/> 
    </xf:trigger>
</xf:group>

This section of code provides two buttons, a Previous and a Next button, indicated with "<" and ">" respectively, that lets someone walk through the scenarios in the order that they appear in the scenario document. This is normally used solely as a debugging tool, though it can be used for creating a "slideshow"-type effect, as well.

One of the more difficult concepts to wrap your head around with XForms is the bind attribute, which is simultaneously one of the most powerful features of XForms and easily one of the more confusing. It's easy to think of bind as being a way to define a variable, but that's not really what it does (and one of the points where you can get confused).

Rather, a binding identifies a node or a set of nodes, then places specific restrictions on those nodes, which in turn help to define the node's behavior. For instance, consider the bind associated with the <xf:group> element in Listing 5. This bind reference points to an <xf:bind> element in the model:

Listing 5. The bind element
<xf:bind nodeset="instance('localState')//displayControls"
    id="showControls" relevant=".='true'"/>

This statement creates the "showControls" binding, and associates the binding with the instance('localState')//displayControls element in the data model. When any change in the model affects the elements defined in the binding, then the binding itself comes into play. In the case of showControls, the relevant attribute is the relevant attribute; whenever the context defined in the nodeset (the <displayControls>element's text value) is equal to the string value "true" then the relevant property of the binding is set to true and any controls that reference this binding are displayed.

If, on the other hand, the value is set to "false", then the relevant property is no longer true, which is the signal to the XForms processor to hide the component. This is a good way to control the visibility of a given set of items on an XForms, without resorting to CSS declarations.

The bindings on the Previous and Next buttons are similar; though rather than checking for a true or false value, they check to see if there is any non-empty content in the respective elements. See Listings 6 and 7, below.

Listing 6. The previousScene binding
<xf:bind id="previousScene" 
  nodeset="instance('localState')//previousScene"
  calculate="instance('data')//scene[@id=instance('localState')//
        activeScene]/preceding-sibling::*[1]/@id"
  relevant=". != ''"/>
Listing 7. The nextScene binding
        <!-- The nextScene binding determines the next scene id from the current one -->
        <xf:bind id="nextScene" nodeset="instance('localState')//nextScene"
          calculate="instance('data')//scene[@id=instance('localState')//
             activeScene]/following-sibling::*[1]/@id"
          relevant=". != ''"/>

Additionally, each of these two bindings have an additional calculation (an ugly one, admittedly) that recalculates the previous and next scene ID references every time that the activeScene property changes, in essence by finding the previous or next scene to the scene specified by the activeScene property.

This showcases a second important aspect of bindings. It is possible for a binding to have more than one associated attribute, as they tend to handle orthogonal activities. Calculating a value for a node is somewhat different from determining whether that node is relevant to the overall model, as is determining whether a given node is schematically valid.

In addition to this is again a reminder of the fundamental distinction between an XForms document and an HTML/JavaScript form. In the former case the controls are, for the most part, simply reflections of the data model: if you change a value in the control, the data model changes, and that change gets reflected in other controls if they are bound to the parts of the model that change. An HTML/JavaScript form, on the other hand, usually tends to have one control directly changing the state of other controls, often leading to a great deal of fragility in the final page.

The trigger, once pressed, dispatches a custom event. Custom events are again a feature of XForms that take a while to fully appreciate. In many ways, they serve as the equivalent of declarative functions that are invoked from inXXX scripts on elements. For instance, for the Next Button, the trigger code

Listing 8. Invoking an event dispatch from a trigger
<xf:trigger bind="nextScene">
    <xf:label>&gt;</xf:label>
    <xf:dispatch 
        name="go-next-scene" 
        target="scenario_model" 
        ev:event="DOMActivate"/> 
</xf:trigger>

responds to the DOMActivate event (which is more or less the same as a generalized onclick event by launching another event -- go-next-scene -- to the scenario model. Beyond having the definitions and bindings, the model in question also has <xf:action> elements with their ev:event attributes set up to receive this dispatched events:

Listing 9. The go-next-scene event handler
          <xf:action ev:event="go-next-scene">
  <xf:setvalue ref="instance('localState')//activeScene"
    value="instance('localState')//nextScene"/>
</xf:action>

Once this event is received, the action sets the local-state's <activeScene> element to the value contained in <nextScene>. In this particular case, the <xf:action> element is a little redundant -- the receiving action could have been placed directly on the <xf:setvalue> tag, but the former approach makes it more obvious that an event handler is being defined.

A similar action exists to handle initializing the activeScene in the first place (Listing 10):

Listing 10. The "initialization code" using the xforms-model-construct-done handler
<xf:action ev:event="xforms-model-construct-done">
    <xf:setvalue 
      ref="instance('localState')//activeScene" 
          value="if (instance('data')/head/startScene != '',
            instance('data')/head/startScene,instance('data')//scene[1]/@id)"/>
      <xf:setvalue ref="instance('localState')//sequence/scene/@ref" 
          value="instance('localState')//activeScene"/>
      <xf:setvalue ref="instance('localState')//sequence/scene/@title" 
      value="instance('data')//scene[@id=instance('localState')/activeScene]/title"/>
</xf:action>

Unlike the go-next-sceen event, xforms-model-construct-done is automatically launched by the XForms model every time a new model is constructed. This makes it particularly useful for handling initialization code, especially when you periodically end up reloading the mode.

While the <xf:setvalue> statements in the action are reasonably complex, the first one is worth noting for its use of the if() statement. Most XForms implementations (and the formal XForms specification) use the XPath 1.0 implementation, which doesn't contain a clean way of evaluating conditional expressions that aren't specifically tied into node-sets. However, certain calculations are impossible without them.

As a consequence, the XForms 1.0 spec defines the if() function that takes three parameters: a conditional expression, an XPath expression if the condition evaluates to true, and a second if the expression evaluates to false. Certain implementations, such as Orbeon, support XPath 2.0 and would consequently offer the if/then/else form of the statement, but until XPath 2.0 becomes a standard part of the specification (and the day won't come soon enough), you need to use the XForms extension function instead.

The second and third <xf:setaction> statements also initialize the first local-state's <scene> element within the <sequence> element. This is used to keep track of which scenes have been walked through over the course of the scenario.

The final block of code occurs within the context of the active scene as shown in Listing 11.

Listing 11. The individual scene display
      <xf:group ref="instance('data')//scene[@id = instance('localState')//activeScene]">
        <h:div>
          <h:h3>
            <xf:output ref="title"/>
          </h:h3>
          <xf:repeat nodeset="content/p">
            <h:p>
              <xf:output ref="."/>
            </h:p>
          </xf:repeat>
        </h:div>
        <h:ol>
          <xf:repeat nodeset="links/link" id="links">
            <xf:group ref="self::node()[((not(@minvalue) and not(@maxvalue)) or 
                ((instance('localState')//score &gt;= @minvalue) 
                and (instance('localState')//score &lt;= @maxvalue))) and 
                (@ref!='null')]">
              <h:li>
                <xf:trigger ref="." appearance="minimal">
                  <xf:label ref="."/>
                  <xf:dispatch name="link-selected" 
                      target="scenario_model" 
                      ev:event="DOMActivate"/>
                </xf:trigger>
              </h:li>
            </xf:group>
          </xf:repeat>
          <xf:repeat nodeset="links/link[@ref='null' and 
              instance('localState')//displayNullLinks='true']">
            <h:li class="nullLink">
              <xf:output value="if (string(.)!='',.,'Null Link')"/>
            </h:li>
          </xf:repeat>
        </h:ol>
        <xf:output ref="instance('localState')//score">
          <xf:label>Current Score =</xf:label>
        </xf:output>
        <h:h3>History</h:h3>
        <h:ol>
          <xf:repeat nodeset="instance('localState')/sequence/scene" id="visited">
            <h:li>
              <xf:output ref="@ref"/>:<xf:output ref="@title"/>
            </h:li>
          </xf:repeat>
        </h:ol>
      </xf:group>

The initial group "de-references" the activeScene reference from the local state and returns the actual XML object (the scene in question), setting it as the context. (Note: In XForms 1.1, this dereferencing could be done through the id() function, but this isn't supported in XForms 1.0.)

The first subpart just displays the title and initial paragraphs of the scene, which is fairly straightforward. Things get a little more interesting in the second part, where the links are displayed. One of the more challenging aspects of XForms 1.0 (handled in XForms 1.1) is the fact that in a given repeated collection of nodes; in order to add a new node you have to work from an existing node in the collection.

This isn't that critical a problem in the display of XForms, but the editing of such nodes in the first place becomes considerably more difficult because of this. For this reason, a scene which has no formal links always has at least one link in it, though this link has a ref value of "null". The next article will focus on an editor for creating such scenarios, and the significance of this will become more obvious at that point. For now, however, it becomes necessary to differentiate between null links and non-null links when generating output, which is why there are two distinct repeat sections.

The group within the non-null links section is itself fairly hairy, though with good reason (Listing 12).

Listing 12. The conditional display of nodes
      <xf:group ref="self::node()[(not(@minvalue) and not(@maxvalue)) or 
        ((instance('localState')//score &gt;= @minvalue) 
        and (instance('localState')//score &lt;= @maxvalue))]">
        <h:li>
          <xf:trigger ref="." appearance="minimal">
            <xf:label ref="."/>
            <xf:dispatch name="link-selected" 
              target="scenario_model" 
              ev:event="DOMActivate"/>
          </xf:trigger>
        </h:li>
      </xf:group>

This shows the score section at work. The implicit assumption on the schema is the one of two conditions will hold: either the link value has both a minvalue and maxvalue attribute, or it has neither (though a more thorough test would probably walk through all four possible states). In essence, if the score is within the range of minvalue to maxvalue inclusive, then it will be rendered. If it's not, it won't.

This is what enables the ability to have two links that point to the same target. If one covers a score over one range and the second covers a different range of scores, then if the score is in the first range but not the second, only the first link will be enabled. It is possible to have an overlap (and hence have both links shown), but at least within this fairly simplistic scoring system the specific use cases for that are limited.

The link itself is a trigger, but with the use of appearance="minimal" that link will appear as a hypertext link rather than a button. Once selected, it too dispatches a custom event, link-selected, as shown in Listing 13.

Listing 13. The link-selected event handler
            <xf:action ev:event="link-selected">
                <xf:setvalue ref="instance('localState')//activeScene" 
                      value="instance('data')//scene[@id=
                        instance('localState')//activeScene]//link[index('links')]/@ref"/>
                <xf:setvalue ref="instance('localState')//score"
                      value="if(instance('data')//scene[@id =
                      instance('localState')//activeScene]//score/@action = 'add',
                      
                      instance('localState')//score + instance('data')//scene[@id =
                      instance('localState')//activeScene]//score/@value,
                      
                      instance('data')//scene[@id =
                      instance('localState')//activeScene]//score/@value
                      )"/>
                <xf:insert nodeset="instance('localState')/sequence/scene" 
                      at="last()" position="after"/>
                <xf:setvalue ref="instance('localState')/sequence/scene[last()]/@ref" 
                      value="instance('localState')//activeScene"/>
                        <xf:setvalue ref="instance('localState')/
                        sequence/scene[last()]/@title" 
                      value="instance('data')/scene[@id=instance('localState')//
                      activeScene]/title"/>
            </xf:action>

Each block of output code within a repeated sequence has its own internal position indicator, and any control within such a block can access that indicator through the index() XPath extension. Thus, index('links') will retrieve the index of the triggered link, and from this you can retrieve the associated id value. The score is updated by looking at the action attribute for the target scene and using this either to set or to add to the score in the local state model.

Once this has been updated, the initial scene in the local-state's sequence is duplicated, then the ref and title attributes are set as appropriate for the new scene.

The final block of code again makes use of this sequence and generates an "itinerary" of which scenes the person had already visited to get to where they are at any given time.

The full listing for the sceneViewer and a sample scenario are given in the download below.

Summary

This particular application is comparatively simple (for all the fairly convoluted XPath expressions), but obviously has a lot of ramifications. For instance, the sample here has just used a selection of paragraphs and literal output of content to display the scenario bodies and links, but in many respects it makes more sense for that content to be markup code of some sort. This way you can include images, text formatting, and related manipulation.

More to the point, you can also include additional XForms control elements, perhaps feeding into another data model. In this case, the scenario format makes it possible to create scenarios and walk through those scenarios to fill out the appropriate forms.

The second part of this series will look at both how to go about creating a rich-text version of the scenario editor as well as building an editor that lets you create new scenarios on the fly and save them to an XML database.


Download

DescriptionNameSize
Sample scenarios for this articlescenarios.zip5KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into XML on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=XML
ArticleID=228232
ArticleTitle=Understanding XForms
publish-date=06052007