4.3 Repeating Structures with |
Id | A unique id used to identify the |
Collection | The collection of nodes being iterated. This is a node-set identified using an XForms binding expression. The binding expression appearing on construct |
Index | An index that points at the current node in the collection. To maintain consistency with XPath, XForms uses a 1-based index to identify the current node in the collection, which is made available to authors via XPath extension function index . |
UI Template | The body of construct |
Controls | Controls that enable the user to add to, delete, or scroll the nodes in the collection. These add , delete , and scroll controls are created by including the appropriate event handler within XForms control |
Presentation | Presentation hints that indicate the portion of the collection to display. This is achieved by specifying the index of the first node to display and the suggested number of members from the collection to display. These are only hints; the presentation must display the current node at all times. |
Next , we define the XML markup that allows XForms authors to create repeating user interfaces. The information items enumerated in Section 4.3.1 are encoded via appropriately designed attributes and child elements. In addition to these, construct repeat
can use all the common XForms user interface markup described in this chapter.
startindex | Optional attribute startindex specifies the first member of the collection that is presented to the user. It defaults to 1 if not specified, that is, the presentation starts with the first member of the collection. |
number | Optional attribute number specifies the number of members to display at any given time. This is a presentation hint, and the client can display fewer or more members of the collection as appropriate for the connecting device. |
Binding | Attribute pair ( model , nodeset ) locates the collection of nodes to be iterated. Alternatively, this binding can be specified using attribute bind to address a predefined binding site . |
The body of construct repeat
is the template user interface to be used when presenting each member of the collection. This can use all of the XForms user interface vocabulary [1] in addition to markup defined by the host language. Thus, when using XForms within XHTML, body of construct
repeat
might use XHTML markup in addition to markup defined by XForms. Conceptually, the contents of construct
repeat
can be thought of as being enclosed in an anonymous
group
construct. This is useful in answering common styling questions that arise with respect to the presentation of
repeat
user interfaces.
[1] An exception to this is construct
switch
.
Using the markup described so far, we define a shopping cart example that allows the user to add and remove products from a conceptual shopping cart interactively. The visual interface for the complete shopping cart example is shown in Figure 4.9.
The shopping cart display grows or shrinks appropriately and displays a portion of the cart that includes the current item at any given time. This example consists of two parts , the model and the user interface, each of which is described following. This shopping cart will be extended with add , delete , and scroll controls in Section 4.3.4.
For this example, we first define an XForms model that declares the structure of our shopping cart in Figure 4.10.
< model xmlns ="http://www.w3.org/2002/xforms" xmlns:xsd ="http://www.w3.org/2001/XMLSchema" id ="cart" schema ="cs.xsd"> < instance id ="c1"> < cart xmlns =""> < line-item >< item >< product >...</ product ></ item > < quantity >1</ quantity > <!-- cost includes: price, tax and shipping and will be computed --> < cost /></ line-item ></ cart > </ instance > < instance id ="cat"> < catalog xmlns =""> < product sku ="a1"> < description />< price /></ product > </ catalog ></ instance > < bind nodeset ="/cart/line-item/cost" calculate ="../item/product/price *../quantity +../item/product/price *../quantity * 0.08 +../item/product/shipping"/> </ model >
This model uses the schema defined in Figure 4.11 to define the type and structure of the instance data used by the shopping cart. The shopping cart holds one or more line-item
elements, with each
line-item
containing details about a given item being purchased.
< x:schema id ="cart-schema" xmlns:x ="http://www.w3.org/2001/XMLSchema"> < x:element name ="cart"> < x:complexType >< x:sequence > < x:element ref ="line-item" maxOccurs ="unbounded"/> </ x:sequence ></ x:complexType > </ x:element > < x:element name ="line-item"> < x:complexType >< x:sequence > < x:element ref ="item"/> < x:element name ="quantity" type ="x:integer"/> < x:element name ="cost" type ="x:decimal"/> </ x:sequence ></ x:complexType > </ x:element > < x:element name ="item"> < x:complexType >< x:sequence > < x:element ref ="product"/> </ x:sequence ></ x:complexType > </ x:element > < x:element name ="product"> < x:complexType >< x:sequence > < x:element name ="description" type ="x:string"/> < x:element name ="price" type ="x:decimal"/> < x:element name ="shipping" type ="x:decimal"/> </ x:sequence > < x:attribute name ="sku" type ="x:string"/> </ x:complexType > </ x:element > </ x:schema >
Notice that this model includes two instances, the first to hold the items on the shopping cart, and the second to hold a product catalog; contrast this with the similar example on dynamic selections in Section 3.4.5 where we used separate models for the bookshelf and catalog. When using multiple instances, XForms binding expressions use XPath extension function instance to identify the instance being addressed; by default, XForms binding expressions address the first instance in the model.
Observe the following facts with respect to the model shown in Figure 4.10:
Structure | Element |
Constraints | Later chapters will add additional dynamic constraints to the shopping cart via XForms element |
Calculate | Some fields appearing in the shopping cart are computed as a function of other values. Thus, the total cost of an item might be computed from its price, tax, and cost of shipping. Such calculations are defined via model property calculate using XForms element |
Catalog | In this example, we have shown the catalog as an inline instance. In a real-world shopping application, element |
The markup shown in Figure 4.12 binds an XForms user interface to the shopping cart model. We use construct repeat to iterate the line-item
elements in the shopping cart. Attribute startindex is set to 1 to cause the user interface to display a portion of the cart starting with the first
line-item
element; attribute number is set to 3 to serve as a presentation hint. The markup appearing within body of element
repeat
contains XForms user interface controls for populating an individual
line-item
in the cart.
< group xmlns ="http://www.w3.org/2002/xforms"> < label >Shopping Cart</ label > < repeat id ="cartUI" model ="cart" nodeset ="/cart/line-item" startindex ="1" number ="3"> < select1 ref ="item" appearance ="minimal"> < label >Select Product</ label > <!-- Namespace qualify bindings.--> < itemset nodeset ="instance('cat')/product"> < label > Item: < output ref ="description"/> Price: < output ref ="price"/> </ label > < copy ref ="."/> </ itemset ></ select1 > < input ref ="quantity"> < label >Quantity</ label ></ input > Price + Shipping: < output ref ="cost"/> </ repeat ></ group >
Control select1
is used within the body of
repeat
to enable the user to select a product from the catalog. We have used appearance to request a minimal presentation, that is, one that takes up minimal display real estate. A visual user agent might choose to render this control as a pull-down list. This is appropriate, since the user will be presented with this control once for each
line-item
appearing in the shopping cart.
Binding attributes on control select1
specify that the value selected will be placed in the shopping cart at location
/cart/line-item/item
The available products are rendered via element itemset
whose binding expression refers to the catalog instance to determine the available products at run-time. Note that this binding expression uses function instance as in
nodeset="instance('cat')/product"
to address the set of products listed in the catalog. Child elements label
and
copy
also use this binding expression to set up the XPath context for evaluating relative XPath locators.
Element itemset
creates the list of alternatives as described in Section 3.4.5. Notice that in this example, we have used element
output
within
label
to build up a label for each alternative that extracts the description and price fields from the product to be selected. Finally, element
copy
uses binding expression
ref="."
to declare that picking one of the alternatives should result in the current node operated on by the containing itemset
being copied to the location specified by the
select1
. The
select1
has been set up to populate the
item
child of the current
line-item
. The result of this copy operation is therefore the addition of the selected
product
element as a child of element
item
in the current
line-item
.
By definition, the product
contained in line-item/item in the shopping cart has the same type and structure as the
product
appearing in the catalog; thus, the copy operation populates the shopping cart with structurally valid content.
The remainder of the template user interface in repeat
is fairly simple; user interface control
input
allows the user to specify the quantity to be ordered. Element
output
is used to display the cost of this line-item. This is a computed value and is automatically recalculated when the user selects a product and specifies the quantity being ordered.
The markup shown in Figure 4.12 does not contain presentational or stylistic information. In a real-world shopping cart, this markup would be hosted in an appropriate host language, for example, XHTML; this would provide the necessary constructs for aligning the controls appearing within the body of repeat
or styling them via CSS.
We will extend this example in a later chapter by extending the model to define cardinality constraints on the number of items that can be placed in the shopping cart. These constraints can then be used in enabling or disabling the add and delete controls shown in Figure 4.13. The shopping cart is contained within construct group
that has an appropriate label. This
group
will be used next in aggregating the
repeat
with the add, delete, and scroll controls.
< group xmlns ="http://www.w3.org/2002/xforms" xmlns:ev ="http://www.w3.org/2001/xml-events"> < label >Shopping Cart</ label > < repeat id ="cartUI">...</ repeat > < group model ="cart" ref ="/cart"> < label >Shopping Cart Toolbar</ label > < trigger id ="addItem"> < label >Create Item</ label > < insert nodeset ="line-item" at ="index('cartUI')" position ="after" ev:event ="DOMActivate"/></ trigger > < trigger id ="del"> < label >Remove Item</ label > < delete nodeset ="line-item" at ="index('cartUI')" ev:event ="DOMActivate"/></ trigger > < trigger id ="forward"> < label >Scroll Forward</ label > < setindex repeat ="cartUI" index ="index('cartUI')+1"/></ trigger > < trigger id ="back"> < label >Scroll Back</ label > < setindex repeat ="cartUI" index ="index('cartUI')-1"/></ trigger > </ group ></ group >
Next, we add add , delete , and scroll controls to the shopping cart. These controls will use XForms declarative event handlers insert
,
delete
, and
setindex
, respectively. We first describe these event handlers before using them within control
trigger
to create the desired controls.
Event handlers insert
and
delete
enable the addition and deletion of nodes to a collection of nodes. In this sense, they are not specific to construct
repeat
; however, in XForms 1.0 they are mostly used in conjunction with user interfaces created via
repeat
. Handler
setindex
is specific to
repeat
since it manipulates the index that determines the current node for a construct
repeat
. Invoking any of these handlers causes the user interface created via construct
repeat
to be updated appropriately to reflect the new state of the underlying collection.
insert
and
delete
Handlers
insert
and
delete
are symmetric with respect to their underlying design as well as the XML markup they expose. These handlers need to specify the following items of information:
Collection | Identify the collection of nodes that will be affected by the insert or delete operation. This is encoded used XForms binding attributes, either via attribute pair ( model , nodeset ) or via a predefined binding site. |
Location | The location in the collection that is to be affected by the insert or delete operation. This is specified via an XPath expression in attribute at . This expression is evaluated to give a 1-based offset into the collection. This XPath expression can use extension function index to compute an offset that is relative to the current node in the collection. |
Position | When inserting nodes into a collection, we need to know whether the new node is to be inserted before or after the specified location. Handler |
Eventing | Attributes defined by module XML Events used to wire up these handlers to the appropriate user interface events. |
setindex
Handler
setindex
can be viewed as a specialized assignment statement for modifying the value of the index for a given
repeat
construct. It takes the following items of information:
repeat | Required attribute repeat holds the id of the |
index | Required attribute index holds an XPath expression that is evaluated to produce the new value for the repeat index. This expression can use extension function index to compute the new index value relative to the current node in the collection. |
Here, we use actions insert
,
delete
, and
setindex
within control
trigger
to create add , delete , and scroll controls for the shopping cart. For this example, we will place these controls outside the body of construct
repeat
to create a conceptual toolbar for the shopping cart. This toolbar will be placed inside a
group
that carries the relevant metadata for these controls. The toolbar and the user interface created via
repeat
are in turn placed inside a
group
to capture the fact that the toolbar and shopping cart are logically related .
Observe the following in the example shown in Figure 4.13:
Grouping | The controls are grouped using construct |
Events | The |
Location | The at="index('cartUI')" As a result, the add control creates a new line-item at the current position in the cart; the delete control removes the current line-item from the cart. |
Position | The |
Scroll | Handler |
index="index(cartUI)+1" index="index(cartUI)-1"
We now review the various stages of user interaction with the shopping cart as created so far. The shopping cart starts off with one empty line-item when the user first accesses the application. This is because of the initial instance declared in the model shown in Figure 4.10. The line-item is empty; that is, no product is selected. The presentational hints on construct repeat
in Figure 4.12 request that three line-items be displayed starting with the first line-item. Since there is only one line-item to display, the presentation starts off displaying only one line-item. The index of construct
repeat
is set to 1. The toolbar consisting of the add , delete , and scroll controls is rendered along with the shopping cart.
In rendering the template user interface specified within the body of construct repeat
for the line-item in the cart, the XForms client creates a
select1
control with no product selected and fields that bind to the quantity and cost fields. Field quantity has an initial value of 1 as specified in the model. Next, the user activates control
select1
to pick a specific product using the pull-down list. Selecting a product causes the corresponding
product
node from the catalog to be copied to the shopping cart.
The current line-item
is no longer empty. The
select1
control now displays the description of the selected product. When the shopping cart model is updated with this selected product, that update in turn triggers the computation of field cost . The shopping cart model has defined the value of cost as follows :
calculate="../item/product/price *../quantity +../item/product/price * ../quantity * 0.08 +../item/product/shipping"/>
This expression is evaluated to produce the value to be stored in cost . The user interface is updated to reflect the updated value of field cost . Next, the user changes the value of quantity from 1 to 5. This again causes field cost to be recomputed and the user interface to be refreshed appropriately.
Next, the user adds a new line-item to the shopping cart by activating the add control. This invokes handler insert
which clones the initial
line-item
node as declared in the
instance
element and inserts this copy into the shopping cart. Notice that each time handler
insert
is invoked, it will use the
line-item
from the initial instance when creating the new
line-item
to be inserted. As a result, each newly created
line-item
starts off as described in the initial step.
The user proceeds to add new line-item
entries to the shopping cart. As the number of
line-item
entries grows beyond 3, the presentational hint provided via attribute number takes effect, and the user interface scrolls to display a portion of the cart that includes the current
line-item
. Activating the scroll forward or scroll back controls results in the index being assigned a new value; this in turn results in the user interface being updated to display the current
line-item
.
When presenting the shopping cart user interface within an XHTML page, each line-item might be presented as a row of a table. The obvious solution would be to place element repeat
within an XHTML
table
element and have the
repeat
produce the rows of the table; see Figure 4.14.
< table xmlns ="http://www.w3.org/1999/xhtml" xmlns:xf ="http://www.w3.org/2002/xforms"> <!-- XHTML does not permit this --> < xf:repeat id ="cartUI" model ="cart" nodeset ="/cart/line-item" startindex ="1" number ="3"> < tr > < td > < xf:select1 ref ="item" appearance ="minimal"> < xf:label >Select Product</ xf:label > < xf:itemset nodeset ="instance('cat')/product"> < xf:label > Item: < xf:output ref ="description"/> Price: < xf:output ref ="price"/> </ xf:label > < xf:copy ref ="."/> </ xf:itemset ></ xf:select1 > </ td > < td > < xf:input ref ="quantity"> < xf:label >Quantity</ xf:label ></ xf:input > </ td > < td > Price + Shipping: < xf:output ref ="cost"/> </ td ></ tr > </ xf:repeat ></ table >
However, doing so would produce an invalid XHTML document; this is because as defined, the content model of XHTML element table
cannot be extended. To cover this common use case while preserving the ability to create valid XHTML documents, XForms defines an alternative means of creating
repeat
structures via a set of attributes. We refer to this set of attributes as repeat attributes .
The repeat attributes encode the same set of information as encapsulated by element repeat
, and repeating structures created via repeat attributes have the same functionality as repeating structures created via element
repeat
. Thus, the difference is one of concrete syntax, and there is no change to the underlying processing model.
To understand this alternative syntax for creating repeating structures, think of repeat
as encapsulating its contents in an anonymous group ”anonymous because in the case of element
repeat
, this
group
element does not appear in the markup as seen in Figure 4.12.
Viewed this way, element repeat
is present in the markup to carry the various information items needed to create the repeating structure; it achieves this by encoding the needed information via attributes appearing on element
repeat
. Next, we make the anonymous group explicit as shown in Figure 4.15. The repeating structures thus created are identical by our definition.
< repeat xmlns ="http://www.w3.org/2002/xforms" id ="cartUI" model ="cart" nodeset ="/cart/line-item" startindex ="1" number ="3"> <!--Anonymous group made explicit --> < group > < select1 ref ="item" appearance ="minimal"> < label >Select Product</ label > < itemset nodeset ="instance('cat')/product"> < label > Description: < output ref ="description"/> Price: < output ref ="price"/> </ label > < copy ref ="."/> </ itemset ></ select1 > < input ref ="quantity">< label >...</ label ></ input > Price + Shipping: < output ref ="cost"/> </ group ></ repeat >
Finally, we can move the attributes appearing on element repeat
to element
group
before dropping the containing
repeat
element as shown in Figure 4.16.
< group xmlns ="http://www.w3.org/2002/xforms" repeat-id ="cartUI" repeat-model ="cart" repeat-nodeset ="/cart/line-item" repeat-startindex ="1" repeat-number ="3"> <!-- cart UI as before --> </ group >
There is one final twist; we prefix each attribute name in the repeat attributes with repeat- for clarity. See Table 4.1 for the correspondence between attributes of element repeat
and the repeat attributes .
Attribute | Repeat Attribute |
---|---|
id | repeat-id |
model | repeat-model |
nodeset | repeat-nodeset |
bind | repeat-bind |
startindex | repeat-startindex |
number | repeat-number |
The XForms working group defined repeating structures via an element (rather than via attributes) to ensure future extensibility. This is why the element version of repeating structures is still the preferred solution over the alternative of placing repeat attributes on element group
. However, XForms is also designed to be hosted in a variety of markup languages such as XHTML and SVG. As illustrated in the case of XHTML tables, these container languages may often define content models that are not extensible, thereby making it impossible to use element
repeat
at the appropriate point in the markup.
XHTML modularization enables host languages to import attributes and apply such foreign attributes to the various elements defined in the language. Thus, the repeat attributes from the XForms namespace can be imported into XHTML and applied to element tr
when creating repeating rows inside a table; see Figure 4.17.
< table xmlns ="http://www.w3.org/1999/xhtml" xmlns:xf ="http://www.w3.org/2002/xforms"> <!--Repeat via attributes on element tr--> < tr repeat-id ="cartUI" repeat-model ="cart" repeat-nodeset ="/cart/line-item" repeat-startindex ="1" repeat-number ="3"> < td > < xf:select1 ref ="item" appearance ="minimal"> < xf:label >Select Product</ xf:label > < xf:itemset nodeset ="instance('cat')/product"> < xf:label > Item: < xf:output ref ="description"/> Price: < xf:output ref ="price"/> </ xf:label > < xf:copy ref ="."/> </ xf:itemset ></ xf:select1 ></ td > < td > < xf:input ref ="quantity"> < xf:label >Quantity</ xf:label ></ xf:input ></ td > < td > Price + Shipping: < xf:output ref ="cost"/> </ td ></ tr ></ table >
To summarize, repeating structures can now be encoded by placing repeat attributes as defined earlier on any markup element and consequently on element tr
appearing inside XHTML tables. This allows the creation of XHTML tables that grow or shrink dynamically while preserving the structural validity of XHTML documents.
We conclude this section with a summary of construct repeat
. Element
repeat
is similar in functionality to iterator constructs found in modern object-oriented languages like Java. Construct
repeat
enables the creation of browser-based user interfaces for editing and manipulating XML structures.
The children of element repeat
define a user interface template that is instantiated for the members of the collection over which the
repeat
iterates. The repeat index determines the current member of this collection and is key to the functioning of the repeating construct. Access to this repeat index is carefully controlled, and XForms action
setindex
is the only means of changing the value of the repeat index .
Special XForms actions insert
,
delete
, and
setindex
can be used to manipulate the underlying collection. XForms user interface controls can be used to view and update individual members of the collection. All such changes to the underlying collection are reflected in the user interface, and this creates dynamic user interfaces.
This design enables the nesting of repeating structures to create complex user interfaces. We detail one such use in Section 4.4 where we use nested, repeating structures to maintain a hierarchical task list. The preferred means of encoding repeating structures is via element repeat
. In addition, XForms 1.0 defines a set of repeat attributes that provide the same functionality as element
repeat
for use in container languages where the content model does not permit new elements.