Master Templates

20 May 2009 by Lau

Templates

Master Template and Macro's

Templating within any CMS is a very important factor and is often a huge stumbling block. Luckily within Umbraco templating is really easy and incredibly quick. Great!

We approach all our projects using a standard template, and then fill in the gaps during the projects development phase.

First off we create a template called 'Public Master', this is the master template for our site and contains things which are on every page (e.g. page title, header, meta, navigation, and footer) this template will also contain a 'ContentPlaceHolder' which pulls in different child templates depending on the page being rendered (more on this later).

If you have a look at the code below you'll see we don't use inline xslt - this is for a simple reason that it doesn't cache & personally I think its terrible practice leading to messy templates and general confusion. All our template content is pulled in using XSLT macros. We've got a couple of basic macro's which augment our basic DocTypes (blog on these soon!)

  1. (get)PageTitle
  2. (meta)Common
  3. (get)Header

You might want to add some additional ones, but I can't think of many projects we do which doesn't use at least these ones! ;)

Right so lets have a quick look at the MasterTemplate...

Master Template - Public Master

<%@ Master Language="C#" MasterPageFile="/umbraco/masterpages/default.master" AutoEventWireup="true" %>
<asp:Content id="PublicMasterContent" ContentPlaceHolderID="ContentPlaceHolderDefault" runat="server">

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

<head id="head" runat="server">
    
    <title><umbraco:Macro Alias="(get)PageTitle" runat="server"></umbraco:Macro></title>
    
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <meta http-equiv="Content-Language" content="en" />
    <meta http-equiv="X-UA-Compatible" content="IE=7;FF=3;OtherUA=4" />

    <!-- site meta information - author, desc, keywords, company, copyright, rss feed-->
    <umbraco:Macro Alias="(meta)Common" runat="server"></umbraco:Macro>

    <!-- remember to add the constant values in beneath this, e.g copyright, author, etc -->

    <!-- disable MS tools -->
    <meta http-equiv="imagetoolbar" content="no" />

    <!-- stylesheets -->
    <link rel="stylesheet" type="text/css" href="/css/Layout.css" title="SITE NAME" media="screen" />

    <!-- javascript entries -->
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
    <script type="text/javascript" src="/Scripts/JS/main.js"></script>
    
</head>

<body id="pageName">     
    <div id="container">
        <div id="header">
            <!-- Header -->
            <umbraco:Macro Alias="(get)Header" runat="server"></umbraco:Macro>

            <div id="nav">
                Navigation Goes Here.
            </div>
        </div>   

        <!-- Start Content Link for Screen Readers, remember to implement this -->
        <a name="startcontent" id="startcontent"></a>

        <div id="content" class="content">
            <form id="PublicMasterForm" runat="server">
                <asp:ContentPlaceHolder ID="PublicMasterContentPlaceHolder" runat="server"></asp:ContentPlaceHolder>
            </form>
        </div>

        <div id="footer">
            Footer Goes Here.
        </div>
    </div>

<!-- Remember to Insert Google Analytics code here -->

</body>

</html>

</asp:content>

Okay so you've had a look at the 'Public Master' template, and hopefully your pretty happy with it.

Lets quickly run through our Macro's. (you'll need to drill into the Developers tab, right click on XSLT and create these! Its not hard, don't worry!)

Macro - (get)PageTitle

Okay, so in our 'site root' doc-type we always create a field called 'Site Name' it makes alot of sense and we're gonna use this all over the place so its great to have it defined in the site root doc. In all our children pages we have a field called, 'bodyHeader' - nice and simple this is the Header of the page. (we quite often also have an SEO tab, with an alternative title for SEO optimisation which will override this).

So we want to grab the Site Title, and then if a body header is present we want to add '- Body Header' after the sitename. Standard.

So our XSLT looks something like this...

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet [
    <!ENTITY nbsp "&#x00A0;">
]>
<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:template match="/">

        <xsl:for-each select="$currentPage">
            <xsl:value-of select="$currentPage/ancestor::root/node/data [@alias = 'siteName']" />
            <xsl:choose>
                <xsl:when test="string(data[@alias= 'bodyHeader']) != ''">
                    - <xsl:value-of select="data [@alias = 'bodyHeader']"/>
                </xsl:when>
                <xsl:otherwise>
                    
                 </xsl:otherwise>
            </xsl:choose>
        </xsl:for-each>

    </xsl:template>

</xsl:stylesheet>

Nice so thats page titles sorted across our whole site!

Macro - (meta)Common

So this one sorts out the meta data for our pages, this is kinda important and as we don't want the same meta data on every page XSLT is really a great solution...

The first thing we need to do is strip out any tags from our content pages we're going to do this really quickly just by setting up a variable and use a nice Umbraco extension to replace any <tags> with ' ' e.g. nothing! BTW - this code could do with some improvement, because when you strip out a P or a BR you lose the space and words end up getting joined together. (feel free to do this)

So now we've got our handy variables set up for the metaDescription and the contentBody we're going to define our meta tags for description. In here we've got a little logic that says, hey is there any body text (you'd hope there is!) and if so, it pulls this text from our variable and then truncates it down to 380 letters. This is so its nice and compact for search engines to read and doesn't fill your header up with the entire page content!

If the page doesn't have any content, we just default back to the root page's meta description (note we are using the same alias). E.g so on your root page the description you've defined in the metaDescription will be used. (on our site, this metaDescription & metaKeywords is inherited by all children nodes)

Next we move onto keywords, these are also kinda important...

On all our content pages I've made field called metaKeywords which is a simple textbox, and allows user to add the keywords as a comma seperated list. All we do now is pull these tags in, and after these tags have been added we add the keywords defined in the root in as well. We do this by altering the XPATH.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp "&#x00A0;"> ]>
<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="description" select="umbraco.library:Replace(umbraco.library:Replace(umbraco.library:StripHtml($currentPage/data [@alias = 'metaDescription']),'&#xD;',''),'&#xA;','')" />
<xsl:variable name="content" select="umbraco.library:Replace(umbraco.library:Replace(umbraco.library:StripHtml($currentPage/data [@alias = 'contentBody']),'&#xD;',''),'&#xA;','')" />

<xsl:template match="/">

<meta>
    <xsl:attribute name="name">description</xsl:attribute>
    <xsl:choose>
        <xsl:when test="$content=''" >
            <xsl:attribute name="content"><xsl:value-of select="umbraco.library:TruncateString($content, '380', '')" disable-output-escaping="yes"/></xsl:attribute>
        </xsl:when>
        <xsl:otherwise>
            <xsl:attribute name="content"><xsl:value-of select="$description" disable-output-escaping="yes"/></xsl:attribute>
        </xsl:otherwise>  
    </xsl:choose>
</meta>

<meta>
<xsl:attribute name="name">keywords</xsl:attribute>
<xsl:attribute name="content">,
    <xsl:value-of select="$currentPage/data [@alias = 'metaKeywords']" disable-output-escaping="yes"/> ,
    <xsl:value-of select="$currentPage/ancestor-or-self::node/data [@alias = 'metaKeywords']"  disable-output-escaping="yes"/>
</xsl:attribute>
</meta>
</xsl:template>
</xsl:stylesheet>

Okay so we've sorted out the areas in the meta data which changes depending on the page.

Macro -(get)Header

Okay so next we want to pull in the site's name and description to build our header. Because google looks at the first part of the page it sometimes makes sense to push a description into this area of the page. Generally your users don't need to see this so we've just given the description a class of 'hidden' which via your CSS you could set to '.hidden {display="none"}'. Its a pretty useful class to have in your CSS anyway, so you were probally thinking of having it anyway right?

All we're doing is looking at the root document and pulling in the siteName in. Again you might just use CSS and do an image replacement technique on the header, but its still important the correct text is there as there are people out there who use screen readers, or turn off your stylesheet to make digesting the information easier. (not that your design sucks or anything, but schematic design IS important!)

Again we do the same with description, but with that added class tag so we can just hide this.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet [
    <!ENTITY nbsp "&#x00A0;">
]>
<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:template match="/">

        <h1 id="siteName">
            <a href="/" title="Click to navigate to the Home Page">
                <xsl:value-of select="$currentPage/ancestor::root/node/data [@alias = 'siteName']" />
            </a>
        </h1>
        <h2 id="siteDescription" class="hidden">
            <xsl:value-of select="$currentPage/ancestor::root/node/data [@alias = 'siteDescription']"/>
        </h2>

    </xsl:template>

</xsl:stylesheet>

More on children templates tommorow! ;) L