Advertisement

Getting Started with Umbraco: Part 4

by

In this part, we'll start off by adding another XSLT file (for the news item pages), and then move on to look at how we can integrate standard .Net User Controls to Umbraco.


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

Adding the newsList Macro

Create a new XSLT file (and macro) in the back-end and call it newsList. In this XSLT file we'll be using a non-standard entity, which is the HTML entity &\mdash;. In order to use this entity we need to define it in the entity list in the XSLT doctype declaration at the top of the file. To add the mdash entity add the following code within the square brackets directly after the defined non-breaking space entity:

<!ENTITY mdash "&#x2014;">

We'll save a variable so that we can easily access the newsList node in order to get its child nodes. Directly after the currentPage param in the new XSLT file, add the following variable:

<xsl:variable name="newsParent" select="umbraco.library:GetXmlNodeById(1094)"/>

Now within the <xsl:template> add the following code:

<xsl:for-each select="$newsParent/* [@isDoc]"> 
	<article> 
		<xsl:if test="position() = last()"> 
			<xsl:attribute name="class">last</xsl:attribute> 
		</xsl:if> 
		<header> 
			<h1> 
				<a href="{umbraco.library:NiceUrl(@id)}"> 
					<xsl:attribute name="title"> 
						<xsl:value-of select="headline"/> 
					</xsl:attribute> 
					<xsl:value-of select="headline"/> 
				</a> 
			</h1> 
			<p class="post-info"> 
				<xsl:value-of select="umbraco.library:FormatDateTime(date, 'dd/MM/yyyy')"/> 
			</p> 
		</header> 
		<div> 
			<xsl:value-of select="substring(newsText, 1, 250)" disable-output-escaping="yes" /> 
			<xsl:text>...</xsl:text> 
			<a href="{umbraco.library:NiceUrl(@id)}"> 
				<xsl:attribute name="title"> 
					<xsl:value-of select="headline"/> 
				</xsl:attribute> 
				<xsl:text>read more</xsl:text> 
			</a> 
		</div> 
	</article> 
</xsl:for-each>

First we create a new variable which is set to point directly to the parent news node. We can grab any node directly by specifying its id property as an argument to the GetXmlNodeById() umbraco library method. The id to use can be obtained by looking for the id field on the Properties tab of any node; this is another property added to the node automatically by Umbraco:


We then use this variable to process each child node under the news parent, which maps to the news list page. For each node that is a child of the list page we first create an <article> element. We then test whether we're dealing with the last node in the set and if we are, we add the class name last to the element. This is done purely for styling reasons; most browsers of course support the :last-child filter, but this can be a useful technique to employ to cater for older browsers.

We then create a <header> element containing a <h1> and an <a> element in order to link to the full news sub page. To crate the link we again use Umbraco's NiceUrl() function. We set the title and text content of the anchor using the headline property from the back-end. Next we display the date the news page was posted and the creator of the news item. To display the date we can use Umbraco's FormatDateTime() function, supplying the name of the date-picker field in the back-end and the format that we'd like the date to appear in. To display the author of the page we simply get the creatorName attribute. Remember, attributes of the page (as opposed to properties mapped to input elements) are referenced with the @ prefix.

Finally, we create a new container <div> and display a preview of the news story; to do this we can just use .Net's substring function, supplying the text from the rich-text editor, the character to start at, and the character to finish at as arguments. After the text preview, we add an ellipses and a read more link which also points to the full news story.

When we access the newsList page in a browser now, the child nodes of the newsList node will be aggregated into the list page:



The newsNav XSLT

Switch to the Developer tab in the Umbraco back-end, right-click on the XSLT Files directory in the node tree at the left and choose Create. Enter newsNav as the name of the file and ensure that Clean is selected in the template select box and the Create Macro check box is ticked.

Once the file has been created, go back to Visual Web Developer Express (VWD) and open up the XSLT file. We'll use a variable to reference the newsList node which contains the individual news story nodes like we did in the newsList XSLT file from the last part of this series:

<xsl:variable name="newsParent" select="umbraco.library:GetXmlNodeById(1094)"/>

We then simply process each news story node in an XSLT for-loop and built the nav based on the headline:

<ul> 
	<xsl:for-each select="$newsParent/* [@isDoc]"> 
		<li> 
			<a href="{umbraco.library:NiceUrl(@id)}"> 
				<xsl:attribute name="title"> 
					<xsl:value-of select="headline"/> 
				</xsl:attribute> 
				<xsl:value-of select="headline"/> 
			</a> 
		</li> 
	</xsl:for-each> 
</ul>

That is all the code we'll need. It simply allows the visitor to navigate directly between news stories without needing to keep returning to the list page. This is all of the XSLT that we need for now; we still have to write the macro that will generate the Hero panels, but we'll come back to that later. For now we can move on to look at how we can use User Controls in an Umbraco site.


Creating a User Control

We may decide at this point that what we want the site to have is a contact us page, where visitors can complete a form and email the site administrator. This presents us with a problem; in a standard .Net site, we could just add a set of form fields to a contact page, and add the logic to send the email to the site owner in the code-behind file. But with Umbraco, there are no code-behind files for pages, or indeed any pages on which to add the form fields.

What we can do instead is create a User Control, as these types of files can have code-behinds in which to add the email-sending logic that we require. We can create the User Control in VWD; right-click on the usercontrols folder in the Solution Explorer at the left of the application and choose Add new item.

In the dialog that appears, select Web User Control in the center column and change the file name to contactform.ascx. Make sure the Place code in separate file check box is ticked and hit the Add button. Add the following code directly after the control declaration that is automatically added to the file:

<asp:Panel ID="form" runat="server"> 
	<fieldset> 
		<legend class="accessible-hidden">Contact Us Form</legend> 
		<asp:Label runat="server" AssociatedControlID="name">Your name<sup>*</sup>:</asp:Label> 
		<asp:RequiredFieldValidator ControlToValidate="name" runat="server"><span>This field is required</span></asp:RequiredFieldValidator> 
		<asp:TextBox ID="name" runat="server"></asp:TextBox> 
		<asp:Label runat="server" AssociatedControlID="email">Your email address<sup>*</sup>:</asp:Label> 
		<asp:RequiredFieldValidator ControlToValidate="email" runat="server"><span>This field is required</span></asp:RequiredFieldValidator> 
		<asp:RegularExpressionValidator ControlToValidate="email" runat="server" ValidationExpression="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*"><span>Please enter a valid email adress</span></asp:RegularExpressionValidator> 
		<asp:TextBox ID="email" runat="server"></asp:TextBox> 
		<asp:Label runat="server" AssociatedControlID="message">Your question/message</asp:Label> 
		<asp:TextBox ID="message" TextMode="MultiLine" Rows="5" Columns="10" runat="server"></asp:TextBox> 
		<asp:Button ID="submit" runat="server" CssClass="button-submit" OnClick="sendMail" Text="Send Email" /> 
	</fieldset> 
</asp:Panel> 
 
<<asp:Panel ID="thanks" runat="server" Visible="false"> 
	<h1>Thanks!</h1> 
	<p>Your message has been sent and will be read shortly. We'll get back to you as soon as we can.</p> 
</asp:Panel>

This should suffice for this basic demonstration. We've added two <asp:Panel> elements to the page; the first contains the form and the second contains a thanks message to be displayed once the form has been sent.

The form panel contains an outer <fieldset> element as a logical container and the obligatory <legend> element. We then have a series of server controls including <asp:label> elements that will be converted to standard HTML label elements, some text inputs and a textarea, as well as a couple of basic validators to ensure that required fields are not left blank and that a valid email address is entered.

So that's the code we'll use to display the form. We won't actually add the code that will send the email as that would involve installing and configuring an SMTP server, which would deviate a little too far from the Umbraco side of things. All we'll do is switch between the two <asp:Panel> elements. We can do that with the following code added within the class in the codebehind file:

protected void sendMail(object sender, EventArgs e) 
{ 
         
	form.Visible = false; 
	thanks.Visible = true; 
}

We just switch the visibility of the two panels in a click-handler for the <asp:Button> element in the form.


Adding the usercontrol to an Umbraco Page

Now that we have our usercontrol we can add it to a new page in Umbraco; on the Developer tab in the back-end right-click the Macros folder and choose Create from the menu. In the dialog enter Contact form as the name and then click Create.

The main right-hand panel will update once the macro has been created. First update the Alias to contactForm so that it is consistent with the aliases for other macros we've created. In the second section there is an entry with the label or .NET User Control; expand the select box next to the input field and choose the usercontrol we created. Also ensure the Use in editor checkbox is ticked:


Next go back to the Content tab in the Umbraco back-end and create a new page called Contact Us by right-clicking the Home icon and choosing Create. This page should use the standard Content Document Type. To add the macro to the page, we can use the orange button near the right-hand side of the toolbar (the icon is an application window containing ><). In the dialog that appears make sure Contact form is selected and hit the OK button. This will insert the usercontrol's content into the textarea. Save and publish the page.

When we view refresh the homepage of the site, we should find that the nav menu has automatically been updated with the new Contact Us page, and when we navigate to the page we should see our new contact form on the page. Once the form has been filled out (leaving either the name or email fields blank should result in the validator messages being displayed) we should also be able to click the button to reveal the thanks panel.


The Hero Panel Macro

The final macro we need to write is the one which creates the hero panel content items, which will take the form of a carousel with several panels that will slide in an out of view. We already added a lot of the mark-up to the HomePage.master file back in part 2 of this series, so we don't have that much to do.

In the Umbraco back-end create a new XSLT file and accompanying Macro. Call the new XSLT file heroPanels. It will need to contain the following code in the main template section of the XSLT file:

<xsl:for-each select="$currentPage/ancestor-or-self::* [@isDoc and @level=1]/HeroPanel"> 
     
	<div class="panel"> 
		<xsl:value-of select="panelContent" disable-output-escaping="yes" /> 
	</div> 
 
</xsl:for-each>

That's all we need; the content of each hero panel should now be wrapped in a <div> and inserted into the #slider element on the home page, where the <umbraco:Macro> resides.


Adding some Styling

At this point we should have an almost completely functioning site (we just need to wire up with subnav and hero panel functionality with some JavaScript); it just doesn't look very pretty. Let's rectify that now by adding a basic skin to the site. Create a new style sheet, and save it in the css folder (this is added when the Umbraco archive is unpacked) as site.css (we added a link to this style sheet when we created the templates in part 2). Add the following code to it:

/* site-wide */ 
body { 
	padding-bottom:60px; margin:0; font:normal 12px/20px "Segoe UI", Candara, "Bitstream Vera Sans", "DejaVu Sans", "Bitstream Vera Sans", "Trebuchet MS", Verdana, "Verdana Ref", sans-serif; 
} 
form { width:960px; margin:auto; } 
.accessible-hidden { display:block; text-indent:-5000px; overflow:hidden; } 
.clear-float:after { display:block; content:""; height:0; clear:both; visibility:hidden; } 
 
h1, h2, h3 { 
	font:bold 24px Constantia, "Lucida Bright", Lucidabright, "Lucida Serif", Lucida, "DejaVu Serif", "Bitstream Vera Serif", "Liberation Serif", Georgia, serif; 
} 
 
header { 
	display:block; width:916px; padding:0 20px; border:2px solid #969696; border-top:none;  
	-moz-border-radius:0 0 8px 8px; border-radius:0 0 8px 8px; background-color:#eee; 
} 
header h1 { float:left; } 
header h1 a { display:block; width:204px; height:75px; background:url(/img/logo.png) no-repeat; } 
header h2 { float:right; color:#535353; } 
 
nav { display:block; margin:0; clear:both; position:relative; left:-12px; } 
nav ul { padding:0; margin:0; } 
nav li { float:left; list-style-type:none; } 
nav li a { 
	display:block; padding:6px 12px; border-top:2px solid #eee; position:relative; font-size:16px; 
	font-weight:bold; text-decoration:none; color:#535353; 
} 
nav li a:hover, nav li a.on { border-top-color:#535353; } 
nav ul ul { 
	display:none; border:2px solid #969696; border-top:none; -moz-border-radius:0 0 8px 8px; 
	border-radius:0 0 8px 8px; position:absolute; background-color:#eee; 
	-moz-box-shadow:0 2px 3px #777; -webkit-box-shadow:0 2px 3px #777; box-shadow:0 2px 3px #777; 
} 
nav li:hover ul { display:block; } 
nav ul ul li { float:none; } 
nav ul ul a { border-top:none; font-weight:normal; font-size:14px; } 
nav ul ul a:hover { color:#969696; } 
 
#innerContent { width:960px; padding-top:20px; clear:both; } 
#innerContent img { 
	display:block; padding:4px; border:1px solid #969696; -moz-border-radius:4px; 
	border-radius:4px; 
} 
 
footer { 
	width:916px; padding:0 20px; border:2px solid #969696; border-bottom:none; 
	-moz-border-radius:8px 8px 0 0; border-radius:8px 8px 0 0; position:fixed; bottom:0; 
	background-color:#eee; 
} 
footer nav { position:relative; top:11px; left:0; } 
footer li { margin-right:10px; } 
footer li.last { margin-right:0; } 
footer nav li:after { content:"|"; } 
footer nav li.last:after { content:""; } 
footer nav a { padding:0; border-bottom:1px dashed #000; border-top:none; margin-right:10px; float:left; font-size:12px; font-weight:normal; text-decoration:none; color:#000; } 
footer nav a:hover, footer nav a.on { border-bottom-style:solid; } 
 
footer p { float:right; } 
 
/* page specific */ 
article { display:block; margin-bottom:40px; } 
article header, article footer { padding:0; border:none; background-color:#fff; } 
article header h1 a { display:inline; background:none; } 
article h1 { margin-bottom:0; float:none; } 
article h1 a { border-bottom:1px dashed #000; text-decoration:none; color:#000; } 
article h1 a:hover { border-bottom-style:solid; } 
.post-info, #newsItem li { font-style:italic; color:#888; } 
article p a { border-bottom:1px dashed #000; margin-left:5px; text-decoration:none; color:#000; } 
article p a:hover { border-bottom-style:solid; } 
 
#newsItem { width:640px; float:left; } 
#newsItem ul { padding:0; margin:0; } 
#newsItem li { margin-left:5px; float:left; list-style-type:none; } 
#newsContent img { margin-right:20px; float:left; } 
#newsNav { width:260px; float:right; clear:none; } 
#newsNav h1 { font-size:14px; } 
#newsNav li { float:none; } 
#newsNav a { 
	padding-left:0; border-top:none; border-bottom:1px dashed #000; font-weight:normal; font-size:14px; color:#000; 
} 
#newsNav a:hover { border-bottom-style:solid; } 
 
fieldset { border:none; } 
label, input, textarea { display:block; width:300px; margin-bottom:10px; } 
label { width:auto; margin-right:10px; float:left; } 
input, textarea { clear:both; } 
label sup { color:#ff0000; } 
.button-submit { width:auto; } 
fieldset span { float:left; color:#ff0000; } 
 
/* hero panel */ 
#hero { position:relative; } 
#viewer { width:960px; height:180px; overflow:hidden; position:relative; } 
#slider { height: 180px; position:absolute; top:0; left:0; } 
.panel { width:960px; float:left; } 
#ui { margin:0; padding:0; } 
#ui li { float:left; list-style-type:none; }

Save the file. This is just a very basic skin, as much for layout as for decoration. The only disclaimer I can offer is that I'm a developer, not a designer. But the site should now at least look a lot tidier and much more like an actual website. The subnav in the top header is activated entirely by CSS in order to make our script have a smaller footprint. Of course, it won't work in IE6 (does anything, ever) nut we could easily fix this using our script if we were so inclined.

The most important bit really is the CSS for our hero panel; it's not quite finished yet, we'll need to add a couple more styles once we've added the JavaScript, but for now, the points to note are that the viewer element is set to the height and width of a single panel so that only one panel at a time is shown. The other panels are floated so that they stack up horizontally. The #slider element is positioned absolutely and will be made to be wide enough to fit in the number of panels. At this point the viewer won't display correctly and all the panels will be visible.

The reason for this is because the width of the #slider has not been set to accommodate all of the panels on a single line. We know that there are three panels because we've just built the site, but it would be easy enough to add a new panel, or several more, at a later date so we can't really hard-code a width into the CSS in case the number of panels changes. We'll handle setting the width in the script.

Here's how the homepage should appear at this point:


Another point to note is that we've added the imaginary logo via our CSS. We could just as easily change this so that an image from the media library was used instead, which would probably be preferable if we were handing the site off to a client.


Summary

In this part of the tutorial we added the remaining XSLT files/macros that build the news list page by pulling in snippets of each individual news story, and the news navigation menu which is shown on individual news stories and allows the visitor to move to different news stories without having to return to the main news page.

We also saw how easy it is to add any unique custom functionality that any particular page may need by creating a .Net user control (ascx file). This allows us to create a page with specialised content not defined in either the template or document type.

Lastly we added some very basic styling to tidy up the site and to lay the groundwork for the sliding hero panel, our last task before the site is complete. This is what we'll focus on in the next and final part of this tutorial.

Advertisement