Advertisement

Getting Started with Umbraco: Part 5

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →

The hero panel carousel that we're going to make in this part of the tutorial will use sliding animations to move between panels, and will be of the infinite style so that it will forever move forwards or backwards through each of the panels. It will not cycle through each panel and then reset back to the start, it will just keep on cycling through the panels indefinitely in one direction depending on whether the previous or next link is used.


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

Getting Started

First of all we need to work out how many content panels there are in the hero panel and create a navigation model for them. Of course at this point we know exactly how many panels there are because we created them in the back-end, but if we ever want to add more panels, or if we hand the site over to someone else to maintain and they add more panels, we don't want the feature to break, so instead of assuming there are only ever going to be three panels, we'll count them and add links to each one.

Create a new .js file, call it hero.js and save it in the scripts folder. Start by adding to it the following code:

(function ($) { 
 
    var baseEl = $("#hero"), 
        slider = baseEl.find("#slider"), 
        numPanels = baseEl.find(".panel").length, 
        panelWidth = baseEl.find("#viewer").width(), 
        curPanel = 1, 
        templateArr = [], 
                 functions = { 
    }; 
 
    slider.width(panelWidth * numPanels); 
 
    $.each(baseEl.find(".panel"), function (i) { 
        var tempObj = {}, 
            num = i + 1; 
 
        tempObj.id = "panel-" + num; 
        tempObj.href = "#"; 
        tempObj.title = "View Panel " + num; 
        tempObj.text = "Panel " + num 
 
        templateArr.push(tempObj); 
 
    }); 
 
    baseEl.find("#linkTemplate").tmpl(templateArr).insertBefore("#next"); 
    baseEl.find("#panel-1").find("a").addClass("on"); 
 
})(jQuery);

Don't forget to add a link to the new script file, as well as the tmpl plugin, in our BasePage.master file after the link to jQuery itself:

<script src="/scripts/jquery.tmpl.min.js"></script> 
<script src="/scripts/hero.js"></script>

The script resides within an anonymous function which is executed immediately and aliases the $ character for compatibility with jQuery's noConflict() method. We start out by setting a series of variables; the first two cache the selectors for a couple of the elements we'll be using frequently throughout the script. Note that the jQuery object that we create when selecting the outer #hero container is the one and only jQuery object we create in the entire script.

We also count the number of panels and store the width of the #viewer container and initialise a panel counter that will keep track of the active panel. We also create an empty array to store the navigation links in ready for templating, as well as an empty object that will be used store some functions in later on.

Next we set the width of the #slider element so that it is able to accommodate all of the panels stacked up horizontally.

Following this we create a navigation link for each of the panels. We do this using jQuery's each() method; for each panel we create a new empty object, then populate the object with an id for the link, and a href, title and some text. Each object is then pushed into the array we initialised a minute ago.

We then apply the template using the tmpl() method, specifying the now populated array as the data to apply to the template, and the #next link as the element to insert each new link before. Finally, we apply the class name on to the first numerical link in the hero panel, as when the page loads, the first panel will be visible.

We also need to add the templating mark-up to the masterpage for the home page. Add the following code directly after the #next link in HomePage.master:

<script id="linkTemplate" type="text/x-jquery-tmpl"> 
	<li id="${id}"> 
		<a href="${href}" title="${href}">${text}</a> 
	</li> 
</script>

Styling the Hero Panel

At this point can add a little additional styling for the hero panel; at the bottom of site.css add the following code:

#hero { 
	border:2px solid #969696; margin-top:20px; -moz-border-radius:4px; border-radius:4px; 
	position:relative; overflow:hidden; background-color:#eee 
} 
#viewer { width:960px; height:220px; overflow:hidden; position:relative; } 
#slider { position:absolute; top:0; left:0; } 
.panel { width:920px; padding:10px 20px; float:left; } 
#ui { margin:0 0 10px; padding:0; text-align:center; } 
#ui li { display:inline; margin-right:15px; list-style-type:none; } 
#ui a { font-weight:bold; text-decoration:none; color:#535353; outline:none; } 
#ui a:hover, #ui a.on { border-bottom:1px solid #535353; }

This is mostly a basic theme so that the hero panel has the same basic skin as the rest of the site, although some of the styling is required from a functional perspective. The #viewer should be set to the width and height of a single content panel and should have the overflow property set to hidden so that only one content panel at a time is visible. The #slider element should be positioned absolutely so that it is free to move within the #viewer.

The links used to navigate between the different panels are within an unordered list; each list-item is set to display inline so that they can be centered within the <ul> element. At this point the hero panel should appear like this:



Wiring up the Links

All we need to do now is add the functionality that makes the new navigation links work. We need to use several different functions to achieve this; first add the following code for the #prev and #next links to the empty functions object we declared with our variables at the start of the code:

prev: function () { 
	baseEl.find(".on").removeClass("on"); 
 
	if (curPanel === 1) { 
 
		baseEl.find(".panel").eq(numPanels - 1).prependTo(slider); 
		slider.css("left", -panelWidth).animate({ 
			left: "+=" + panelWidth + "px" 
		}, function () { 
			baseEl.find("#ui a").eq(numPanels).addClass("on"); 
 
			baseEl.find(".panel").eq(0).appendTo(slider); 
			slider.css("left", -(numPanels - 1) * panelWidth); 
			curPanel = numPanels; 
		}); 
	} else { 
		slider.animate({ 
			left: "+=" + panelWidth + "px" 
		}, function () { 
			baseEl.find("#ui a").eq(curPanel - 1).addClass("on");                         
			curPanel--; 
		}); 
	} 
}, 
next: function () { 
	baseEl.find(".on").removeClass("on"); 
 
	if (curPanel === numPanels) { 
 
		baseEl.find(".panel").eq(0).appendTo(slider); 
		slider.css("left", -(panelWidth * (numPanels - 2))).animate({ 
			left: "-=" + panelWidth + "px" 
		}, function () { 
 
			baseEl.find("#ui a").eq(1).addClass("on"); 
			baseEl.find(".panel").eq(numPanels - 1).prependTo(slider); 
			slider.css("left", 0); 
			curPanel = 1 
		}); 
	} else { 
              slider.animate({ 
			left: "-=" + panelWidth + "px" 
		}, function () { 
			baseEl.find("#ui a").eq(curPanel + 1).addClass("on"); 
			curPanel++; 
		}); 
	} 
}

These two functions are very similar, with just some subtle differences. We'll look at the prev function first in detail and then look at the differences in next. The first thing we do is remove the on class from the element that currently has it. We then check to see if the first panel is currently visible by checking the curPanel variable.

If the first panel is visible we select the first panel and move it to the end of the #slider element and correct the left CSS style of the #slider so that the first panel is still visible. To correct the positioning of the #slider element we just move it minus one panel width to the left. We then animate from the first panel backwards to the last panel.

Once the animation is complete we add the on class to the navigation link for the last panel, move the first panel back to its original location in the #slider element and again correct the left position of the #slider so that the last panel is still visible. Working out the left position is slightly more complex at this point; we need to move the #slider element the width of the slider in total minus the width of one panel to the left, hence why this is a minus figure. Finally we update the curPanel variable to the last panel.

If the first panel is not visible we simply animate to the previous panel and then update the on state for the navigation link and the curPanel variable to store the new panel id. This is all we need to do when the #prev navigation link is clicked.

As I said, the #next behaviour is very similar, essentially we are doing the same things, but this time we need to check whether the last panel is currently visible instead of the first panel. When on the last panel, we move the last panel to the start of the #slider element and correct the left position by moving the #slider the width of the #slider minus the width of 2 panels to the left. We then animate to the first panel before moving the last panel back to its correct location at the end of the #slider element.

If we are not on the last panel when the #next link is clicked we simply animate to the next panel along. Whether we are on the last panel or not, we also move the on class to the relevant navigation link and update our curPanel tracking variable. Note that when we adjust the left position of the #slider after moving either the first or last panels, the change is instant and so is not noticeable. This is what makes the carousel appear infinite.

The two functions we just added are simply stored within an object; they won't do anything on their own. The last bit of functionality we need to add is a way of calling these functions and some behaviour for the numerical navigation links that were added dynamically. We can do all of that with the following click handler:

baseEl.find("#ui li").click(function (e) { 
	e.preventDefault(); 
 
	if (!slider.is(":animated")) { 
		if (this.id === "prev" || this.id === "next") { 
 
			functions[this.id].call(); 
		} else { 
			var target = parseInt(this.id.split("-")[1]), 
				animString = (target > curPanel) ? "-=" + ((target - curPanel) * panelWidth) + "px" : "+=" + ((curPanel - target) * panelWidth) + "px"; 
 
			slider.animate({ 
				left: animString 
			}, function () { 
				baseEl.find(".on").removeClass("on"); 
				baseEl.find("#ui a").eq(target).addClass("on"); 
				curPanel = target; 
			}); 
		} 
	} 
});

The click handler is attached to the <li> elements in the navigation links, but we still need to stop the browser following the link inside each list item using the preventDefault() method of the event object.

We start out be checking that the hero panel is not already being animated, as this would cause it break in the #prev or #next links were clicked too quickly. We then check whether the id of the navigation link that was click is equal to prev or next. If it is, we call the relevant prev or next function in our functions object using JavaScript's call() function.

If the id is not prev or next we know one of the numerical links was used instead. In this case we first get the id number of the list item that was clicked and then use this to animate the correct number of pixels in the correct direction to move the #slider so that the relevant panel is visible.

To determine the distance and the direction we first check whether the number of the target panel is great than the number of the current panel. If it is, we animate the #slider element to the left. To work out the distance in pixels to move we subtract the current panel number from the target panel number and multiply the result by the width of a panel.

If the number of the target panel is less than the number of the current panel, we instead subtract the number of the target panel from the current panel and multiply this figure by the width of a panel.

Once we have the direction and distance saved in the animString variable we can then perform the animation by moving the #slider element using the animString. Once the animation is complete we update the on class of the relevant navigation item and set the curPanel vaiable to the number of the target panel.

This is all we need to do; we should now have a completely functional managed-content Umbraco website, and an attractive carousel. If more panel nodes are added from the back-end, the carousel will automatically update itself to accommodate the new panels and should continue to work flawlessly.


Summary

This now brings us to the end of the Getting started with Umbraco series. I hope that by now you can see the beauty and power of the Umbraco CMS and are as devoted a follower as myself. Let's briefly recap the key points from each part on the series.

We learned that we set up Document types to define the different types of nodes that we can create in the back-end. These Document Types specify how the different nodes are structured, and the different fields that should be editable and content-managed. Each node usually relates roughly to a page of the web site, but ultimately they may represent any piece of content-managed data.

Each Document Type is usually associated with a template which defines the HTML code and Umbraco items or macros that may be used to render the page. Templates are simply .Net masterpages. Masterpages are usually nested in Umbraco to maximise code-reuse. Not all Document Types need to be associated with a template however, as we saw with our hero panels.

The templates can reference the editable content added to the back-end using Umbraco:Item objects in order to display the managed-content. XSLT (or Razor in newer versions of Umbraco) can be used to iterate over a set of nodes and generate dynamic items such as the navigation menus for the site, or hub pages like our news listing page. The XSLT (or Razor) can also be added directly to templates for simple data processing.

Code-behind files are not used for any of the standard pages of the site, but if we do need to make use of them we can insert standard .Net usercontrols into our templates as we saw when creating our contact form.

Although pre-built site templates can be installed, Umbraco makes it extremely easy to build a completely bespoke, managed-content web site from scratch.

Everything about the CMS is completely customizable. We've covered the basics over the course of this series, but there is so much more to the CMS than what we have looked at so far.

Advertisement