Using XSLT can be rather daunting if it's a new language for you. But, once you have a grasp of how to use it, you can whip up custom navigation, RSS feeds, depicted calendars, product lists, and much more with great ease. The good news is that Umbraco comes with a set of predefined templates that you can use to generate standard output such as navigation and sitemaps. What's more is that they serve as a powerful learning tool to get you started on creating more advanced output and tailoring the stylesheets to your needs.
If you're an avid XSL developer you can skip this section because it covers the bare-bone basics of what XSLT is and how it applies to Umbraco. XSLT stands for Extensible Stylesheet Language Transformation and is made up of XSL. XSL is basically a way to render structured data, in the form of XML elements, to the browser. There are a number of other ways to parse XML elements but we won't get into those here; however, you can find out how to parse these elements in Beginning XSLT and XPath: Transforming XML Documents and Data (Wiley, 2009).
Microsoft .NET uses the XSL 1.0 specification. This means that you cannot take advantage of more advanced functions and methods presented in XSL 2.0.
The most important part of learning and using XSLT with Umbraco is understanding how you traverse the cached XML content tree using the XML Path Language, aka XPath. Inspecting and learning the structure of the XML cache will allow you to drill down and specify exactly what parts of the content structure you are looking for. The XML cache file can be found in ~/App_Data/umbraco.config.
In addition, Umbraco uses a parameter, currentPage, that is always available to you. This parameter holds the entire nodeset of the current node where the template is going to be executed, which in turn also includes all child nodes and grandchild nodes. From a performance standpoint this is great because you are not constantly querying the entire XML nodeset from memory.
Finally, you should know that Umbraco comes with something called an XSLT Extension Library, which allows you to easily do things like formatting dates, split strings, truncate strings, and a whole lot more. Of course, on top of that you can also leverage the EXSLT extensions that are part of the XSL parsing libraries in .NET.
All these important XSLT features—XPath, currentPage, and the XSLT Extension Library—are thoroughly examined through various examples, both in the remaining portions of this chapter, as well as throughout the rest of the book.
For more details on XSL and the W3C specification, please visit http://www.w3c.org/TR/xsl.
XSLT macros can ONLY access published content because it looks at the in-memory XML cache found in ~/App_Data/umbraco.config. This XML structure does not include unpublished nodes. To access unpublished content, you have to use either .NET driven macros, DLR (Python and Ruby) driven macros, or add XSLT extensions to access the data from the database.
As mentioned earlier, Umbraco ships with a number of XSLT templates to get you going. When creating a new XSLT file, as shown in Figure 5-6, you can choose to base your new file on one of the templates listed in Table 5-3.
To get an idea of what a template looks like, see Listing 5-1, which shows how the Breadcrumb template is created. Bolded portions of the template show the critical parts of the XSLT file that specify what pages to select and also shows the use of an XSLT extension method from the umbraco.library extensions method library, covered in detail in “Using XSLT Extensions” later in this chapter.
<?xml version=“1.0” encoding=“UTF-8”?> <!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp “ ”>]> <xsl:stylesheet version=“1.0” xmlns:xsl=“http://www.w3.org/1999/XSL/Transform” xmlns:msxml=“urn:schemas-microsoft-com:xslt” xmlns:umbraco.library=“urn:umbraco.library” xmlns:Exslt.ExsltCommon=“urn:Exslt.ExsltCommon” xmlns:Exslt.ExsltDatesAndTimes=“urn:Exslt.ExsltDatesAndTimes” xmlns:Exslt.ExsltMath=“urn:Exslt.ExsltMath” xmlns:Exslt.ExsltRegularExpressions=“urn:Exslt.ExsltRegularExpressions” xmlns:Exslt.ExsltStrings=“urn:Exslt.ExsltStrings” xmlns:Exslt.ExsltSets=“urn:Exslt.ExsltSets” exclude-result-prefixes=“msxml umbraco.library Exslt.ExsltCommon Exslt.ExsltDatesAndTimes Exslt.ExsltMath Exslt.ExsltRegularExpressions Exslt.ExsltStrings Exslt.ExsltSets “> <xsl:output method=“xml” omit-xml-declaration=“yes”/> <xsl:param name=“currentPage”/> <xsl:variable name=“minLevel” select=“1”/> <xsl:template match=“/”> <xsl:if test=“$currentPage/@level > $minLevel”> <ul> <xsl:for-each select=“$currentPage/ancestor::* [@level > $minLevel and string(umbracoNaviHide) != ‘1’]”> <li> <a href=“{umbraco.library:NiceUrl(@id)}”> <xsl:value-of select=“@nodeName”/> </a> </li> </xsl:for-each> <!-- print currentpage --> <li> <xsl:value-of select=“$currentPage/@nodeName”/> </li> </ul> </xsl:if> </xsl:template> </xsl:stylesheet>
To save an XSLT file in the Umbraco backoffice you can use the commonly used keyboard combination, Ctrl+S.
The following steps provide a step-by-step walkthrough of Listing 5-1.
Now that you know the inner workings of this template, let's see how to create this from scratch.
You can now reload the Macros node in the Developers section and see that a Macro was added named Site Breadcrumb, with the newly created XSLT file pre-selected.
So, now what? You have a terrific little macro but need somewhere to put it. The appropriate location to include this macro is in one of your templates, seeing as you want this displayed on all your internal pages and it is context sensitive. The bolded code in Listing 5-2 shows the syntax for including a macro in your template. If you installed Runway during installation in Chapter 1, the best template to place this macro in is Runway Textpage, as shown in Listing 5-2.
LISTING 5-2 RunwayTextpage.master
<%@ Master Language=“C#” MasterPageFile=“~/masterpages/RunwayMaster.master” AutoEventWireup=“true” %> <asp:Content ContentPlaceHolderID=“RunwayMasterContentPlaceHolder” runat=“server”> <div id=“content”> <umbraco:Macro Alias=“SiteBreadcrumb” runat=“server”></umbraco:Macro> <div id=“contentHeader”> <h2><umbraco:Item runat=“server” field=“pageName”/></h2> </div> <umbraco:Item runat=“server” field=“bodyText” /> </div> <div id=“subNavigation”></div> </asp:Content>
There's an easier way to insert the macro tag than typing it in there. To use the built-in Insert Macro tool, follow these steps.
If you've specified macro parameters, clicking the OK button reveals a properties screen asking you to fill in the various fields associated with the macro parameters before the tag is inserted into the template.
Using XSLT extensions will provide you with a whole new set of tools to render and work with content nodes, custom data, and other standard features of .NET. Umbraco ships with the following libraries:
The combination of all these libraries gives you access to hundreds of extension methods to make output of data easier. For the purposes of this book, however, we are going to cover the methods provided in the umbraco.library method library. The Umbraco library contains methods that allow you to access Umbraco data and functions specific to working with Umbraco pages, data types, and document types. It's beyond the scope of this book to describe each and every method in the umbraco.library library, so in Table 5-4 you get an overview of what the most widely used methods can do for you.
You can easily generate your own XSLT extensions library by creating a public class with public static methods in it. For an example of this, please refer to Chapter 12.
To utilize any of the methods in Table 5-4, and the rest of the available methods, simply follow these steps:
Believe it or not, what you've seen so far are the basics. XSLT can get a lot more advanced and complicated. Again, it is not the job of this book to teach you all there is to know about XSLT, but to make your journey a tad more useful, the examples in Listings 5-3 and 5-5 take you through some more common usages of XSLT within Umbraco.
Media items, such as images, files, and folders, can be retrieved using the GetMedia XSLT extension that you were introduced to in the previous section. However, GetMedia returns a nodeset so it's not as simple as just calling the method. In addition, you also need to specify what information you want out of the nodeset. Listing 5-3 shows you how this is done. The steps that follow walk you through the process.
To select a media item to display, simply add the Media Picker data type to one of your document types and you can target a file, folder, or image using that data type when editing a node.
The Media Picker data type stores the chosen media item using the associated node Id for the media item.
LISTING 5-3: MediaOutput-4.5.xslt
<?xml version=“1.0” encoding=“UTF-8”?> <!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp “ ”> ]> <xsl:stylesheet version=“1.0” xmlns:xsl=“http://www.w3.org/1999/XSL/Transform” xmlns:msxml=“urn:schemas-microsoft-com:xslt” xmlns:umbraco.library=“urn:umbraco.library” xmlns:Exslt.ExsltCommon=“urn:Exslt.ExsltCommon” xmlns:Exslt.ExsltDatesAndTimes=“urn:Exslt.ExsltDatesAndTimes” xmlns:Exslt.ExsltMath=“urn:Exslt.ExsltMath” xmlns:Exslt.ExsltRegularExpressions=“urn:Exslt.ExsltRegularExpressions” xmlns:Exslt.ExsltStrings=“urn:Exslt.ExsltStrings” xmlns:Exslt.ExsltSets=“urn:Exslt.ExsltSets” exclude-result-prefixes=“msxml umbraco.library Exslt.ExsltCommon Exslt.ExsltDatesAndTimes Exslt.ExsltMath Exslt.ExsltRegularExpressions Exslt.ExsltStrings Exslt.ExsltSets “> <xsl:output method=“xml” omit-xml-declaration=“yes”/> <xsl:param name=“currentPage”/> <xsl:variable name=“propertyAlias” select=“/macro/propertyAlias”/> <xsl:variable name=“propertyValue” select=“$currentPage/* [name()=$propertyAlias]”/> <xsl:template match=“/”> <!-- check to make sure the property value that we pass in is not empty --> <xsl:if test=“$propertyValue!=‘’”> <!-- localize the media nodeset into a shorter variable name for readability --> <xsl:variable name=“mediaItem” select=“umbraco.library:GetMedia($propertyValue, ‘false’)”/> <xsl:if test=“$mediaItem!=‘’”> <img> <xsl:attribute name=“src”> <xsl:value-of select=“$mediaItem/umbracoFile” /> </xsl:attribute> <xsl:attribute name=“width”> <xsl:value-of select=“$mediaItem/umbracoWidth” /> </xsl:attribute> <xsl:attribute name=“height”> <xsl:value-of select=“$mediaItem/umbracoHeight” /> </xsl:attribute> <xsl:attribute name=“alt”> <xsl:value-of select=“$mediaItem/@nodeName” /> </xsl:attribute> </img> </xsl:if> </xsl:if> </xsl:template> </xsl:stylesheet>
LISTING 5-4: MediaOutput-4.0.x.xslt
<?xml version=“1.0” encoding=“UTF-8”?> <!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp “ ”> ]> <xsl:stylesheet version=“1.0” xmlns:xsl=“http://www.w3.org/1999/XSL/Transform” xmlns:msxml=“urn:schemas-microsoft-com:xslt” xmlns:umbraco.library=“urn:umbraco.library” xmlns:Exslt.ExsltCommon=“urn:Exslt.ExsltCommon” xmlns:Exslt.ExsltDatesAndTimes=“urn:Exslt.ExsltDatesAndTimes” xmlns:Exslt.ExsltMath=“urn:Exslt.ExsltMath” xmlns:Exslt.ExsltRegularExpressions=“urn:Exslt.ExsltRegularExpressions” xmlns:Exslt.ExsltStrings=“urn:Exslt.ExsltStrings” xmlns:Exslt.ExsltSets=“urn:Exslt.ExsltSets” exclude-result-prefixes=“msxml umbraco.library Exslt.ExsltCommon Exslt.ExsltDatesAndTimes Exslt.ExsltMath Exslt.ExsltRegularExpressions Exslt.ExsltStrings Exslt.ExsltSets “> <xsl:output method=“xml” omit-xml-declaration=“yes”/> <xsl:param name=“currentPage”/> <xsl:variable name=“propertyAlias” select=“/macro/propertyAlias”/> <xsl:template match=“/”> <!-- check to make sure the property value that we pass in is not empty --> <xsl:if test=“$propertyAlias!=‘’”> <!-- localize the media nodeset into a shorter variable name for readability --> <xsl:variable name=“mediaItem” select=“umbraco.library:GetMedia($currentPage/data [@alias=$propertyAlias], ‘false’)”/> <xsl:if test=“$mediaItem!=‘’”> <img> <xsl:attribute name=“src”> <xsl:value-of select=“$mediaItem/data [@alias=‘umbracoFile’]” /> </xsl:attribute> <xsl:attribute name=“width”> <xsl:value-of select=“$mediaItem/data [@alias=‘umbracoWidth’]” /> </xsl:attribute> <xsl:attribute name=“height”> <xsl:value-of select=“$mediaItem/data [@alias=‘umbracoHeight’]” /> </xsl:attribute> <xsl:attribute name=“alt”> <xsl:value-of select=“$mediaItem/@nodeName” /> </xsl:attribute> </img> </xsl:if> </xsl:if> </xsl:template> </xsl:stylesheet>
Often times you will want to output lists of information in a certain order and/or grouping. One great example of this is the Event document type that you added in Chapter 3. On the event details page, you may want to output all of the events grouped by month, as shown in the following steps:
See “Creating an XSLT Macro” earlier in this chapter for detailed steps on how to create an XSLT file and associated macro.
LISTING 5-5: EventOutputGroupedByMonth-4.5.xslt
<?xml version=“1.0” encoding=“UTF-8”?> <!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp “ ”> ]> <xsl:stylesheet version=“1.0” xmlns:xsl=“http://www.w3.org/1999/XSL/Transform” xmlns:msxml=“urn:schemas-microsoft-com:xslt” xmlns:umbraco.library=“urn:umbraco.library” xmlns:Exslt.ExsltCommon=“urn:Exslt.ExsltCommon” xmlns:Exslt.ExsltDatesAndTimes=“urn:Exslt.ExsltDatesAndTimes” xmlns:Exslt.ExsltMath=“urn:Exslt.ExsltMath” xmlns:Exslt.ExsltRegularExpressions=“urn:Exslt.ExsltRegularExpressions” xmlns:Exslt.ExsltStrings=“urn:Exslt.ExsltStrings” xmlns:Exslt.ExsltSets=“urn:Exslt.ExsltSets” xmlns:scandia.library=“urn:scandia.library” exclude-result-prefixes=“msxml umbraco.library Exslt.ExsltCommon Exslt.ExsltDatesAndTimes Exslt.ExsltMath Exslt.ExsltRegularExpressions Exslt.ExsltStrings Exslt.ExsltSets scandia.library “> <xsl:output method=“html” omit-xml-declaration=“yes”/> <xsl:param name=“currentPage”/> <!-- Local variables --> <xsl:variable name=“documentTypeAlias” select=“string(‘Event’)”/> <!-- the specific document type that we're are looking for --> <!-- store the nodes into a localized and sorted nodeset --> <xsl:variable name=“data”> <xsl:for-each select=“$currentPage/descendant-or-self::* [name() = $documentTypeAlias and string(umbracoNaviHide) != ‘1’]”> <xsl:sort select=“.” data-type=“text” /> <xsl:copy-of select=“.”/> </xsl:for-each> </xsl:variable> <!-- create a key that we can use to “look up” data based on in our loop below and match only the Event document type --> <xsl:key name=“nodes-by-MMMM” match=“Event” use=“umbraco.library:FormatDateTime(eventStartDateTime,‘MMMM’)”/> <xsl:template match=“/”> <!-- create a nodeset of the data we stored earlier and pick out the ‘Event’ nodes, then group them by the full month string --> <xsl:for-each select=“msxml:node-set($data)/Event [count(. | key(‘nodes- by-MMMM’, umbraco.library:FormatDateTime(eventStartDateTime,‘MMMM’))[1]) = 1]”> <xsl:sort select=“eventStartDateTime” order=“descending” /> <div class=“news-month”> <div class=“heading”> <xsl:value-of select=“umbraco.library:FormatDateTime(eventStartDateTime,‘MMM yyyy’)”/> </div> <ul class=“ctr-events”> <!-- This inner loop simply outputs the nodes that match the selected key we specified outside of this template --> <xsl:for-each select=“key(‘nodes-by-MMMM’, umbraco.library:FormatDateTime(eventStartDateTime,‘MMMM’))”> <li> <!-- create an anchor element so we can link to the event details --> <xsl:element name=“a”> <xsl:attribute name=“href”> <xsl:value-of select=“umbraco.library:NiceUrl(@id)”/> </xsl:attribute> <xsl:value-of select=“eventTitle”/> </xsl:element> <div class=“fulldate”> <xsl:value-of select=“umbraco.library:FormatDateTime(eventStartDateTime,‘MMM dd, yyyy’)”/> </div> </li> </xsl:for-each> </ul> </div> </xsl:for-each> </xsl:template> </xsl:stylesheet>
LISTING 5-6: EventOutputGroupedByMonth-4.0.x.xslt
<?xml version=“1.0” encoding=“UTF-8”?> <!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp “ ”> ]> <xsl:stylesheet version=“1.0” xmlns:xsl=“http://www.w3.org/1999/XSL/Transform” xmlns:msxml=“urn:schemas-microsoft-com:xslt” xmlns:umbraco.library=“urn:umbraco.library” xmlns:Exslt.ExsltCommon=“urn:Exslt.ExsltCommon” xmlns:Exslt.ExsltDatesAndTimes=“urn:Exslt.ExsltDatesAndTimes” xmlns:Exslt.ExsltMath=“urn:Exslt.ExsltMath” xmlns:Exslt.ExsltRegularExpressions=“urn:Exslt.ExsltRegularExpressions” xmlns:Exslt.ExsltStrings=“urn:Exslt.ExsltStrings” xmlns:Exslt.ExsltSets=“urn:Exslt.ExsltSets” xmlns:scandia.library=“urn:scandia.library” exclude-result-prefixes=“msxml umbraco.library Exslt.ExsltCommon Exslt.ExsltDatesAndTimes Exslt.ExsltMath Exslt.ExsltRegularExpressions Exslt.ExsltStrings Exslt.ExsltSets scandia.library “> <xsl:output method=“html” omit-xml-declaration=“yes”/> <xsl:param name=“currentPage”/> <!-- Local variables --> <xsl:variable name=“documentTypeAlias” select=“string(‘Event’)”/> <!-- the specific document type that we‘re are looking for --> <!-- store the nodes into a localized and sorted nodeset --> <xsl:variable name=“data”> <xsl:for-each select=“$currentPage/descendant-or-self::node [string(data [@alias=‘umbracoNaviHide’]) != ‘1’ and @nodeTypeAlias=$documentTypeAlias]”> <xsl:sort select=“.” data-type=“text” /> <xsl:copy-of select=“.”/> </xsl:for-each> </xsl:variable> <!-- create a key that we can use to “look up” data based on in our loop below and match only the Event document type --> <xsl:key name=“nodes-by-MMMM” match=“node” use=“umbraco.library:FormatDateTime(data [@alias=‘eventStartDateTime’],‘MMMM’)”/> <xsl:template match=“/”> <!-- create a nodeset of the data we stored earlier and pick out the ‘Event’ nodes, then group them by the full month string --> <xsl:for-each select=“msxml:node-set($data)[count(. | key(‘nodes-by-MMMM’, umbraco.library:FormatDateTime(data [@alias=‘eventStartDateTime’],‘MMMM’))[1]) = 1]”> <xsl:sort select=“string(data [@alias=‘eventStartDateTime’])” order=“descending” /> <div class=“news-month”> <div class=“heading”> <xsl:value-of select=“umbraco.library:FormatDateTime(data [@alias=‘eventStartDateTime’],‘MMM yyyy’)”/> </div> <ul class=“ctr-events”> <!-- This inner loop simply outputs the nodes that match the selected key we specified outside of this template --> <xsl:for-each select=“key(‘nodes-by-MMMM’, umbraco.library:FormatDateTime(data [@alias=‘eventStartDateTime’],‘MMMM’))”> <li> <!-- create an anchor element so we can link to the event details --> <xsl:element name=“a”> <xsl:attribute name=“href”> <xsl:value-of select=“umbraco.library:NiceUrl(@id)”/> </xsl:attribute> <xsl:value-of select=“data [@alias=‘eventTitle’]”/> </xsl:element> <div class=“fulldate”> <xsl:value-of select=“umbraco.library:FormatDateTime(data [@alias=‘eventStartDateTime’],‘MMM dd, yyyy’)”/> </div> </li> </xsl:for-each> </ul> </div> </xsl:for-each> </xsl:template> </xsl:stylesheet>
The above grouping method is known as the Muenchian Method. More on that topic can be found at, http://en.wikipedia.org/wiki/Muenchian_grouping.
For more examples and utilizations of XSLT macros take a look at Chapter 11. In addition to this book, take a look at http://our.umbraco.org, specifically the Projects section, where developers continuously share their solutions with the community.