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
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
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
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.
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
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
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
<displayNullLinks> serve to indicate to the application whether
given interface elements. In the first case,
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.
<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
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
from the localState instance. Thus, to reference the
<activeScene>, you would use the expression
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,
author are now both provided in the context of
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><</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>></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
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
"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::*/@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::*/@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.
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>></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
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
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
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/@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>
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.
<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
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 >= @minvalue) and (instance('localState')//score <= @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 >= @minvalue) and (instance('localState')//score <= @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
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
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.
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.
|Sample scenarios for this article||scenarios.zip||5KB|
- Get a basic introduction to XForms in Introduction to XForms, Part 1: The new Web standard for forms (Chris Herborth, developerWorks, September 2006), Introduction to XForms, Part 2: Forms, models, controls, and submission actions (Chris Herborth, developerWorks, September 2006), and Introduction to XForms, Part 3: Using actions and events (Chris Herborth, developerWorks, September 2006).
- Learn more about XForms in the IBM developerWorks XML zone.
- IBM XML certification: Find out how you can become an IBM-Certified Developer in XML and related technologies.
- XML technical library: See the developerWorks XML Zone for a wide range of technical articles and tips, tutorials, standards, and IBM Redbooks.
- Visit XForms.org, a clearinghouse of XForms-related information.
- developerWorks technical events and webcasts: Stay current with technology in these sessions.
- Make sure to visit the new XForms community topic, a community micro-site focused on everything you ever wanted to know about XForms.
Get products and technologies
- The XForms Recommendation is maintained by the W3C.
- For more information on XPath, check out the XPath Recommendation.
- Get MozzIE, an open-source control that allows you to render XForms in Internet Explorer.
- Get XForms templates from Google Data API's.