Layout Management

One of XML's best features is that it enforces global consistency. This is easily implemented through the use of the controller design pattern. In this section we will use XML to define a unique layout model for designing a simple UI. If you recall the Noverant Web site in Chapter 5, the controller pattern prescribes a single front-end to all page requests in the application. The controller has the following two main jobs:

  1. Gather the XML that makes up the content of the requested page. Often, this task is not handled directly by the controller. Doing so would make the controller too complex. Delegating this responsibility to view helpers that are specifically designed for each view is easier.
  2. Apply presentation rules to the content by transforming the XML content into a final document to be delivered to the client. The presentation engine is free to produce any type of document suited to a particular context. It might create an HTML document intended for a Web browser, a Wireless Markup Language (WML) document for a wireless device, a PDF or Microsoft Excel document for reporting purposes, an XML document to be processed further by another application, or just plain text.

A Simple Detail View

To begin our examination of the controller, let's look at a simple XML document that will serve as a foundation for our document model. Listing 6-3 is an excerpt from the Noverant back-office application in which a user has requested detailed information about a particular CD.

Listing 6-3 CDDetail1.xml: a generic document describing how a CD should be displayed.

<document>
  <header>
    <title>This is our first XML application page</title>
    <icon>document.gif</icon>
  </header>
  <section>
    <header>
      <title>Hello World</title>
      <icon>world.gif</icon>
    </header>
    <view>
      <properties>
        <property description="Property 1">Hello World</property>
      </properties>
    </view>
  </section>
</document>

The <document> element indicates the beginning and end of the content in this document. Like the <html> element in HTML, the <document> element is a container for all the information underneath it. The <document> element might contain a <header> element with a title and an icon.

The <section> element represents an area of content on the screen. Zero, one, or more <section> elements might appear inside a <document> element. The <section> element might also have a <header> element and one or more <view> elements beneath it. A <view> element can house many forms of content. The content in a <view> might be generic (for example, XML documents that describe property/value pair listings or generic input forms) or it might be a specific entity (for example, an XML document that describes a specific business object).

In this example our document contains a single <section> and <view> and a small property sheet. Suppose we transform this document into an HTML page suitable for rendering in our application. We will create an XSLT style sheet to perform this transformation. The first lines of the style sheet are used for initialization. The <xsl:stylesheet> element signifies the beginning of a transformation, and the <xsl:output> element indicates that the result of this transformation will be an HTML document.

<xsl:stylesheet xmlns:xsl=/1999/XSL/
Transform" version="1.0"> <xsl:output method="html" />

First we will match the root <document> node and initialize our HTML document. After inserting the <html> element the transformation creates a <head> element and provides a link to the CSS page that will be used to govern fine-grained formatting in the application. The guts of the transformation affect the body of the document. Inside the <body> element, the transformation creates a <div> element to house the rest of the information in the page. Notice that this master <div> inherits style attributes from the document class that appears in our CSS. The final step is to call <xsl:apply-templates/> to delegate further transformations to more precise rules.

<xsl:template match="document">
  <html>
    <head>
      <link rel="stylesheet" type="text/css" href="css/style.css"/>
    </head>
    <body>
      <div class="document">
        <xsl:apply-templates/>
      </div>
    </body>
  </html>
</xsl:template>

The document's header contains information about the page as a whole. The header might contain a title and a link to an icon. This information will be displayed at the top of the screen. The document header template matches on the "document/header" XPath expression to distinguish a <document> element's header from a <section> element's header.

The first task of this transformation template is to create a division for the header. The division links to the documentHeader class in our style sheet. Next the template checks for the existence of an <icon> element, transforming it into an <img> element if it exists. Notice that the transformation inserts an absolute path to the image directory before inserting the value of the icon. Next the template gets the value of the <title> element and inserts it into the result document.

<xsl:template match="document/header">
  <div class="documentHeader">
    <xsl:if test="icon">
      <img align="absmiddle" src="images/{icon}"/>
    </xsl:if>
    <xsl:value-of select="title"/>
  </div>
</xsl:template>

The section template is similar to the document template. It creates a wrapper division with padding to set it apart from other sections that might appear in the page. It then creates a new division, referring to the section CSS class and makes a recursive call to <xsl:apply-templates>.

<xsl:template match="section">
  <div style="padding:20px;" align="center">
    <div class="section">
      <xsl:apply-templates/>
    </div>
  </div>
</xsl:template>

The section header template is, again, similar to the document header template. It creates a new division and inserts the icon and title found in the XML document.

<xsl:template match="section/header">
  <div class="sectionHeader">
    <xsl:if test="icon">
      <img align="absmiddle" src="images/{icon}"/>
    </xsl:if>
    <xsl:value-of select="title"/>
  </div>
</xsl:template>

In this example the view template does not need to do anything other than look for further templates to apply. This will be extended in later examples.

<xsl:template match="view">
  <xsl:apply-templates/>
</xsl:template>

We will transform the document's <properties> and <property> elements into a simple table containing name/value pairs. The properties template sets up the beginning and ending <table> elements and defers to the property template to create the individual rows. Properties might also include hyperlinks to other documents. The XSLT tests for the existence of an href attribute and creates a hyperlink if one exists.

<xsl:template match="properties">
  <table cellpadding="5">
    <xsl:apply-templates/>
  </table>
</xsl:template>
<xsl:template match="property">
  <tr>
    <td bgcolor="a1a1a1">
      <font class="text"><b><xsl:value-of select="@description"/></b></font>
    </td>
    <td><font class="text">
      <xsl:choose>
        <xsl:when test="@href"><a href="{@href}"><xsl:value-of select="."/></a></xsl:when>
        <xsl:otherwise><xsl:value-of select="."/></xsl:otherwise>
      </xsl:choose>
    </font></td>
  </tr>
</xsl:template>
</xsl:stylesheet>
Try to use <xsl:apply-templates/> instead of <xsl:for-each/> wherever possible. This syntax will allow you to mix and match components more easily.

The following CSS example, Listing 6-4, contains all the CSS rules for this document.

Listing 6-4 Style.css: a CSS for providing fine-grained element formatting.

body {
  background-color: background;
}
.text {
  font-family: Verdana;
  font-size:9pt;
  color: black;
}
.document {
  background-color: background;
  border:0px;
  width: 100%;
}
.documentHeader {
  border:1px solid;
  width: 100%;
  background-color:778899;
  border-bottom:1px solid;
  font-weight: bold;
  color:white;
  font-family: Verdana;
  font-size:9pt;
}
.section {
  border:0px;
  width: 99%;
  text-align: left;
  background-color:white;
  border: 3px;
  border-style: outset;
  border-color: e0e0e0;
  color:white;
  font-family: Verdana;
  font-size:9pt;
}
.sectionHeader {
  background-color: activeCaption;
  color: captionText;
  align: left;
  border:0px;
  width: 100%;
  border-bottom:1px solid;
  font-family: Verdana;
  font-size:9pt;
}
Although XSLT is the language of choice for most XML transformations, CSS should still be used to isolate formatting rules for your HTML documents.

What does the XML document look like after the XSL and CSS styling rules are applied? Figure 6-1 shows the results of our transformation.

Figure 6-1 Microsoft Internet Explorer displaying a simple application page transformation.

What have we accomplished here? For starters, we have created a 30-line HTML document out of an 18-line XML document. Although the addition of 12 lines does not sound like much, it amounts to a 60 percent inflation ratio. Therefore, we see that XML can do much of the "dirty work" of page creation for you. A layout specialist needs only to program the XSLT once and the XML engine will create and recreate thousands of lines of fine-tuned HTML code. You don't have to worry about un-closed tags (XSLT requires that all style sheets are well formed, and this feature catches a lot of HTML errors) or plenty of copied-and-pasted formatting logic running rampant in your application. Furthermore, global style changes need to be applied only a single time to a single document to affect every page.

Another detail to notice in this example is that the source XML document is simple. It boils everything down to the essential content to be displayed on the page. This becomes more evident when we look at a more intricate example.

A More Detailed View

Consider the Noverant CD On-Line storefront from the previous example. Presumably, Noverant uses a back-office application to maintain its database of albums and artists. This application allows Noverant employees to browse the inventory, update the database, and easily link to more detailed information. Listing 6-5 is an example of an XML document that contains information about one of the popular albums in the collection.

Listing 6-5 CDDetail2.asp: XML document containing a second section element to show the CD's track listing.

<document>
  <header>
    <title>The Beatles - Revolver</title>
    <icon>document.gif</icon>
  </header>
  <section>
    <header>
      <title>General Information</title>
      <icon>world.gif</icon>
    </header>
    <view>
      <properties>
        <property description="Artist">The Beatles</property>
        <property description="Album">Revolver</property>
        <property description="Label">EMI Records Ltd</property>
        <property description="Year Released">1966</property>
        <property description="Price">$13.99</property>
      </properties>
    </view>
  </section>
  <section>
    <header>
      <title>Track Listing</title>
      <icon>world.gif</icon>
    </header>
    <view>
      <properties>
        <property description="Track 1">Taxman [2:39] (Harrison)</property>
        <property description="Track 2">Eleanor Rigby [2:07] (Lennon/McCartney)</property>
        <property description="Track 3">I'm Only Sleeping [3:01] (Lennon/McCartney)</property>
        <property description="Track 4">Love You To [3:01] (Harrison)</property>
        <property description="Track 5">Here, There, and Everywhere [2:25] (Lennon/McCartney)</property>
        <property description="Track 6">Yellow Submarine [2:40] (Lennon/McCartney)</property>
        <property description="Track 7">She Said She Said [2:37] (Lennon/McCartney)</property>
        <property description="Track 8">Good Day Sunshine [2:09] (Lennon/McCartney)</property>
        <property description="Track 9">And Your Bird Can Sing [2:01] (Lennon/McCartney)</property>
        <property description="Track 10">For No One [2:01] (Lennon/McCartney)</property>
        <property description="Track 11">Doctor Robert [2:15] (Lennon/McCartney)</property>
        <property description="Track 12">I Want to Tell You [2:29] (Harrison)</property>
        <property description="Track 13">Got to Get You Into My Life [2:30] (Lennon/McCartney)</property>
        <property description="Track 14">Tomorrow Never Knows [2:57] (Lennon/McCartney)</property>
      </properties>
    </view>
  </section>
</document>

This XML document, when transformed with the XSLT listed earlier, produces an HTML page with two content areas. This page is shown in Figure 6-2. Notice that the transformation took care of positioning and styling the information in the result document.

Figure 6-2 A more interesting example of an XSLT-generated application page

These rules can be modified easily by tweaking the XSLT. Suppose that Noverant would like to preserve real estate on its Web pages by adopting a "tab-folder" approach to content layout (instead of stacking content sections on top of one another). This can be achieved by modifying the section template of our XSLT.

Layout specialists can begin experimenting with different rendering techniques once your XML schema has been defined. Little cost is associated with making global styling changes, as these rules are localized in your XSLT document.

A code listing of the new version of the transformation follows. JavaScript was added to the top of the page to handle tab switching. The section template has been augmented to hide all sections except the first one and to display a row of buttons that activate other content sections. Two new classes, tabButton and activeTabButton, were added to the CSS to control the formatting of the tab buttons.

<xsl:stylesheet xmlns:xsl=/1999/XSL/Transform" 
version="1.0"> <xsl:output method="html" indent="yes" /> <xsl:template match="document">   <html>     <head>       <link rel="stylesheet" type="text/css" href="css/style.css"/>       <xsl:if test="header/title"><title><xsl:value-of select="header/title"/></title></xsl:if>

The following script implements the "tabbing" action in the screen. The function accepts the ID of a tab, which is brought to the foreground by setting its display property. The function then iterates over the other tabs, making them invisible by setting their display style properties to none.

      <script language="javaScript">
        <![CDATA[
          function showTab(section, scnt) {
            for (i=0; i < scnt; i++) {
              t = document.getElementById("s_"+i);
              if (t) {
                if (i == section) {
                  t.style.display = '';
                } else {
                  t.style.display = 'none';
                }
              }
            }
          }
        ]]>
      </script>
    </head>
    <body>
      <div class="document">
        <xsl:apply-templates/>
      </div>
    </body>
  </html>
</xsl:template>
<xsl:template match="document/header">
  <div class="documentHeader">
    <xsl:if test="icon">
      <img align="absmiddle" src="images/{icon}"/>
    </xsl:if>
    <xsl:value-of select="title"/>
  </div>
</xsl:template>

The section transformation template contains the bulk of the changes that are necessary to implement tab folders. The template must now keep track of sibling <section> elements to display their tabs at the top. The logic in this template creates darkened tabs for all of the sibling sections and a highlighted tab for the context section. Each of the siblings tabs, or buttons, contains an onClick( ) event handler that calls the showTab( ) JavaScript function to bring that section to the foreground.

<xsl:template match="section">
  <xsl:variable name="numsections" select="count(../section)"/>
  <xsl:variable name="precedingsibs" 
select="count(preceding-sibling::section)"/>
  <xsl:variable name="sectionid" select="concat('s_',$precedingsibs)"/>
  <div align="center">
    <xsl:if test="$precedingsibs = 0">
      <xsl:attribute name="style">padding-top:20px</xsl:attribute>
    </xsl:if>
    <div id="{$sectionid}" align="center" class="section">
      <xsl:if test="$precedingsibs > 0">
        <xsl:attribute name="style">display:none</xsl:attribute>
      </xsl:if>
      <xsl:apply-templates select="header"/>
      <xsl:if test="$numsections > 1">
        <div align="left">

Here the XSLT creates tab buttons for all the previous sections in the document.

          <xsl:for-each select="preceding-sibling::section">
            <xsl:choose>
              <xsl:when test="header/shortTitle">
                <button id="button{position}" class="tabButton" onClick="showTab({position()-1},
{$numsections})"><xsl:value-of select="header/shortTitle"/></button>               </xsl:when>               <xsl:otherwise>                 <button id="button{position}" class="tabButton"
onClick="showTab({position()-1},{$numsections})"><xsl:value-of
select="header/title"/></button>               </xsl:otherwise>             </xsl:choose>           </xsl:for-each>

The XSLT now creates a tab for the current section node. Notice that the CSS class attached to the button has changed to activeTabButton to indicate a selected tab.

          <xsl:choose>
            <xsl:when test="header/shortTitle">
              <button id="button{position}" class="activeTabButton" 
onClick="showTab({$precedingsibs},{$numsections})">
<xsl:value-of select="header/shortTitle"/></button>             </xsl:when>             <xsl:otherwise>               <button id="button{position}" class="activeTabButton"
onClick="showTab({$precedingsibs},{$numsections})">
<xsl:value-of select="header/title"/></button>             </xsl:otherwise>           </xsl:choose>

Here the XSLT template iterates over all of the following <section> elements. These tabs are also marked as inactive.

          <xsl:for-each select="following-sibling::section">
            <xsl:choose>
              <xsl:when test="header/shortTitle">
                <button id="button{position}" class="tabButton" 
onClick="showTab({$precedingsibs+position()},{$numsections})">
<xsl:value-of select="header/shortTitle"/></button>               </xsl:when>               <xsl:otherwise>                 <button id="button{position}" class="tabButton"
onClick="showTab({$precedingsibs+position()},{$numsections})">
<xsl:value-of select="header/title"/></button>               </xsl:otherwise>             </xsl:choose>           </xsl:for-each>         </div>       </xsl:if>       <xsl:apply-templates select="view"/>     </div>   </div> </xsl:template>

The rest of the template remains unchanged.

<xsl:template match="section/header">
  <div class="sectionHeader">
    <xsl:if test="icon">
      <img align="absmiddle" src="images/{icon}"/>
    </xsl:if>
    <xsl:value-of select="title"/>
  </div>
</xsl:template>
<xsl:template match="view">
  <xsl:apply-templates/>
</xsl:template>
<xsl:template match="properties">
  <table cellpadding="5">
    <xsl:apply-templates/>
  </table>
</xsl:template>
<xsl:template match="property">
  <tr>
    <td bgcolor="a1a1a1">
      <font class="text"><b><xsl:value-of select="@description"/>
</b></font>
    </td>
    <td><font class="text">
      <xsl:choose>
        <xsl:when test="@href"><a href="{@href}">
<xsl:apply-templates /></a></xsl:when>
        <xsl:otherwise><xsl:apply-templates /></xsl:otherwise>
      </xsl:choose>
    </font></td>
  </tr>
</xsl:template>
</xsl:stylesheet>

The effects of applying the new transformation to the same source XML document can be seen in Figure 6-3.