List Views

At this point we've seen how you can use XML and XSLT to create generic content blocks of property listings. This is a familiar approach to displaying related information to a user. Sometimes, however, other layout constructs are more appropriate. For example, the CD Track Listing section might be better shown in a list where each row represents a track with separate columns for the track number, title, length, and author. To accomplish this we need to extend our XML schema to support a simple list construct. We will begin by looking at the portion of our XML schema that defines a valid list.

Figure 6-3 A new transformation applied to the Listing 6-5 document to allow tab-based navigation of content sections.

The top level of a list XML document will contain a description and a reference to a default icon that will be displayed on each row. This icon can be overridden by a reference to a row-specific icon.

 <ElementType name="icon" content="textOnly" model="closed"/> <ElementType name="list.description" content="textOnly" model="closed"/> 

The list will also need a collection of columns that define the different types of fields that each row will contain. All columns will have a name, description, and data type. A sorted column will indicate whether or not the data is sorted in ascending or descending order.

 <ElementType name="list.column.sortdirection" content="textOnly" model="closed"/> <ElementType name="list.column.name" content="textOnly" model="closed"/> <ElementType name="list.column.description" content="textOnly" model="closed"/> <ElementType name="list.column.datatype" content="textOnly" model="closed"/> <ElementType name="list.column" order="many" content="eltOnly" model="closed"> <element type="list.column.sortdirection" minOccurs="0"  maxOccurs="1"/> <element type="list.column.name" minOccurs="1" maxOccurs="1"/> <element type="list.column.description" minOccurs="1"  maxOccurs="1"/> <element type="list.column.datatype" minOccurs="1" maxOccurs="1"/> </ElementType> 

A list's collection of column elements will roll up into a parent columns element.

 <ElementType name="list.columns" order="many" content="eltOnly" model="closed"> <element type="list.column" minOccurs="0" maxOccurs="*"/> </ElementType> 

All rows in the list will have a collection of properties representing the data to be shown in the row. The list.property element contains a list.property.name element that matches the value of one of the list.column.name elements as well as a corresponding value element and an optional hyperlink element.

 <ElementType name="list.property.name" content="textOnly" model="closed"/> <ElementType name="list.property.value" content="textOnly" model="closed"/> <ElementType name="list.property.href" content="textOnly" model="closed"/> <ElementType name="list.property" order="many" content="eltOnly" model="closed"> <element type="list.property.name" minOccurs="1" maxOccurs="1"/> <element type="list.property.value" minOccurs="1" maxOccurs="1"/> <element type="list.property.href" minOccurs="0" maxOccurs="1"/> </ElementType> 

No list would be complete without support for grouping rows by a common column value. Summarizing information this way can be an effective means of organizing data. This will be accomplished in our schema by inserting list.group elements that indicate the column being grouped, the common value for items in the same group, and a list of children that share that value.

 <ElementType name="list.group.name" content="textOnly" model="closed"/> <ElementType name="list.group.description" content="textOnly" model="closed"/> <ElementType name="list.group.value" content="textOnly" model="closed"/> <ElementType name="list.children" order="many" content="eltOnly" model="closed"> <element type="list.item" minOccurs="0" maxOccurs="*"/> <element type="list.group" minOccurs="0" maxOccurs="*"/> </ElementType> <ElementType name="list.item" order="many" content="eltOnly" model="closed"> <element type="list.property" minOccurs="0" maxOccurs="*"/> <element type="icon" minOccurs="0" maxOccurs="1"/> </ElementType> <ElementType name="list.group" order="many" content="eltOnly" model="closed"> <element type="list.group.name" minOccurs="1" maxOccurs="1"/> <element type="list.group.description" minOccurs="1" maxOccurs="1"/> <element type="list.group.value" minOccurs="1" maxOccurs="1"/> <element type="list.children" minOccurs="1" maxOccurs="1"/> </ElementType> 

Finally the list element type rolls up all its components into a single node.

 <ElementType name="list" order="many" content="eltOnly" model="closed"> <element type="icon" minOccurs="1" maxOccurs="1"/> <element type="list.description" minOccurs="1" maxOccurs="1"/> <element type="list.columns" minOccurs="1" maxOccurs="1"/> <element type="list.children" minOccurs="0" maxOccurs="1"/> </ElementType>  

Remember to avoid characters such as <, >, and & in your XML data. These characters can creep into your XML pages, particularly when your content is retrieved dynamically from a database, and cause run-time XSLT errors.

Now we replace the bland property-based track listing with one that uses our new list schema. This XML document is much larger than the simpler version we saw before. Generating the code behind such listings can be tedious and error-prone when done by hand. It would be wise to invest time in creating server-side scripts to help automate the process.

 <document> <header> <title>The Beatles - Revolver</title> <icon>CD.gif</icon> </header> 

The first section of this document stays the same as before.

 <section> <header> <title>General Information</title> <icon>world.gif</icon> </header> <view> <properties> <property href="controller.asp?view=artistdetail&amp;id=10001" 
description="Artist">The Beatles</property> <property description="Album">Revolver</property> <property href="controller.asp?view=labeldetail&amp;id=10001"
description="Label">EMI Records Ltd</property> <property description="Year Released">1966</property> <property description="Price">$13.99</property> </properties> </view> </section>

The second section is replaced by an XML list construct. The list view begins with information describing the columns that will appear in the table.

 <section> <header> <title>Track Listing</title> <icon>document.gif</icon> </header> <view> <list> <icon>CD.gif</icon> <list.description>The Beatles / Revolver: Track Listing </list.description> <list.columns> <list.column> <list.column.sortdirection>asc</list.column.sortdirection> <list.column.name>TRACK</list.column.name> <list.column.description>Track</list.column.description> <list.column.datatype>numeric</list.column.datatype> </list.column> <list.column> <list.column.name>TITLE</list.column.name> <list.column.description>Song Title</list.column.description> <list.column.datatype>text</list.column.datatype> </list.column> <list.column> <list.column.name>LENGTH</list.column.name> <list.column.description>Length</list.column.description> <list.column.datatype>text</list.column.datatype> </list.column> <list.column> <list.column.name>WRITER</list.column.name> <list.column.description>Song Writer</list.column.description> <list.column.datatype>text</list.column.datatype> </list.column> </list.columns> 

We create <listitem> elements for each song on the album. Each listitem, or row in the table, contains properties for each column defined in the table. The ordering of the properties within the <listitem> does not matter; the columns collection at the root of the document control the column order.

 <list.children> <list.item> <list.property> <list.property.name>TRACK</list.property.name> <list.property.value>1</list.property.value> </list.property> <list.property> <list.property.name>TITLE</list.property.name> <list.property.value>Taxman</list.property.value> <list.property.href>controller.asp?view= trackdetail&amp;id=10001</list.property.href> </list.property> <list.property> <list.property.name>LENGTH</list.property.name> <list.property.value>3:01</list.property.value> </list.property> <list.property> <list.property.name>WRITER</list.property.name> <list.property.value>Harrison</list.property.value> </list.property> </list.item> <list.item> <list.property> <list.property.name>TRACK</list.property.name> <list.property.value>2</list.property.value> </list.property> <list.property> <list.property.name>TITLE</list.property.name> <list.property.value>Eleanor Rigby</list.property.value> <list.property.href>controller.asp?view= trackdetail&amp;id=10002</list.property.href> </list.property> <list.property> <list.property.name>LENGTH</list.property.name> <list.property.value>2:07</list.property.value> </list.property> <list.property> <list.property.name>WRITER</list.property.name> <list.property.value>Lennon/McCartney </list.property.value> </list.property> </list.item> <list.item> <list.property> <list.property.name>TRACK</list.property.name> <list.property.value>3</list.property.value> </list.property> <list.property> <list.property.name>TITLE</list.property.name> <list.property.value>I'm Only Sleeping </list.property.value> <list.property.href>controller.asp?view= trackdetail&amp;id=10003</list.property.href> </list.property> <list.property> <list.property.name>LENGTH</list.property.name> <list.property.value>3:01</list.property.value> </list.property> <list.property> <list.property.name>WRITER</list.property.name> <list.property.value>Lennon/McCartney </list.property.value> </list.property> </list.item> <list.item> <list.property> <list.property.name>TRACK</list.property.name> <list.property.value>4</list.property.value> </list.property> <list.property> <list.property.name>TITLE</list.property.name> <list.property.value>Love You To</list.property.value> <list.property.href>controller.asp?view= trackdetail&amp;id=10004</list.property.href> </list.property> <list.property> <list.property.name>LENGTH</list.property.name> <list.property.value>3:01</list.property.value> </list.property> <list.property> <list.property.name>WRITER</list.property.name> <list.property.value>Harrison</list.property.value> </list.property> </list.item> </list.children> </list> </view> </section> </document> 

Finally we need to extend our XSLT so that it understands what it should do with the elements defined in our list schema. The first template will match the list element. Its purpose will be to initialize an HTML table that will contain the list.

 <xsl:template match="list"> <table  cellspacing="0" cellpadding="0"> <tr> <td valign="top" nowrap="yes"> <table width="100%" cellpadding="0" cellspacing="1"> 

After inserting the table in the result document, the transformation creates table headers for each of the <list.column> elements in the XML document.

 <tr bgcolor="a0a0a0" style="padding:2px;spacing-top:0px"> <td width="1%"  align="center"> <img src="/books/4/456/1/html/2/images/envelope.gif" width="16"/> </td> <xsl:for-each select="list.columns/list.column"> <td  align="center" style="color:white; font-weight:bold"> <xsl:value-of select="list.column.description"/> <xsl:variable name="sort-dir" select= "list.column.sortdirection"/> <xsl:if test="($sort-dir = 'asc')"> <img src="/books/4/456/1/html/2/images/up.gif" hspace="4"/> </xsl:if> <xsl:if test="($sort-dir = 'desc')"> <img src="/books/4/456/1/html/2/images/down.gif" hspace="4"/> </xsl:if> </td> </xsl:for-each> </tr>  

The transformation then applies further templates to the list.children collection to generate the table body. Finally the list template appends a footer to the table to provide a summary of the number of lines listed.

 <xsl:apply-templates select="list.children"/> </table> </td> </tr> <tr bgcolor="e0e0e0"> <td > <xsl:variable name="childrencount" select="count (.//list.item)"/> <xsl:if test="$childrencount=1"> <xsl:value-of select="$childrencount"/> item </xsl:if> <xsl:if test="$childrencount!=1"> <xsl:value-of select="$childrencount"/> items </xsl:if> </td> </tr> </table> </xsl:template> 

The job of the list.item template is to build a table row for each <list.item> element in the document. The template loops over the list.columns collection, locates a list.property with a matching name, gets its value from the <list.property.value> element, and fills in a table cell with that value. The template also includes rules to handle embedded icons and hyperlinks.

 <xsl:template match="list.item"> <xsl:variable name="current-item" select="."/> <xsl:variable name="current-row" select="position()"/> <xsl:variable name="id" select="concat('v',generate-id())"/> <tr> <xsl:choose> <xsl:when test="position() mod 2"> <xsl:attribute name="style"> background-color:ffffff <xsl:if test="count(ancestor::list.group) > 0"> ;display:none </xsl:if> </xsl:attribute> </xsl:when> <xsl:otherwise> <xsl:attribute name="style"> background-color:f1f1f1 <xsl:if test="count(ancestor::list.group) > 0"> ;display:none </xsl:if> </xsl:attribute> </xsl:otherwise> </xsl:choose> <xsl:attribute name="id"><xsl:value-of select="$id"/> </xsl:attribute> <td  align="center" valign="top"> <xsl:variable name="iconurl" select="ancestor::list/icon"/> <xsl:choose> <xsl:when test="icon and icon != ''"> <img width="16" height="18"><xsl:attribute name="src">
images/<xsl:value-of select="icon"/></xsl:attribute></img> </xsl:when> <xsl:otherwise> <img width="16" height="18"><xsl:attribute name="src">
images/<xsl:value-of select="$iconurl"/></xsl:attribute></img> </xsl:otherwise> </xsl:choose> </td> <xsl:for-each select="ancestor::list/list.columns/list.column"> <xsl:variable name="current-property" select="list.column.name"/> <xsl:variable name="current-datatype" select= "list.column.datatype"/> <xsl:variable name="current-position" select="position()"/> <xsl:variable name="last-position" select="last()"/> <xsl:variable name="current-description" select= "list.column.description"/> <xsl:for-each select="$current-item/list.property [list.property.name=$current-property]"> <td valign="top"> <xsl:attribute name="title"><xsl:value-of select= "$current-description"/>: <xsl:value-of select="list.item.value"/> </xsl:attribute> <xsl:choose> <xsl:when test="($current-datatype = 'numeric' or $current-datatype='monetary' or $current-datatype='date' or $current-datatype='percentage')"> <xsl:attribute name="align">right</xsl:attribute> </xsl:when> <xsl:otherwise> <xsl:attribute name="align">left</xsl:attribute> </xsl:otherwise> </xsl:choose> <xsl:if test="list.property.value"> <xsl:choose> <xsl:when test="list.property.href != ''"> <xsl:element name="a"> <xsl:attribute name="href"><xsl:value-of
select="list.property.href"/></xsl:attribute> <xsl:value-of select="list.property.value" disable-output-escaping="yes"/>&#160; </xsl:element> </xsl:when> <xsl:otherwise> <xsl:value-of select="list.property.value" disable-output-escaping="yes"/>&#160; </xsl:otherwise> </xsl:choose> </xsl:if> </td> </xsl:for-each> </xsl:for-each> </tr> </xsl:template>

The list.group template is written to handle n-levels of groupings within a list. The template assumes that all items in the list have been grouped prior to the transformation. Therefore, a different program must be used to associate the similar line items to be grouped.

 <xsl:template match="list.group"> 

The following line determines how much space you should use when indenting the group summary lines. The desired stairstep effect can be achieved by multiplying a constant by the number of list.group ancestors the node has.

 <xsl:variable name="padding" select="20 * count(ancestor::list.group)"/> <xsl:variable name="id" select="concat('v',generate-id())"/> 

The template next creates a row to display the group summary information. The row's display style property is set to none if it's a child of another list.group. A plus sign is inserted into the row that, when clicked, will expand the group and display all its children. (The JavaScript code to do this will be listed following this transformation.)

 <tr> <xsl:attribute name="id"><xsl:value-of select="$id"/></xsl:attribute> <xsl:if test="ancestor::list.group"> <xsl:attribute name="style">display:none</xsl:attribute> </xsl:if> <td> <xsl:attribute name="id"><xsl:value-of select="$id"/>_plus </xsl:attribute> <xsl:attribute name="colspan"><xsl:value-of select="1 + 
count(ancestor::list/list.columns/list.column)"/></xsl:attribute> <table width="100%" cellpadding="0" cellspacing="0" border="0" > <xsl:attribute name="id"><xsl:value-of select="$id"/>_table </xsl:attribute> <tr> <xsl:if test="$padding != 0"> <td> <xsl:attribute name="width"> <xsl:value-of select="round($padding)"/></xsl:attribute> <xsl:attribute name="style">background-color:727272 </xsl:attribute> <img src="/books/4/456/1/html/2/images/spacer.gif" height="1"> <xsl:attribute name="width"> <xsl:value-of select="round($padding)"/></xsl:attribute> </img> </td> </xsl:if> <td width="5" style="cursor:hand"> <img src="/books/4/456/1/html/2/images/plus.gif" hspace="4" style="border:1px outset"> <xsl:attribute name="onClick">ExpandCollapse('<xsl:for-each
select="list.children/*[name() = 'list.group' or name() = 'list.item']">
<xsl:value-of select="concat('v',generate-id())" /><xsl:if test="last() > position()">,</xsl:if></xsl:for-each>','<xsl:value-of
select="$id"/><xsl:for-each select=".//*[name() = 'list.group' or name() = 'list.item']">,
<xsl:value-of select= "concat('v',generate-id())"/></xsl:for-each>')</xsl:attribute> <xsl:attribute name="id"><xsl:value-of select="$id" />_plus_image</xsl:attribute> </img> </td>

The summary information, including property description, value, and item count, is inserted into the result document. Finally the template makes a call to <xsl:apply-templates> to transform child list.item and list.group nodes.

 <td align="left" > <xsl:value-of select="list.group.description" />&#160;:&#160;<xsl:value-of select="list.group.value"/> <xsl:variable name="childrencount" select="count (.//list.item)"/> <xsl:if test="$childrencount=1"> (<xsl:value-of select="$childrencount"/> item) </xsl:if> <xsl:if test="$childrencount>1"> (<xsl:value-of select="$childrencount"/> items) </xsl:if> </td> </tr> </table> </td> </tr> <xsl:apply-templates select="list.children/*"/> </xsl:template> 

The table-based implementation of the function shown here is much more complicated than its <div> element-based cousin. Because <div> elements are block displays, their display properties are inherited by all their descendants.

As promised, here is the expandCollapse( ) JavaScript function that opens and closes table groupings. This function is inserted into the document by the XSLT shown previously.

 function ExpandCollapse(c1,ca) { var ca_a = ca.split(','); var c1_a  = c1.split(','); c1 = document.getElementById(ca_a[1]); if (c1.style.display == 'none') expand = true; else expand = false; thePlus = ca_a[0]+"_plus_image"; plus_div = document.getElementById(thePlus); if (expand) { if (plus_div) plus_div.src = "images/minus.gif"; for (i=0; i < c1_a.length; i++) { theRow = document.getElementById(c1_a[i]); theRow.style.display = ''; plus_div = document.getElementById(c1_a[i]+"_plus_image"); if (plus_div) plus_div.src = "images/plus.gif"; } } else { if (plus_div) plus_div.src = "images/plus.gif"; for (i=1; i < ca_a.length; i++) { theRow = document.getElementById(ca_a[i]) theRow.style.display = 'none'; } } } 

To group the CDs shown on our page, the XML markup must be modified to represent the grouping hierarchy. An excerpt from this document is shown in the following listing.

 <list.group> <list.group.name>WRITER</list.group.name> <list.group.description>Song Writer</list.group.description> <list.group.value>Harrison</list.group.value> <list.children> <list.item> <list.property> <list.property.name>TRACK</list.property.name> <list.property.value>1</list.property.value> </list.property> <list.property> <list.property.name>TITLE</list.property.name> <list.property.value>Taxman</list.property.value> <list.property.href>controller.asp?view= trackdetail&amp;id=10001</list.property.href> </list.property> <list.property> <list.property.name>LENGTH</list.property.name> <list.property.value>3:01</list.property.value> </list.property> <list.property> <list.property.name>WRITER</list.property.name> <list.property.value>Harrison</list.property.value> </list.property> </list.item> <list.item> <list.property> <list.property.name>TRACK</list.property.name> <list.property.value>4</list.property.value> </list.property> <list.property> <list.property.name>TITLE</list.property.name> <list.property.value>Love You To</list.property.value> <list.property.href>controller.asp?view= trackdetail&amp;id=10004</list.property.href> </list.property> <list.property> <list.property.name>LENGTH</list.property.name> <list.property.value>3:01</list.property.value> </list.property> <list.property> <list.property.name>WRITER</list.property.name> <list.property.value>Harrison</list.property.value> </list.property> </list.item> <list.item> <list.property> <list.property.name>TRACK</list.property.name> <list.property.value>12</list.property.value> </list.property> <list.property> <list.property.name>TITLE</list.property.name> <list.property.value>I Want to Tell You</list.property.value> <list.property.href>controller.asp?view= trackdetail&amp;id=10012</list.property.href> </list.property> <list.property> <list.property.name>LENGTH</list.property.name> <list.property.value>2:29</list.property.value> </list.property> <list.property> <list.property.name>WRITER</list.property.name> <list.property.value>Harrison</list.property.value> </list.property> </list.item> </list.children> </list.group>  

As you can tell by the following screen shots, a list-based approach works best for laying out multiple items that share similar properties. Lists can also be extended in your application to support user-defined sorting, grouping, filtration, and export. Figure 6-5 shows how an XML list construct is used to display CD tracks. Figure 6-4 shows the CD tracks grouped by author.

Figure 6-4 CD Tracks again, this time grouped by author.



XML Programming
XML Programming Bible
ISBN: 0764538292
EAN: 2147483647
Year: 2002
Pages: 134

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net