I started with the basic Hello World example. This tutorial assumes that sections have been created in Symphony and data sources attached to a page. This example shows how I developed the page template that displays the entries that you are currently reading.
The Hello World example used a skeleton XSL stylesheet to provide the minimum necessary to create a web page with an XHTML 1.0 Strict doctype. For this tutorial, we will be recreating the Journal overview page. We can refer to the XML output shown in the previous entry on XML Output and the Debug Page for the XML data that will be processed by the XSLT template.
First, create the Journal page. Use the following settings to configure the page:
Using a couple of the available page parameters, $website-name
and $page-title
, we can add a page title to the template use an xsl:value-of
instruction.
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" /> <xsl:template match="/"> <html> <head> <title><xsl:value-of select="$page-title"/> | <xsl:value-of select="$website-name"/></title> </head> <body> <h1><xsl:value-of select="$website-name"/></h1> <h2><xsl:value-of select="$page-title"/></h2> </body> </html> </xsl:template> </xsl:stylesheet>
Using an XPath expression, the value of an entry title can be output to the result document. To find the title of every entry in our list of entries, we can use the Entries data source and an xsl:for-each
instruction to select every entry node and output the entry Title field value with an xsl:value-of
instruction.
<xsl:for-each select="data/entries/entry"> <h3><xsl:value-of select="title"/></h3> </xsl:for-each>
To include the date the entry was posted, we can add a paragraph element with another xsl:value-of
instruction.
<xsl:for-each select="data/entries/entry"> <h3><xsl:value-of select="title"/></h3> <p class="meta">Posted <xsl:value-of select="date"/></p> </xsl:for-each>
To include the category assigned to the entry, we can test whether a value exists for the category field and add the value to the output if it does.
<xsl:for-each select="data/entries/entry"> <h3><xsl:value-of select="title"/></h3> <p class="meta">Posted <xsl:value-of select="date"/> <xsl:if test="category"> <xsl:text> in </xsl:text> <xsl:value-of select="category/item"/> </xsl:if> </p> </xsl:for-each>
To include the value of the description
field, we can use a different instruction that includes not only the text value of a node but also the XML nodes contained by the description
node. Since Markdown is being used to format the entries, the description
node of the XML will contain at least <p>
elements, and possibly several different HTML elements. The xsl:copy-of
instruction will output a copy of the selected XML node including the selected element. To select all the child elements of the description
element, without including the body element node itself, use the wildcard selector *
to select all child elements.
<xsl:for-each select="data/entries/entry"> <h3><xsl:value-of select="title"/></h3> <p class="meta">Posted <xsl:value-of select="date"/> <xsl:if test="category"> <xsl:text> in </xsl:text> <xsl:value-of select="category/item"/> </xsl:if> </p> <xsl:copy-of select="description/*"/> </xsl:for-each>
The completed overview page will display the list of available Journal entries with a title, date, category and a brief description of the entry.
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" /> <xsl:template match="/"> <html> <head> <title><xsl:value-of select="$page-title"/> | <xsl:value-of select="$website-name"/></title> </head> <body> <h1><xsl:value-of select="$website-name"/></h1> <h2><xsl:value-of select="$page-title"/></h2> <xsl:for-each select="data/entries/entry"> <h3><xsl:value-of select="title"/></h3> <p class="meta">Posted <xsl:value-of select="date"/> <xsl:if test="category"> <xsl:text> in </xsl:text> <xsl:value-of select="category/item"/> </xsl:if> </p> <xsl:copy-of select="description/*"/> </xsl:for-each> </body> </html> </xsl:template> </xsl:stylesheet>
The completed overview page will display the list of available Journal entries with a title, date, category and brief description of the entry.
To limit the number of entries that is output by the template, the selected node set can be filtered by using a predicate on the xsl:for-each
instruction. The following instruction will select the first entry.
<xsl:for-each select="data/entries/entry[position() = 1]">
The shorthand version omits the need for the equality operator. For example, to select the fourth entry:
<xsl:for-each select="data/entries/entry[4]">
To select the first 4 entries:
<xsl:for-each select="data/entries/entry[position() <= 4]">
To select the last entry:
<xsl:for-each select="data/entries/entry[position() = last()]">
To select an entry by its id
attribute use the attribute selector, @
:
<xsl:for-each select="data/entries/entry[@id = 36]">
To sort the entries that are output by the template, the selected node set can be sorted by using an xsl:sort
instruction. To avoid throwing an XSLT processor error, the xsl:sort
instruction must immediately follow the opening tag of the xsl:for-each
instruction. It is always expressed as a self-closing XML element. The following XSL instructions will sort the entries by the text value of the date field.
<xsl:for-each select="data/entries/entry"> <xsl:sort select="date" order="ascending"/> <h3><xsl:value-of select="title"/></h3> </xsl:for-each>
If more than one entry has been posted on the same day, you might also want to sort the entries by the time to keep the entries in correct chronological order. Use the @
selector to select attributes of an XML element. In this case, we are selecting the time
attribute of the date
element. The following instructions will sort first by the date
field, then by the time
field.
<xsl:for-each select="data/entries/entry"> <xsl:sort select="date" order="ascending"/> <xsl:sort select="date/@time" order="ascending"/> <h3><xsl:value-of select="title"/></h3> </xsl:for-each>
Note that sorting by the date field works correctly here, even though the default sort mode evaluates a value as string data type, only because the ISO format of the date can correctly be sorted as a string value. The default sort order is also ascending
, so we could have omitted the order attribute of the xsl:sort
instruction. To sort by System ID, it is possible to change the sort mode:
<xsl:for-each select="data/entries/entry"> <xsl:sort select="@id" data-type="number"/> <h3><xsl:value-of select="title"/></h3> </xsl:for-each>
To keep the XML efficient and optimized, it is often best to filter and sort entries when configuring data sources. Refer to the Data Sources section in the documentation for more information.
The Symphony Data Sources tutorial describes how to configure the Entry data source to filter the Entries section by the Title field and sort by the Date field.
Symphony uses URL parameters to manage different views of the same data set, or to dynamically modify the XML data set based on page parameters or data source parameters. Using XSLT conditional instructions, it is possible to serve different views using the same page template. When creating pages, URL parameters can be configured. Instead of using traditional PHP GET strings with name/value pairs, such as ?name=value&foo=bar
, Symphony uses clean URLs that are mapped to XSL page parameters configured in the page template. The drawback is that the values have to appear in the order specified by the page template. So, with the GET example, we could configure a page with the URL parameters name/foo
. Then to navigate to a page that is filtered by these two parameters, we could express the values in the URL:
http://www.example.com/journal/value/bar/
You can specify any valid XSL parameter name for each URL parameter. Since Symphony entry handles are created with lowercase characters and hyphens, it’s generally best to stick to this character set. As with any XSL parameter, never begin the name with a number. You could potential use the following as your URL Parameters in the page template: a/b/c/d/e/f/g
and these would be mapped to the following XSL parameters:
To manage different views, however, this would require at least the following logic to display different results for each parameter:
<xsl:choose> <xsl:when test="$g"> <!-- Do something when $g has a value --> </xsl:when> <xsl:when test="$f"> <!-- Do something when $f has a value --> </xsl:when> <xsl:when test="$e"> <!-- Do something when $e has a value --> </xsl:when> <xsl:when test="$d"> <!-- Do something when $d has a value --> </xsl:when> <xsl:when test="$c"> <!-- Do something when $c has a value --> </xsl:when> <xsl:when test="$b"> <!-- Do something when $b has a value --> </xsl:when> <xsl:otherwise> <!-- Do something when $a has a value --> </xsl:otherwise> </xsl:choose>
The xsl:choose
instruction is the XSLT version of a switch statement or if/else conditionals in procedural programming languages.
The Journal page has been configured with a single URL parameter: entry
. To create a page template that will display an overview when the $entry
parameter has no value, that is, when the browser has navigated to the Journal page at http://www.example.com/journal/
, we can use the Entries data source. When the browser has navigated to a specific entry, http://www.example.com/journal/entry-title/
, we can use the Entry data source. An xsl:choose
instruction provides the logic to display an overview of entries or the full selected entry.
<xsl:choose> <xsl:when test="$entry"> <!-- Do something when $entry has a value --> </xsl:when> <xsl:otherwise> <!-- Do something when $entry has no value --> </xsl:otherwise> </xsl:choose>
Apply this logic to the Journal page template, where we filter the entry using XSLT. Notice the predicate in the xsl:for-each
instruction:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" /> <xsl:template match="/"> <html> <head> <title><xsl:value-of select="$page-title"/> | <xsl:value-of select="$website-name"/></title> </head> <body> <h1><xsl:value-of select="$website-name"/></h1> <h2><xsl:value-of select="$page-title"/></h2> <xsl:choose> <xsl:when test="$entry"> <xsl:for-each select="data/entries/entry[title/@handle = $entry]"> <h3><xsl:value-of select="title"/></h3> <p class="meta">Posted <xsl:value-of select="date"/> <xsl:if test="category"> <xsl:text> in </xsl:text> <xsl:value-of select="category/item"/> </xsl:if> </p> <xsl:copy-of select="description/*"/> </xsl:for-each> </xsl:when> <xsl:otherwise> <xsl:for-each select="data/entries/entry"> <h3><xsl:value-of select="title"/></h3> <p class="meta">Posted <xsl:value-of select="date"/> <xsl:if test="category"> <xsl:text> in </xsl:text> <xsl:value-of select="category/item"/> </xsl:if> </p> <xsl:copy-of select="description/*"/> </xsl:for-each> </xsl:otherwise> </xsl:choose> </body> </html> </xsl:template> </xsl:stylesheet>
The problem with this is that the Entries data source does not include all the elements required to display the full entry. We could include all the elements in the XML, but this would not be efficient to have all the data for every entry included in the XML output for the page. The most efficient way to display the full entry would be to prevent the entry from being included in the XML output if the $entry
parameter has no value, but to include the full entry data in the XML output when the $entry
parameter does have a value. To display the entry, simply select the entry node set in the xsl:for-each
instruction.
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" /> <xsl:template match="/"> <html> <head> <title><xsl:value-of select="$page-title"/> | <xsl:value-of select="$website-name"/></title> </head> <body> <h1><xsl:value-of select="$website-name"/></h1> <h2><xsl:value-of select="$page-title"/></h2> <xsl:choose> <xsl:when test="$entry"> <xsl:for-each select="data/entry/entry"> <h3><xsl:value-of select="title"/></h3> <p class="meta">Posted <xsl:value-of select="date"/> <xsl:if test="category"> <xsl:text> in </xsl:text> <xsl:value-of select="category/item"/> </xsl:if> </p> <xsl:copy-of select="description/*"/> </xsl:for-each> </xsl:when> <xsl:otherwise> <xsl:for-each select="data/entries/entry"> <h3><xsl:value-of select="title"/></h3> <p class="meta">Posted <xsl:value-of select="date"/> <xsl:if test="category"> <xsl:text> in </xsl:text> <xsl:value-of select="category/item"/> </xsl:if> </p> <xsl:copy-of select="description/*"/> </xsl:for-each> </xsl:otherwise> </xsl:choose> </body> </html> </xsl:template> </xsl:stylesheet>
So, in this case, the data source does the filtering for us.
Once you understand the basics of configuring sections, data sources and pages, and building templates with XSLT, you should be well on your way to discovering just how flexible Symphony can be for developing sites that go far beyond the traditional blog or brochure site.
DesignProjectX | The digital sandbox of Stephen Bau