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
|
repeat
repeat
can use all the common XForms user interface markup described in this chapter.
repeat
|
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
)
|
repeat
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
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
.
repeat
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
For this example, we first define an XForms model that declares the structure of our shopping cart in Figure 4.10.
cart
.
< 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:elementname ="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
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.
repeat
.
< 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
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
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
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.
repeat
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
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
group
to capture the fact that the toolbar and shopping cart are logically
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
|
|
Position |
The
|
|
Scroll |
Handler
|
index="index(cartUI)+1" index="index(cartUI)-1"
repeat
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
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
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
line-item
node as declared in the
instance
element and
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
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
.
repeat
within XHTML Tables
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.
repeat
within element
table
produces invalid XHTML.
< 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
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
contains an anonymous
group
element.
< 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
.
repeat
|
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
repeat
at the appropriate point in the markup.
XHTML
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.
repeat
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
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.