Getting Started with Umbraco: Part 3

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.


  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
First we'll add the rest of our content nodes. Remember, Document Types + Templates + content nodes = pages. 

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:

Nodes Snap
Back End Snap



Enter some text in the text area, 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 text area 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 check box. 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:



Nodes Now Snap
Back End Looks like this - Observe Nodes


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.


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]" />

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>.

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:

Result Snpshot
Result after Refresh

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.
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.


Comments

Popular posts from this blog

The Top 15 Google Products for People Who Build Websites

Google Translator using Windows forms

5 Useful Tricks for Ubuntu Power Users