4.3 Repeating Structures with repeat
The ability to adapt dynamically to user interaction is one of the most important usability features of electronic forms. We saw examples of such dynamic behavior when reviewing construct switch in Section 4.2 and when creating dynamic selections via element itemset in Section 3.4.5. XForms construct repeat , described in this section, completes this picture by enabling the creation of template-based user interfaces that grow or shrink during user interaction.
Such repeating constructs are most commonly found when interacting with electronic stores on the Web today where the metaphor of a dynamic shopping cart is implemented using appropriate looping constructs in either client-side or server-side scripts. XForms construct repeat defines a declarative construct that can be used to iterate over collections of like nodes in the XForms model, for example, the items in a shopping cart.
4.3.1 Designing Construct repeat
The purpose of construct repeat is to create a user interface that repeats over a collection of nodes. To this end, repeat can be thought of as an iterator ; contents of repeat can be thought of as a template of the user interface to be created for each node in the collection being iterated. The user interface created by construct repeat displays a portion of the collection being iterated with user interaction facilities that permit the user to scroll through the collection, and to add or delete nodes to the collection. Notice that all of these operations depend on the notion of a well-defined current node in the collection; XForms calls this the repeat index . Thus, construct repeat encapsulates the following information:
4.3.2 Anatomy of Construct repeat
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.
Attributes of Construct repeat
Child Elements of 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  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.
4.3.3 Shopping Cart Using Construct 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.
Figure 4.9. X-Smiles rendering of the XForms shopping cart.
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.
Shopping Cart Model
For this example, we first define an XForms model that declares the structure of our shopping cart in Figure 4.10.
Figure 4.10 Shopping cart model defines the structure of element 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.
Figure 4.11 Schema for the shopping cart.
< 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:
Shopping Cart User Interface
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.
Figure 4.12 Shopping cart user interface using construct 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
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
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
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.
Figure 4.13 Adding add , delete , and scroll controls to shopping cart.
< 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 >
4.3.4 Adding Controls to the Shopping Cart
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 for Use with 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:
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:
Add, Delete, and Scroll Controls
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:
4.3.5 User Interaction with Construct 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 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 .
4.3.6 Using Construct 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.
Figure 4.14 Placing element 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 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.
Figure 4.15 Element 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.
Figure 4.16 Moving repeat attributes to the containing anonymous group.
< 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 .
Table 4.1. Correspondence between Repeat Attributes and Attributes of Element repeat
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.
Figure 4.17 XHTML tables that grow and shrink dynamically.
< 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.
4.3.7 Summary of Construct 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 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.