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!)
- (get)PageTitle
- (meta)Common
- (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 " ">
]>
<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 " "> ]>
<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']),'
',''),'
','')" />
<xsl:variable name="content" select="umbraco.library:Replace(umbraco.library:Replace(umbraco.library:StripHtml($currentPage/data [@alias = 'contentBody']),'
',''),'
','')" />
<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 " ">
]>
<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