Hostingheaderbarlogoj
Join InMotion Hosting for $3.49/mo & get a year on Tuts+ FREE (worth $180). Start today.
Advertisement

Getting Started with Umbraco: Part 3

by
Gift

Want a free year on Tuts+ (worth $180)? Start an InMotion Hosting plan for $3.49/mo.

In this part of the series we'll create the rest of our content nodes (pages) and start adding the XSLT macros to handle things like building the site navigation, and populating the <umbraco:Macro> elements.


Also available in this series:

  1. Getting Started with Umbraco: Part 1
  2. Getting Started with Umbraco: Part 2
  3. Getting Started with Umbraco: Part 3
  4. Getting Started with Umbraco: Part 4
  5. Getting Started with Umbraco: Part 5

Creating the Rest of the Content Nodes

First we'll add the rest of our content nodes. Remember, Document Types + Templates + content nodes = pages. To view the back-end the site sill need to be open in Visual Web Developer Express (VWD). Right-click the default.aspx page in the right hand Solution Explorer pane and choose View in Browser. The home page of the site should then load. To access the back-end, replace default.aspx in the address bar with umbraco, you should then be taken to the login screen, and then to the back-end once your credentials have been entered.

All pages in a site should be created as child nodes of the home page in order for the XSLT to work correctly. Right-click on the Home node in the top left hand panel of the back-end and choose Create from the drop-down menu. This will give us the Create dialog; in the Name field enter About Us. The Choose Document Type select box should already be filled with the first available Document Type – Content – this is fine, just click the Create button at the bottom and the new node will be created as a child node of the Home node:


Enter some text in the textarea, then switch to the Properties tab and enter All About Us in the Page Title field, then save the new node using the disk icon in the toolbar. Next we'll create the News List page; right-click the Home node and choose Create again. Enter News as the name and choose the News List Document Type in the select box. This page has no custom properties (it will be built using a macro) so the node will open up on the Properties tab. Enter Company News in the Page Title field and click the save and publish icon.

Next we can create some news items; this time right-click the News node and choose Create. Add News Item 1 in the Name field and click Create (the correct Document Type will already be selected). We've a few more fields to complete this time; go ahead and pick a date, add a Headline and the Story Text. We might also want to add another image to the Media library and select it using the media picker (although the image isn't mandatory with News Items). Also add a Page Title on the Properties tab. Once this is done create another News Item, and complete the fields again. Make sure there is a decent amount of text in the text area for each news story as we'll be pulling some from each News Item in to build the News List page.

So now we've created a Content page, the News List page and a couple of News Items. All that's left is to create some Hero Panels for display on the home page. Right-click the Home node again, then add Panel 1 as the name of the new node, and choose the Hero Panel document type. Use the textarea on the newly created node to add some text to the panel. Also, we should ensure that we switch to the Properties tab and tick the Umbraco Hide In Navigation checkbox. Create several panels in this way, maybe using different text, or something to differentiate between the different panels. At this point, our back-end should appear as follows:



Creating a Macro

Now that our pages have been created, we can start writing the XSLT macros that will build the more complex aspects of the site, such as the navigation, news list, etc. If we open up the BasePage.master file in VWD, the first <umbraco:Macro> element we find is the pageTitle macro. We'll build this one first as it's pretty straight-forward and should provide a basic introduction to XSLT.

In the Umbraco back-end, go to the Developer section, right-click on the XSLT folder and choose Create. In the dialog that appears add pageTitle as the Filename and leave the select box set to Clean. Note that the Create Macro checkbox is already ticked. Click the Create button. The new XSLT file and its matching macro will be created. Select the macro (it will be found in the Macros folder in the node tree) and change the Alias of the macro from PageTitle to pageTitle (for consistency).

Now, switch back to VWD and open up the XSLT file in the Solution Explorer at the right. The new XSLT file we created in Umbraco should be listed. Of course, we can add the XSLT code directly through Umbraco, however, for Intellisense reasons, it is helpful to write it in VWD. Open up the XSLT file, around the middle of the file there will be a comment that says <!—start writing XSLT -->, remove this comment and replace it with the following code:

<xsl:if test="$currentPage/pageTitle != ''"> 
	<xsl:value-of select="$currentPage/pageTitle"/> 
	<xsl:text> - </xsl:text> 
</xsl:if> 
 
<xsl:value-of select="$currentPage/ancestor-or-self::* /domainName"/>

Save the XSLT file. Essentially, XSLT is used to transform XML into either other types of XML, or other formats of XML, such as XHTML. The data that we enter into fields in the Umbraco back-end is stored in the database as XML, so we can use XSLT to process this XML and turn it into (X)HTML for display on our pages. Before we go into detail about what the above code does, we just need to look at a couple of core concepts for working with XSLT.

First of all, just after <xsl:output> element there is an XSL parameter (<xsl:param>) element called currentPage. This will be set to whichever page 'calls' the XSLT file, so if a visitor views the home page of the site, the currentPage parameter will be set to the Home node in the back-end. If a content page, such as the About us page, is viewed the currentPage parameter will be set to that page instead.

On the next line we have the XSL template (<xsl:template>) element, which is set to match / which means that it should match all XML elements and attributes in the document (the stored XML representation of the node in the back-end).

Now let's look at the code we added to the <xsl:template> element. We first use the <xsl:if> element to test whether the currentPage (in XSLT parameters must start with the $ character) contains a value for the pageTitle element. This is the pageTitle element in our page node, not the pageTitle macro by the way. If it does contain a value, the next line, <xsl:value-of> allows us to select the contents of the pageTitle property. We then use the <xsl:text> element to specify a hyphen to use as a separator.

Lastly we use the <xsl:value-of> element once again to select the domainName property. In this element we use an XPath step which consists of the axis ancestor-or-self to select all ancestor nodes of the currentPage as well as the currentPage, and the ::* nodetest to select all attributes of the selected ancestors. We narrow this down specifically to the domainName property we set on the Home node using the /domainName predicate.

View the home page of the site now, and the title of the page should automatically be set to the value we entered into the domainName property. If we view the about us page (just add /about-us.aspx to the end of the localhost URL in the address bar), we instead find that the title is set to All About Us – The Company. Page name – Site name is the preferred title format for SEO reasons.

In this example, pages that don't have the pageTitle property set will simply revert to the domainName property. Some macro authors add an extra level of protection and if the pageTitle isn't set fall back to the nodeName instead. This is useful if the client is likely to forget to set the pageTitle when adding new pages to the site, but does mean that the page title could end up with camel-cased filenames in it. There are arguments for and against this method. I'll leave it up to you to decide whether or not this additional fallback is necessary.


Adding More Macros

Let's add some more macros; next we should build the navigation for the site so that it is easier to move between pages. This macro will be much more complex than the pageTitle maco that we just looked at. We left some mark–up in the BasePage.master file, this is the structure we will end up with once we are done. Remove this from the masterpage and add an <umbraco:Macro> element in its place:

<umbraco:Macro Alias="topNav" runat="server"></umbraco:Macro>

Now create a new XSLT file (and therefore a macro) called topNav. In VWD, open the new XSLT file and add the following code directly after the currentPage parameter:

<xsl:variable name="rootNode" select="$currentPage/ancestor-or-self::* [@isDoc and @level=1]" /></div>

Next, add the following code to the <xsl:template> element:

<nav> 
	<ul class="clear-float"> 
		<li> 
			<a href="{umbraco.library:NiceUrl($rootNode/@id)}"> 
				<xsl:if test="$rootNode/@id = $currentPage/@id"> 
					<xsl:attribute name="class">on</xsl:attribute> 
				</xsl:if> 
				<xsl:value-of select="$rootNode/@nodeName" /> 
			</a> 
		</li> 
 
		<xsl:for-each select="$currentPage/rootNode/* [@isDoc and string(umbracoNavHide) != '1']"> 
			<li> 
				<xsl:if test="position() = last()"> 
					<xsl:attribute name="class"> 
						<xsl:text>last</xsl:text> 
					</xsl:attribute> 
				</xsl:if> 
				<a href="{umbraco.library:NiceUrl(@id)}"> 
					<xsl:if test="$currentPage/@id = @id"> 
						<xsl:attribute name="class">on</xsl:attribute> 
					</xsl:if> 
					<xsl:attribute name="title"> 
						<xsl:value-of select="@nodeName"/> 
					</xsl:attribute> 
					<xsl:value-of select="@nodeName"/> 
				</a> 
                <xsl:if test="child::* [@isDoc]"> 
                    <ul> 
                        <xsl:for-each select="child::* [@isDoc and string(umbracoNavHide) != '1']"> 
                            <xsl:choose> 
                                <xsl:when test="position() < 6 and string(child::NewsItem = '1')"> 
                                    <li> 
                                        <a href="{umbraco.library:NiceUrl(@id)}"> 
                                            <xsl:attribute name="title"> 
                                                <xsl:value-of select="@nodeName"/> 
                                            </xsl:attribute> 
                                            <xsl:value-of select="@nodeName"/> 
                                        </a> 
                                    </li> 
                                </xsl:when> 
                                <xsl:otherwise> 
                                    <li> 
                                        <a href="{umbraco.library:NiceUrl(@id)}"> 
                                            <xsl:attribute name="title"> 
                                                <xsl:value-of select="@nodeName"/> 
                                            </xsl:attribute> 
                                            <xsl:value-of select="@nodeName"/> 
                                        </a> 
                                    </li> 
                                </xsl:otherwise> 
                            </xsl:choose> 
                        </xsl:for-each> 
                    </ul> 
                </xsl:if> 
            </li> 
        </xsl:for-each> 
    </ul> 
</nav>

Save the file. First of all, we set a variable that matches the root node of the site, which is the Home page. To select only the home page, we navigate from the currentPage (using the $currentPage parameter) through all ancestors using the ancestor-or-self axis and the ::* nodetest and predicate, checking that the node has the @isDoc attribute (this is added to all page nodes by Umbraco) and that it is at level 1 (another attribute added by Umbraco). This will get us the root node of the site, which is the home page.

Then, in the within the <xsl:template> we first create the outer containers for our navigation menu, specifically the <nav> and <ul> elements. We then add the first <li> element and the first anchor element. We set the href of the anchor using Umbraco's NiceUrl library function, which accepts a document id as an argument. We supply the id of the root node, using our $rootNode variable (like parameters, variables are prefixed with the $ character. Attributes are prefixed with @ in XSLT). The NiceUrl function will return the stored URL for the node.

We then check whether the id of the $rootNode matches the id of the $currentPage using an <xsl:if> statement; if it does we know that the home page is the one currently being viewed and add an on class to the anchor. We then select the name of the node (the Name given to the node in the Umbraco back-end, which is shown on the Properties tab for the node) to use as the title attribute of the anchor element, and its text, both using the <xsl:value-of> element to select the values.

Next, we need to cycle through each page node under the home page, se we use an <xsl:for-each> loop to select and process all nodes that are children of the $rootNode and are also documents, but which additionally do not have the umbracoNavHide checkbox ticked. This prevents our Hero panels from being listed in the site-wide navigation menu, which clearly, we do not want!

For each child of the $rootNode, we create a new <li> element. We do a quick test to see if we are processing the last node in the set, which we achieve by checking whether the position of the node in the node set is equal to last. If it is, we add the class name last to the <li>.

While not strictly necessary in this example, this is still an incredibly useful technique to utilise seeing as neither IE6, nor IE7 (which still account for a large percentage of the audience of an unfortunate number of sites) are able to use the last-child CSS selector.

We then create a new <a> within the <li> and set its href using the NiceUrl function once more, this time passing in the id attribute of the current node. We set the title and text of the anchor in the same way as before. We also add an on class if the page currently being viewed is the same as the page currently being processed. As we are already in the context of a node, we mostly don't need to use the $currentPage parameter, using it only when we check the @id of the current node against the @id of the current page.

Once the link for the nav item has been created we do another test (again using the <xsl:if> conditional) to see whether the current node has any child nodes which are documents. If it does, we create a sub nav using a nested <ul>, and then select each child node that is a document and doesn't have the umbracoNvHide checkbox ticked.

We then use an <xsl:choose> conditional, which is kind of like the XSLT equivalent to an if...else, to limit the number of NewsItem child menu items to five by testing whether the child has a position of less than 6 when we are processing NewsItems. We can check whether we are currently processing NewsItem nodes by checking whether a test for this element (child::NewsItem) is equal to true.

We should limit the subnav in for the news items because, after the site has been live for a while, there could potentially be hundreds of news articles, and it would be ridiculous to list them all in the nav menu. We then create a new <li> and <a> for each child, up to the maximum of 5. This is in the <xsl:when> element, which is equivalent to the if part of if...else. When we are not processing NewsItems, we just create a subnav for all the items (in the <xsl:otherwise> element, which is equivalent to the else part of an if...else.)

That's essentially it; if we save this now, and return to the home page of the site (the actual home page as viewed in a browser, not the Home node in the back-end) and refresh it, we should find that our top level nav is created for us automatically:


Next we can create the nav for the footer of the site; this is almost identical to the top nav except that it doesn't have subnavs. Create the new XSLT file in the same way as before and call it footerNav. Add the following code to the file:

<nav> 
	<ul> 
		<li> 
			<a href="{umbraco.library:NiceUrl($rootNode/@id)}"> 
				<xsl:if test="$rootNode/@id = $currentPage/@id"> 
					<xsl:attribute name="class">on</xsl:attribute> 
				</xsl:if> 
			<xsl:attribute name="title"> 
				<xsl:value-of select="$rootNode/@nodeName"/> 
			</xsl:attribute> 
			<xsl:value-of select="$rootNode/@nodeName" /> 
		</a> 
	</li> 
 
        <xsl:for-each select="$rootNode/* [@isDoc and string(umbracoNavHide) != '1']"> 
            <li> 
                <xsl:if test="position() = last()"> 
                    <xsl:attribute name="class"> 
                        <xsl:text>last</xsl:text> 
                    </xsl:attribute> 
                </xsl:if> 
                <a href="{umbraco.library:NiceUrl(@id)}"> 
                    <xsl:if test="$currentPage/@id = @id"> 
                        <xsl:attribute name="class">on</xsl:attribute> 
                    </xsl:if> 
                    <xsl:attribute name="title"> 
                        <xsl:value-of select="@nodeName"/> 
                    </xsl:attribute> 
                    <xsl:value-of select="@nodeName"/> 
                </a> 
            </li> 
        </xsl:for-each> 
    </ul> 
</nav>

This XSLT file will also need to define the rootNode variable near the top, just like with the topNav, and don't forget to add the <umbraco:Macro> element to the BasePage.master file:

<umbraco:Macro Alias="footerNav" runat="server"></umbraco:Macro>

These are all of the macros we need in the BasePage.master file. The Home.master file only has the Hero panel macro, which we'll add in another part of the series, and the Content.master file doesn't have any macros in it at all. The next file that we need to add a macro to is the NewsList.master page; we'll add this in the next part in the series.


Summary

In this part of the tutorial, we began writing the XSLT files that build different parts of the site based on the content nodes that have been created. This is where the site really starts coming together and feeling like a fully working web site.

XSLT and XPath are powerful languages for working with XML documents and the elements contained within those documents. If this has been your first exposure to XSLT some of it may still feel a little alien, especially if you're used to working with more intuitive languages such as HTML and CSS. But don't worry, XSLT and XPath are easy to pick up with a little practice and they are languages worth learning, jQuery for example uses XPath to select elements from (X)HTML pages.

In the next part of the series we'll add the XSLT files that will build the news list page and the news navigation menu, and then move on to look at how we can add .Net User Controls to Umbraco for even more custom functionality.

Advertisement