Spry Data Set and Dynamic Region Overview

 



What is the Spry framework?

The Spry framework is a client-side JavaScript library that consists of a set of JavaScript, CSS, and image files that provide support for XML Data Sets, Dynamic Regions, Widgets, and Transition Effects.

Browser Diagram

Figure - The Spry framework runs on the client-side in a browser.

Although these files reside on a server, they are meant to be loaded and run inside of a browser. Users of the framework will include/link to these files and make use of the various components of the framework in their HTML documents to add a richer experience for their users.

The Spry framework is geared towards Designers, so every element of the framework should adhere to the following guidelines:


XML Data Sets

An XML data set is a JavaScript Object that provides an API for loading and managing data from an XML document. As the data is loaded, it is flattened out into a tabular format of rows and columns.

As you read about the features of the XML data set, please keep in mind that this is a preview release, so this document simply describes what is currently implemented. The API for the data set is still being worked out and may change.

Creating an XML Data Set

To create an XML data set, you must first include 2 JavaScript files in your HTML document:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Dynamic Region Example</title>
<script type="text/javascript" src="../../includes/xpath.js"></script>
<script type="text/javascript" src="../../includes/SpryData.js"></script>
</head>
<body>
</body>
</html>

Source Listing - To use XML data sets and dynamic regions, you must first include xpath.js and SpryData.js in your HTML document.

"xpath.js" is Google's JavaScript implementation of the XPath 1.0 standard. You can get more information about it by visiting their open source google-ajaxslt project page. "SpryData.js" contains the code that defines XML data sets and dynamic regions.

XML data sets are created programmatically with the JavaScript "new" keyword. The constructor for the XML data set requires 2 arguments:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Dynamic Region Example</title>
<script type="text/javascript" src="../../includes/xpath.js"></script>
<script type="text/javascript" src="../../includes/SpryData.js"></script>
<script type="text/javascript">
var dsPhotos = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo");
</script>
</head>
<body>
</body>
</html>

Source Listing - Example of creating an XML Data Set in JavaScript. The first argument to the constructor is the url. The second argument is the XPath to the data.

The URL argument can be a relative or absolute URL to an XML file, or to a web service/application that returns XML data. Users need to be aware that the loading of XML data is subject to the browser's security model which requires that any data you load must come from the domain from which the HTML page, currently running in the browser, came from. Web developers typically get around this limitation by having a server-side script that can be called from the browser that can load URLs from other domains.

The data set uses the XMLHTTPRequest object to load the URL. When the XML data arrives, it is actually in 2 formats, a text format, and a DOM tree format.

<gallery id="12345">
	<photographer id="4532">John Doe</photographer>
	<email>john@doe.com</email>
	<photos id="2000">
		<photo path="sun.jpg" width="16" height="16" />
		<photo path="tree.jpg" width="16" height="16" />
		<photo path="surf.jpg" width="16" height="16" />
	</photos>
</gallery>

Source Listing - Example of XML in text format.

XML as Text

Figure - Example of XML as a DOM tree.

The data set uses the XPath argument that was passed to its constructor to navigate the XML DOM tree to find the set of nodes that represent the data you are interested in.

<gallery id="12345">
	<photographer id="4532">John Doe</photographer>
	<email>john@doe.com</email>
	<photos id="2000">
		<photo path="sun.jpg" width="16" height="16" />
		<photo path="tree.jpg" width="16" height="16" />
		<photo path="surf.jpg" width="16" height="16" />
	</photos>
</gallery>

Source Listing -Data selected with the XPath "/gallery/photos/photo".

XPath /gallery/photos/photo

Figure - Data selected with the XPath "/gallery/photos/photo".

The data set then flattens the set of nodes into a tabular format:

@path @width @height
sun.jpg 16 16
tree.jpg 16 16
surf.jpg 16 16

Figure - Flattened data selected with XPath "/gallery/photos/photo".

The column names of the flattened table are derived from the tag name of the selected node or its child nodes, and their attributes. A column will be created for each attribute on the selected node. If the selected node has a single child that is a text or cdata node, that child is considered the value of that node. A column will be created for that text or cdata node and its name will be the tag name for the node. If the selected node has child elements, a column will be created for the value, if it has one, of each child element, and each of its attributes.

This description is a bit confusing, so lets run through a few more examples to illustrate the flattening process and the different column names that can be generated.

An XPath of "/gallery/photographer":

<gallery id="12345">
	<photographer id="4532">John Doe</photographer>
	<email>john@doe.com</email>
	<photos id="2000">
		<photo path="sun.jpg" width="16" height="16" />
		<photo path="tree.jpg" width="16" height="16" />
		<photo path="surf.jpg" width="16" height="16" />
	</photos>
</gallery>

Source Listing - Selected nodes for XPath "/gallery/photographer".

XPath /gallery/photographer

Figure - Selected nodes for XPath "/gallery/photographer".

would result in the following table:

photographer @id
John Doe 16

Figure - Flattened data for XPath "/gallery/photographer".

In this particular case, only one node is selected, so we only get one row in our data set. The value of the <photographer> node is the text "John Doe", so a column named "photographer" is created to store that value. "id" is an attribute of the <photographer> node so its value is placed in the "@id" column. All attribute names are preceded by an "@" character.

An XPath of "/gallery":

<gallery id="12345">
	<photographer id="4532">John Doe</photographer>
	<email>john@doe.com</email>
	<photos id="2000">
		<photo path="sun.jpg" width="16" height="16" />
		<photo path="tree.jpg" width="16" height="16" />
		<photo path="surf.jpg" width="16" height="16" />
	</photos>
</gallery>

Source Listing - Selected nodes for XPath /gallery.

XPath /gallery

Figure - Selected nodes for XPath /gallery.

Would result in the following table and column names:

@id photographer photographer/@id email
12345 John Doe 4532 john@doe.com

Figure - Flattened data selected with XPath "/gallery".

Notice that the column name for attributes of children elements are prefixed with the tag name of the child element. In this particular case, <photographer> is a child element of the selected <gallery> node, so its id attribute is prefixed with "photographer/@". Also notice that nothing was added to the table for the <photos> element even though it is a child of the <gallery> node. That is because we don't flatten any child elements that contain other elements.

With XPath, you can also select attributes of nodes, so if you used an XPath of "gallery/photos/photo/@path":

<gallery id="12345">
	<photographer id="4532">John Doe</photographer>
	<email>john@doe.com</email>
	<photos id="2000">
		<photo path="sun.jpg" width="16" height="16" />
		<photo path="tree.jpg" width="16" height="16" />
		<photo path="surf.jpg" width="16" height="16" />
	</photos>
</gallery>

Source Listing -Selected nodes for XPath "gallery/photos/photo/@path".

XPath /gallery/photos/photo

Figure - Selected nodes for XPath "gallery/photos/photo/@path".

you would end up with a table like this:

@path
sun.jpg
tree.jpg
surf.jpg

Figure - Flattened data selected with XPath "/gallery/photos/photo/@path".

Getting at the Data

As mentioned above, after the XML data is loaded it is flattened into a tabular format. Inside the data set, the data is actually stored as an array of objects (rows) with properties (columns) and values.

[
	{ "@path": "sun.jpg",  "@width": 16, "@height": 16 },
	{ "@path": "tree.jpg", "@width": 16, "@height": 16 },
	{ "@path": "surf.jpg", "@width": 16, "@height": 16 }
]
@path @width @height
sun.jpg 16 16
tree.jpg 16 16
surf.jpg 16 16

Source Listing - The data in the data set is stored as an array of row objects with properties that are the columns.

You can get all of the rows in the data set by calling getData().

var dsPhotos = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo");

...

var rows = dsPhotos.getData(); // Get all rows.
var path = rows[0]["@path"];   // Get the data in the "@path" column of the first row.

Source Listing - Use getData() to fetch all the rows in the Data Set. You can get the value of a specific column by indexing into the row with the column name.

Current Row

Each data set maintains the notion of a "current row". By default, the current row is set to the first row in the data set. The current row can be changed programmatically by calling the setCurrentRowNumber() method and passing the row number of the row you want to make the current row. Note that the index of the first row is always zero, so if a data set contains 10 rows, the index of the last row is 9.

var dsPhotos = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo");

...

dsPhotos.setCurrentRowNumber(2); // Make the 3rd row in the data set the current row.

Source Listing - Use setCurrentRowByNumber() or setCurrentRow() to change the current row.

Each row in the data set is also assigned a unique ID which allows someone to refer to a specific row in the data set even after the order of the rows in the data set change. You can get the ID of a row by accessing its "ds_RowID" property. You can also change the "current row" by calling setCurrentRow() and passing the row ID of the row you want to make the current row.

var dsPhotos = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo");

...

var id = dsPhotos.getData()[2]["ds_RowID"]; // Get the ID of the 3rd row.

...

dsPhotos.setCurrentRow(id); // Make the 3rd row the current row by using its ID.

Source Listing - You can get the ID of a row by accessing its "ds_RowID" property. You can call setCurrentRow with a rowID to set the data set's "current row".

Sorting

You can sort the rows of a data set using the values in a given column by calling the sort() method on the data set.

var dsPhotos = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo");

...

dsPhotos.sort("@path"); // Sort the rows of the data set using the "@path" column.

Source Listing - Calling the sort() method of a data set will sort the rows in ascending order, using data in the column specified.

Currently, the sort() method sorts the data in ascending order. Support for descending order, as well as support for specifying a column for a secondary sort, will be added in a future release of the framework.

If you'd like to have the data in the data set sorted automatically whenever data is loaded, you can specify the column to sort by as an option to the constructor.

var dsPhotos = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo", { sortOnLoad: "@path" });

Source Listing - Use the "sortOnLoad" option to the data set constructor to specify a column to sort by. In this example the data set will automatically sort by the data in the "@path" column.

By default all values in the data set are treated as text. This can be problematic when sorting numeric or date values. You can specify the data type for a given column by calling the setColumnType() method.

var dsPhotos = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo");
dsPhotos.setColumnType("@width", "number");
dsPhotos.setColumnType("@height", "number");

...

dsPhotos.sort("@width"); // Sort the rows of the data set using the "@width" column.

Source Listing - Use the setColumnType() method to specify the data type for a given column.

Current supported data types are "number", "date", and "string".

Distinct

You can remove duplicate rows in a data set by using the distinct() method on the data set.

var dsPhotos = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo");

...

dsPhotos.distinct(); // Remove all duplicate rows.

Source Listing - Use the distinct() method to remove duplicate rows.

If you'd like to run the distinct() method automatically whenever data is loaded into a data set, use the "distinctOnLoad" option to the constructor.

var dsPhotos = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo", { distinctOnLoad: true });

Source Listing - Use the distinct() method to remove duplicate rows.

Filtering

You can filter the rows of a data set by calling the filter() method and passing a filter function as an argument, which will return true if a row should be kept or false if it should be removed from the data set.

var dsPhotos = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo");

...

// Filter out all rows that don't have a path that begins
// with the letter 's'.
var myFilterFunc = function(row)
{
	if (row["@path"].search(/^s/) != -1)
		return true;                     // Return true to keep the row in the data set.
	return false;                        // Return false to remove the row from the data set.
}

dsPhotos.filter(myFilterFunc); // Filter the rows in the data set.

Source Listing - Use the filter() method to filter the rows in a data set.

Observer Notifications

The XML data set supports an observer mechanism that allows objects to register themselves to receive data or row changed notifications. To receive notifications, an object must define a callback function property named "onDataChanged", and then register itself as an observer on the data set.

var dsPhotos = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo");

...

var myObserver = new Object;
myObserver.onDataChanged = function(dataSet, notificationType)
{
	alert("Received notification: " + notificationType);
};

dsPhotos.addDataChangedObserver("myObserverName", addDataChangedObserver);

Source Listing - An object must define an onDataChanged () method and then call addDataChangedObserver() to get data set notifications.

Currently, there are only 3 notification types supported:


Dynamic Regions

A dynamic region is a portion of an HTML document that is bound to one or more data sets, and has the ability to regenerate itself as the data in the data sets change.

This automatic regeneration is possible because each dynamic region registers itself as an observer of each of the data sets it is bound to.

Region Observes Data Set

Figure - Each dynamic region on the page registers itself as an observer of the data sets it is bound to.

Anytime the data in any of these data sets is modified (loaded, updated, sorted, filtered, etc), the data sets fire off a notification to all of its observers, triggering each dynamic region that is listening to regenerate itself.

Data Set Notifies Region

Figure - As the data in the data set is changed, each dynamic region is notified, resulting in their regeneration.

Creating a Dynamic Region

Dynamic regions are contained completely within a single element. Adding an "spryregion" attribute with a value that is a space separated list of data set names, tells the framework that all of the content inside that element should be treated as a dynamic region that is bound to the named data sets.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Dynamic Region Example</title>
<script type="text/javascript" src="../../includes/xpath.js"></script>
<script type="text/javascript" src="../../includes/SpryData.js"></script>
<script type="text/javascript">
var dsPhotos = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo");
</script>
</head>
<body>
	<ul spryregion="dsPhotos">
		<li>{dsPhotos::path}</li>
	</ul>
</body>
</html>

Source Listing - This ul element is a dynamic region container that is bound to the dsPhotos data set.

Most elements can be a dynamic region container, but there are some exceptions. The following elements cannot be dynamic region containers:

COL, COLGROUP, FRAMESET, HTML, IFRAME, STYLE, TABLE, TBODY, TFOOT, THEAD, TITLE, TR

Although the elements listed above can't be a dynamic region container, they can exist inside a dynamic region container.

Data References

In a dynamic region, data references of the form:

{<data set name>::<data set column>}

are used as place holders to indicate where real data should be inserted when the region is re-generated.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Dynamic Region Example</title>
<script type="text/javascript" src="../../includes/xpath.js"></script>
<script type="text/javascript" src="../../includes/SpryData.js"></script>
<script type="text/javascript">
var dsPhotos = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo");
</script>
</head>
<body>
	<ul spryregion="dsPhotos">
		<li>{dsPhotos::@path}</li>
	</ul>
</body>
</html>

Source Listing - {dsPhotos::@path} is a data reference.

If a region is only bound to a single data set you can leave off the data set name in a data reference.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Dynamic Region Example</title>
<script type="text/javascript" src="../../includes/xpath.js"></script>
<script type="text/javascript" src="../../includes/SpryData.js"></script>
<script type="text/javascript">
var dsPhotos = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo");
</script>
</head>
<body>
	<ul spryregion="dsPhotos">
		<li>{@path}</li>
	</ul>
</body>
</html>

Source Listing - {@path} can be used as a data reference because the region is only bound to one data set.

As a dynamic region is re-generated, data references are replaced with data from the current row of the data set.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Dynamic Region Example</title>
<script type="text/javascript" src="../../includes/xpath.js"></script>
<script type="text/javascript" src="../../includes/SpryData.js"></script>
<script type="text/javascript">
var dsPhotos = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo");
</script>
</head>
<body>
	<ul>
		<li>sun.jpg</li>
	</ul>
</body>
</html>

Source Listing - {@path} was replaced with the data in the path column of the current row of the data set.

Each data set has a set of built-in data references that may be useful during the region re-generation process:

Like data set column names, these built-in data references must be prefixed with the name of the data set if the dynamic region is bound to more than one data set.

Looping Constructs

Dynamic regions currently support 2 looping constructs. One allows you to repeat an element and all of its content for each row in a given data set (spryrepeat), and another that allows you to repeat all of the children of a given element for each row in a given data set (spryrepeatchildren).

To designate an element as something that repeats, simply add an "spryrepeat" attribute to the element with a value that is the name of the data set you want repeat over:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Dynamic Region Example</title>
<script type="text/javascript" src="../../includes/xpath.js"></script>
<script type="text/javascript" src="../../includes/SpryData.js"></script>
<script type="text/javascript">
var dsPhotos = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo");
</script>
</head>
<body>
	<div spryregion="dsPhotos">
		<ul>
			<li spryrepeat="dsPhotos">{@path}</li>
		</ul>
	</div>
</body>
</html>

Source Listing - This li will now repeat for every row in the dsPhotos data set.

To repeat just the children of an element, simply use the "spryrepeatchildren" attribute instead:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Dynamic Region Example</title>
<script type="text/javascript" src="../../includes/xpath.js"></script>
<script type="text/javascript" src="../../includes/SpryData.js"></script>
<script type="text/javascript">
var dsPhotos = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo");
</script>
</head>
<body>
	<div spryregion="dsPhotos">
		<ul spryrepeatchildren="dsPhotos">
			<li>{@path}</li>
		</ul>
	</div>
</body>
</html>

Source Listing - The children of the ul will repeat for every row in the dsPhotos data set.

The "spryrepeat" and "spryrepeatchildren" examples above are functionally equivalent, but "spryrepeatchildren" becomes more useful when used in conjunction with conditional constructs, covered in the next section. Both examples would result in the following output:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Dynamic Region Example</title>
<script type="text/javascript" src="../../includes/xpath.js"></script>
<script type="text/javascript" src="../../includes/SpryData.js"></script>
<script type="text/javascript">
var dsPhotos = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo");
</script>
</head>
<body>
	<div>
		<ul>
			<li>sun.jpg</li>
			<li>tree.jpg</li>
			<li>surf.jpg</li>
		</ul>
	</div>
</body>
</html>

Source Listing - The code after the region is re-generated.

There may be times when you don't want to output the content in a repeat region for every row in the data set. You can limit what gets written out during the loop processing by adding an "sprytest" attribute to the element that has the "spryrepeat" or "spryrepeatchildren" attribute on it.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Dynamic Region Example</title>
<script type="text/javascript" src="../../includes/xpath.js"></script>
<script type="text/javascript" src="../../includes/SpryData.js"></script>
<script type="text/javascript">
var dsPhotos = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo");
</script>
</head>
<body>
	<div spryregion="dsPhotos">
		<ul>
			<li spryrepeat="dsPhotos" sprytest="'{@path}'.search(/^s/) != -1;">{@path}</li>
		</ul>
	</div>
</body>
</html>

Source Listing - This li will only be output if the first letter of the value of {@path} starts with the character 's'.

The value of this sprytest attribute can be any JavaScript expression that evaluates to zero/false or some non-zero value. If the expression returns a non-zero value, the content will be output. Keep in mind that since we are using XHTML, that any special characters like '&', '<' or '>' that may be used in a JavaScript expression will need to be converted to HTML entities. You can also use data references inside this JavaScript expression and the dynamic region processing engine will plug in the actual values from a data set just before evaluating the sprytest expression.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Dynamic Region Example</title>
<script type="text/javascript" src="../../includes/xpath.js"></script>
<script type="text/javascript" src="../../includes/SpryData.js"></script>
<script type="text/javascript">
var dsPhotos = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo");
</script>
</head>
<body>
	<div>
		<ul>
			<li>sun.jpg</li>
			<li>surf.jpg</li>
		</ul>
	</div>
</body>
</html>

Source Listing - Only paths that begin with 's' are written to the final output.

Conditional Constructs

Dynamic regions currently support 2 conditional constructs. The first is "spryif":

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Dynamic Region Example</title>
<script type="text/javascript" src="../../includes/xpath.js"></script>
<script type="text/javascript" src="../../includes/SpryData.js"></script>
<script type="text/javascript">
var dsPhotos = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo");
</script>
</head>
<body>
	<div spryregion="dsPhotos">
		<ul class="spryrepeat">
			<li spryif="'{@path}'.search(/^s/) != -1;">{@path}</li>
		</ul>
	</div>
</body>
</html>

Source Listing - The li is only written out if the value of {@path} begins with the character 's'.

To make an element conditional, add an "spryif" attribute to the element with a value that is a JavaScript expression that returns zero or non-zero values. A non-zero value returned by the JavaScript expression will result in the element being written to the final output.

If you need an if/else construct, use the "sprychoose" construct:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Dynamic Region Example</title>
<script type="text/javascript" src="../../includes/xpath.js"></script>
<script type="text/javascript" src="../../includes/SpryData.js"></script>
<script type="text/javascript">
var dsPhotos = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo");
</script>
</head>
<body>
	<div spryregion="dsPhotos">
		<div sprychoose="sprychoose">
			<div sprywhen="'{@path}' == 'surf.gif'">{@path}</div>
			<div sprywhen="'{@path}' == 'undefined'">Path was not defined.</div>
			<div sprydefault="sprydefault">Unexpected value for path!</div>
		</div>
	</div>
</body>
</html>

Source Listing - Example use of the sprychoose construct to color every other div.

The "sprychoose" construct provides functionality equivalent to a case statement, or an if/else if/else construct. To create an "sprychoose" structure add an "sprychoose" attribute to an element. Next, add one or more child elements with "sprywhen" attributes on them. The value of an "sprywhen" attribute should be a JavaScript expression that returns a zero or non-zero value.. If you want to have a default case, just in case all of the JS expressions for each sprywhen attribute returns zero/false, add an element with an "sprydefault" attribute. The "sprydefault" attribute doesn't require a value, but XHTML states that all attributes must have a value, so we use the convention of setting the value of the attribute equal to its name.

The region processing engine will evaluate the "sprywhen" attribute of each node in the order they are listed under their parent element. The "sprydefault" element is always evaluated last, and only if no "sprywhen" expression returned a non-zero value.


Master/Detail Relationships

When working with data, many applications/web pages use a Master/Detail display pattern. The basic idea is that you have one region of the page, called the master, that drives the display of another region, the detail. Typically, the master region displays an abbreviated form of a set of records, and the detail region displays more information about the current record.

Master/Detail

Figure - The table on the left is a master region, the detail region is to the right of it.

Master/Detail Changed

Figure - As the user selects a different object in the master region, the detail region updates.

The Spry framework makes it really easy to set up master/detail relationships between 2 or more dynamic regions of the page by taking advantage of a few key facts:

As you may have noticed, the data set is the one common thing between all of the facts mentioned above, so the techniques used to establish a master/detail relationship all center around the use of one or more data sets.

Lets walk through a few examples that show how to take advantage of these facts.

Example 1: Use "sprydetailregion" When the Master and Detail Share the Same Data Set

In this example the master and detail regions both rely on data from the same data set. You can see a working version of this example here.

The master region is a span that consists of a single select form widget that displays a list of all employee "usernames".

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Master/Detail Same Data Set</title>
<script type="text/javascript" src="../../includes/xpath.js"></script>
<script type="text/javascript" src="../../includes/SpryData.js"></script>
<script type="text/javascript">
var dsEmployees = new Spry.Data.XMLDataSet("../data/employees-01.xml", "/employees/employee");
</script>
</head>
<body>
<span spryregion="dsEmployees">
<select spryrepeatchildren="dsEmployees" onchange="dsEmployees.setCurrentRow(this.value)">
	<option spryif="{ds_RowNumber} == 0" value="{ds_RowID}" selected="selected">{username}</option>
	<option spryif="{ds_RowNumber} != 0" value="{ds_RowID}">{username}</option>
</select>
</span>
<span sprydetailregion="dsEmployees">{@id} - {firstname} {lastname} - {phone} </span>
</body>
</html>

Source Listing - The master region contains a single select widget.

A select option is generated for each row in the dsEmployees data set, its value is set to the ds_RowID of the row it is associated with, and the display value for the option will be the "username" field of the row. A couple of "spryif" options are necessary so we can add a "selected" attribute to the first option in the select widget to make sure that it is what shows by default when the widget is first displayed.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Master/Detail Same Data Set</title>
<script type="text/javascript" src="../../includes/xpath.js"></script>
<script type="text/javascript" src="../../includes/SpryData.js"></script>
<script type="text/javascript">
var dsEmployees = new Spry.Data.XMLDataSet("../data/employees-01.xml", "/employees/employee");
</script>
</head>
<body>
<span>
<select onchange="dsEmployees.setCurrentRow(this.value)">
	<option value="0" selected="selected">esmith</option>
	<option value="1">njohnson</option>
	<option value="2">swilliams</option>
	<option value="3">jjones</option>
	<option value="4">jbrown</option>
</select>
</span>

<span>123456 - Edward Smith - (415) 333-0235 </span>
</body>
</html>

Source Listing - After the master region is processed/regenerated, it looks something like this.

The detail region is a span that will display the employee id, first name, last name and phone number of the username that is selected in the widget.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Master/Detail Same Data Set</title>
<script type="text/javascript" src="../../includes/xpath.js"></script>
<script type="text/javascript" src="../../includes/SpryData.js"></script>
<script type="text/javascript">
var dsEmployees = new Spry.Data.XMLDataSet("../data/employees-01.xml", "/employees/employee");
</script>
</head>
<body>
<span spryregion="dsEmployees">
<select spryrepeatchildren="dsEmployees" onchange="dsEmployees.setCurrentRow(this.value)">
	<option spryif="{ds_RowNumber} == 0" value="{ds_RowID}" selected="selected">{username}</option>
	<option spryif="{ds_RowNumber} != 0" value="{ds_RowID}">{username}</option>
</select>
</span>
<span sprydetailregion="dsEmployees">{@id} - {firstname} {lastname} - {phone} </span>
</body>
</html>

Source Listing - The detail region displays more information for the username currently selected in the select widget.

You'll notice that the detail region uses an "sprydetailregion" attribute instead of an "spryregion" attribute to tell the framework that it is a dynamic region. "spryregion" and "sprydetailregion" are identical, except that dynamic regions with an "sprydetailregion" will regenerate themselves when they receive a "CurrentRowChanged" notification from any of the data sets they are bound to.

Master/Detail sprydetailregion

Figure - Regions with an "sprydetailregion" attribute also listen for CurrentRowChanged notifications.

As the user changes the value of the select widget, the onchange handler of the widget is triggered.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Master/Detail Same Data Set</title>
<script type="text/javascript" src="../../includes/xpath.js"></script>
<script type="text/javascript" src="../../includes/SpryData.js"></script>
<script type="text/javascript">
var dsEmployees = new Spry.Data.XMLDataSet("../data/employees-01.xml", "/employees/employee");
</script>
</head>
<body>
<span spryregion="dsEmployees">
<select spryrepeatchildren="dsEmployees" onchange="dsEmployees.setCurrentRow(this.value)">
	<option spryif="{ds_RowNumber} == 0" value="{ds_RowID}" selected="selected">{username}</option>
	<option spryif="{ds_RowNumber} != 0" value="{ds_RowID}">{username}</option>
</select>
</span>
<span sprydetailregion="dsEmployees">{@id} - {firstname} {lastname} - {phone} </span>
</body>
</html>

Source Listing - As the value of the select widget changes, the current row of the dsEmployees data set is changed.

The onchange handler simply calls the setCurrentRow() method on the dsEmployees data set and gives it the ID of the row in the data set that it is associated with. This results in a CurrentRowChanged notification being fired off to the detail region, causing it to regenerate itself.

Master/Detail sprydetailregion notification

Figure - The master region sets the current row which triggers a CurrentRowChanged notification to the detail region.

Example 2: Master and Detail Use 2 Different Data Sets

In this example we have one data set that will be used to display a list of states, and a second data set that will be used to display the list of cities for a given state. Because the list of cities for a given state can be quite long, the data for the second data set will be fetched "on-the-fly" whenever the current state changes. You can see a working version of example 2 here.

Since the list of states and cities can be quite long, this example will use select widgets to display information. As the user selects a state from the "State" select widget, the "Cities" select widget will automatically update with the list of cities for that state.

We start by creating the 2 data sets we need. One that will provide our list of states (dsStates) and one that will provide the list of cities for a given state (dsCities).

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>States Example</title>
<script type="text/javascript" src="../../includes/xpath.js"></script>
<script type="text/javascript" src="../../includes/SpryData.js"></script>
<script type="text/javascript">
var dsStates = new Spry.Data.XMLDataSet("../data/states/states.xml", "states/state");
var dsCities = new Spry.Data.XMLDataSet("../data/states/{dsStates::url}", "state/cities/city");
</script>
</head>
<body>
</body>
</html>

Source Listing - The URL for dsCities depends on data from the dsStates data set.

If you look at the URL for dsCities, you'll notice that it has a data reference "{dsStates::url}" in it. The URL and XPath arguments to the XMLDataSet constructor are allowed to contain data references to other data sets, as long as these data references don't create a circular dependency between the data sets. That is you can't have data set 'B' depend on data set 'A', if 'A' already has a data reference to 'B'. Also, you can't have data set 'B' depend on data set 'A' if 'A' depends on data set 'C' and data set 'C' depends on 'B'.

If a data set has a URL or XPath with data references in it, the data set will register itself as an observer on every data set used by a data reference. This allows the data set to reload its data or reapply its XPath whenever any of the data set dependencies fire off a DataChanged or CurrentRowChanged notification. The data references will be converted to real data just before the data set tries to load the data at the given url or applies the XPath to the resulting XML DOM tree.

It should be noted that in this example, the URL "data/states/{dsStates::url}" results in a path to a particular sample XML file that contains data for a given state, but had this data come from a real web service or application, we could've easily used an URL that looked something like "/webapp/cities.php?stateid={dsStates::@id}".

Now that we've set up our data sets, lets set up our master and detail regions. The master region will consist of a select form widget that displays the list of states that it gets from the dsStates data set..

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>States Example</title>
<script type="text/javascript" src="../../includes/xpath.js"></script>
<script type="text/javascript" src="../../includes/SpryData.js"></script>
<script type="text/javascript">
var dsStates = new Spry.Data.XMLDataSet("../data/states/states.xml", "states/state");
var dsCities = new Spry.Data.XMLDataSet("../data/states/{dsStates::url}", "state/cities/city");
</script>
</head>
<body>
<form name="selectForm">
	State:
	<span spryregion="dsStates" id="stateSelector">
		<select spryrepeatchildren="dsStates" name="stateSelect">
			<option spryif="{ds_RowNumber} == 0" value="{ds_RowID}" selected="selected">{name}</option>
			<option spryif="{ds_RowNumber} != 0" value="{ds_RowID}">{name}</option>
		</select>
	</span>
</form>
</body>
</html>

Source Listing - The master region displays a select widget that is the list of states.

The detail region will consist of a select form widget that displays the list of cities for the current state that it gets from the dsCities data set.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>States Example</title>
<script type="text/javascript" src="../../includes/xpath.js"></script>
<script type="text/javascript" src="../../includes/SpryData.js"></script>
<script type="text/javascript">
var dsStates = new Spry.Data.XMLDataSet("../data/states/states.xml", "states/state");
var dsCities = new Spry.Data.XMLDataSet("../data/states/{dsStates::url}", "state/cities/city");
</script>
</head>
<body>
<form name="selectForm">
	State:
	<span spryregion="dsStates" id="stateSelector">
		<select spryrepeatchildren="dsStates" name="stateSelect">
			<option spryif="{ds_RowNumber} == 0" value="{ds_RowID}" selected="selected">{name}</option>
			<option spryif="{ds_RowNumber} != 0" value="{ds_RowID}">{name}</option>
		</select>
	</span>
	City:
	<span spryregion="dsCities" id="citySelector">
		<select spryrepeatchildren="dsCities" name="citySelect">
			<option spryif="{ds_RowNumber} == 0" value="{name}" selected="selected">{name}</option>
			<option spryif="{ds_RowNumber} != 0" value="{name}">{name}</option>
		</select>
	</span>
</form>
</body>
</html>

Source Listing - The detail region displays a select widget that is the list of cities for a given state.

At this point, we now have the following observer relationships between our data sets and dynamic regions:

Master/Detail Observer Relationships

Figure - Observer relationships between the data sets and dynamic regions.

To get the "City" select widget to update automatically whenever the user selects a new state from the "State" select widget, we need to add an onchange handler to the "State" select widget.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>States Example</title>
<script type="text/javascript" src="../../includes/xpath.js"></script>
<script type="text/javascript" src="../../includes/SpryData.js"></script>
<script type="text/javascript">
var dsStates = new Spry.Data.XMLDataSet("../data/states/states.xml", "states/state");
var dsCities = new Spry.Data.XMLDataSet("../data/states/{dsStates::url}", "state/cities/city");
</script>
</head>
<body>
<form name="selectForm">
	State:
	<span spryregion="dsStates" id="stateSelector">
		<select spryrepeatchildren="dsStates" name="stateSelect" onchange="document.forms[0].citySelect.disabled = true; dsStates.setCurrentRow(this.value);">
			<option spryif="{ds_RowNumber} == 0" value="{ds_RowID}" selected="selected">{name}</option>
			<option spryif="{ds_RowNumber} != 0" value="{ds_RowID}">{name}</option>
		</select>
	</span>
	City:
	<span spryregion="dsCities" id="citySelector">
		<select spryrepeatchildren="dsCities" name="citySelect">
			<option spryif="{ds_RowNumber} == 0" value="{name}" selected="selected">{name}</option>
			<option spryif="{ds_RowNumber} != 0" value="{name}">{name}</option>
		</select>
	</span>
</form>
</body>
</html>

Source Listing - Add an onchange event handler to the State select widget.

This onchange handler disables the City select widget and then sets the current row on the dsStates data set. This triggers the dsStates data set to fire off a CurrentRowChanged notification, causing the dsCities data set to reload its data. When the dsCities data set finishes loading its data, it fires off a DataChanged notification, causing the detail region to regenerate itself, resulting in the creation of a new "City" select widget that contains all of the cities for the state in the current row of the dsState data set.

Master/Detail URL/XPath data reference auto update

Figure - Setting the current row on data set 'A' sets off a chain of events that causes the detail region to automatically update.


Behavior Attributes

Behavior attributes can be placed on elements within a dynamic region to automatically enable common behaviors that would ordinarily require some manual programming. Currently, only 2 behavior attributes are supported:

spryhover

The value for the "spryhover" attribute should be the name of the class to put on the element whenever the mouse enters or exits the element.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Behavior Attributes Example</title>
<style>
.myHoverClass {
	background-color: yellow;
}
</style>
<script type="text/javascript" src="../../includes/xpath.js"></script>
<script type="text/javascript" src="../../includes/SpryData.js"></script> <script type="text/javascript"> var dsEmployees = new Spry.Data.XMLDataSet("../data/employees-01.xml", "/employees/employee"); </script> </head> <body> <div spryregion="dsEmployees"> <ul> <li spryrepeat="dsEmployees" spryhover="myHoverClass">{username}</li> </ul> </div> </body> </html>

Source Listing - Whenever the mouse enters an li element, the "myHoverClass" class name will be added to the elements class attribute. It will automatically be removed when the mouse exits the element.

spryselect

The value for the "spryselect" attribute should be the name of the class to put on the element whenever the mouse is clicked over the element, which causes the element to appear selected.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Behavior Attributes Example</title>
<style>
.myHoverClass {
	background-color: yellow;
}
.mySelectClass {
	color: white;
	background-color: black;
}
</style>
<script type="text/javascript" src="../../includes/xpath.js"></script>
<script type="text/javascript" src="../../includes/SpryData.js"></script>
<script type="text/javascript">
var dsEmployees = new Spry.Data.XMLDataSet("../data/employees-01.xml", "/employees/employee");
</script>
</head>
<body>
<div spryregion="dsEmployees">
	<ul>
		<li spryrepeat="dsEmployees" spryhover="myHoverClass" spryselect="mySelectClass">{username}</li>
	</ul>
</div>
</body>
</html>

Source Listing - Whenever the mouse is clicked over an li element, the "mySelectClass" class name will be added to the elements class attribute.

If an element on the page with an "spryselect" attribute was previously selected, the class name used as the value for its "spryselect" attribute will automatically be removed, in effect unselecting that element.

You can use a "spryselectgroup" attribute in conjunction with an "spryselect" attribute to allow you to have more than one set of selections on a page. See the RSS Reader example that is distributed with the framework for a working example of this.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Behavior Attributes Example</title>
<style>
.myHoverClass {
	background-color: yellow;
}
.mySelectClass {
	color: white;
	background-color: black;
}
.myOtherSelectClass {
	color: white;
	background-color: black;
}
</style><script type="text/javascript" src="../../includes/xpath.js"></script>
<script type="text/javascript" src="../../includes/SpryData.js"></script> <script type="text/javascript"> var dsEmployees = new Spry.Data.XMLDataSet("../data/employees-01.xml", "/employees/employee"); </script> </head> <body> <div spryregion="dsEmployees"> <ul> <li spryrepeat="dsEmployees" spryhover="myHoverClass" spryselect="mySelectClass" spryselectgroup="username">{username}</li> </ul> <ul> <li spryrepeat="dsEmployees" spryhover="myHoverClass" spryselect="myOtherSelectClass" spryselectgroup="firstname">{firstname}</li> </ul> </div> </body> </html>

Source Listing - Use an spryselectgroup attribute to group elements that effect each other.

The value of an "spryselectgroup" attribute is an arbitrary name. The idea is that any element that uses the same name for its "spryselectgroup" attribute will automatically become unselected when another element with the same select group name is clicked. Other elements with differing "spryselectgroup" values are unaffected.


Copyright © 2006. Adobe Systems Incorporated.
All rights reserved.