Theming SharePoint (5 of Infinite)

Today I bring you more of the same! Well, not quite, but today’s post is about navigation. Having covered the left / quick launch navigation, I decided it was time to tackle the top navigation. Reasons? Drop down menus are definately nice, and I think there is a possibility to again get away from tables while adding some nice styling and better functionality. Enter javascript!

Step 1: Get this JavaScript menu (.js) and put it into “C:\…\12\TEMPLATE\LAYOUTS\1033\”

Step 2: Time to get Visual Studio open again, remember that menu we made for the left hand side navigation? Well we can use the same class and reconfigure it a bit for the top nav. This code adds some extra functionality that I will cover in a step or two. Once you are done creating the class, follow the same steps mention in the 4th installment of this series to get the navigation control installed.

using System;
using System.Collections;
using System.Globalization;
using System.ComponentModel;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Xml;
using System.Xml.XPath;
using Microsoft.SharePoint.Utilities;

namespace MMOinsite.SharePoint.Components
{
    [AspNetHostingPermission(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal),
     AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    public class DropDownNavigation : HierarchicalDataBoundControl
    {
        private XmlDocument navigationTree;
        private string currentNodeKey = string.Empty;

        private string linkClass = "underline";
        private string listItemClass = "";
        private string listClass = "";
        private string linkSelectedClass = "";

        private bool appendLevel = false;
        private string appendLevelClass = "";

        int counter = 1;

        protected override void PerformDataBinding()
        {
            base.PerformDataBinding();

            // Oh god, no data source! ABORT!
            if (!IsBoundUsingDataSourceID && (DataSource == null))
                return;

            HierarchicalDataSourceView view = GetData("");

            if (view == null)
                throw new InvalidOperationException("Cannot get any data from the data source control.");

            IHierarchicalEnumerable enumerable = view.Select();
            if (enumerable != null)
            {
                navigationTree = new XmlDocument();
                navigationTree.LoadXml("");

                try
                {
                    AddNavigationItem(navigationTree.DocumentElement, enumerable, 1);
                }
                finally { }

                ViewState["navXml"] = navigationTree.OuterXml;
            }
        }

        private void AddNavigationItem(XmlElement currentNode, IHierarchicalEnumerable enumerable, int depth)
        {
            foreach (object item in enumerable)
            {
                IHierarchyData data = enumerable.GetHierarchyData(item);

                NavigationItem navItem = BuildNavigationItem(data, depth);

                XmlElement newNavElement = BuildNavigationNode(currentNode, navItem);

                currentNode.AppendChild(newNavElement);

                if (data.HasChildren)
                {
                    IHierarchicalEnumerable newEnumerable = data.GetChildren();

                    if (newEnumerable != null)
                        AddNavigationItem(newNavElement, newEnumerable, depth + 1);
                }
            }
        }

        private NavigationItem BuildNavigationItem(IHierarchyData data, int depth)
        {
            NavigationItem tmpNavItem = new NavigationItem();

            try
            {
                INavigateUIData navNode = (INavigateUIData)data;

                tmpNavItem.Title = navNode.Value;
                tmpNavItem.Url = navNode.NavigateUrl;
                tmpNavItem.Level = depth;

                if (currentNodeKey.Length == 0)
                {
                    SiteMapDataSource dataSource = GetDataSource() as SiteMapDataSource;

                    if (dataSource != null)
                        if (dataSource.Provider.CurrentNode != null)
                            currentNodeKey = dataSource.Provider.CurrentNode.Url.ToLower();
                }

                if (string.Compare(tmpNavItem.Url, currentNodeKey, true, CultureInfo.InvariantCulture) == 0)
                    tmpNavItem.Selected = true;
            }
            catch (Exception ex)
            {
                throw ex;
            }

            return tmpNavItem;
        }

        private NavigationItem BuildNavigationItem(XmlNode node)
        {
            NavigationItem tmpNavItem = new NavigationItem();

            tmpNavItem.Title = node.Attributes["title"].InnerText;
            tmpNavItem.Url = node.Attributes["url"].InnerText;
            tmpNavItem.Selected = Convert.ToBoolean(node.Attributes["isSelected"].InnerText);
            tmpNavItem.Level = Convert.ToInt32(node.Attributes["level"].InnerText);

            return tmpNavItem;
        }

        private XmlElement BuildNavigationNode(XmlElement parentNode, NavigationItem navItem)
        {
            XmlElement tmpNode = navigationTree.CreateElement("node");

            tmpNode.SetAttribute("url", navItem.Url);
            tmpNode.SetAttribute("title", navItem.Title);
            tmpNode.SetAttribute("level", navItem.Level.ToString());
            tmpNode.SetAttribute("isSelected", navItem.Selected.ToString());

            return tmpNode;
        }

        protected override void Render(HtmlTextWriter writer)
        {
            if (navigationTree == null && ViewState["navXml"] != null)
            {
                navigationTree = new XmlDocument();
                navigationTree.LoadXml(ViewState["navXml"].ToString());
            }

            XmlNodeList nodeList = navigationTree.SelectNodes("nodes/node");

            if (nodeList.Count > 0)
            {
                counter = 1;
                foreach (XmlNode node in nodeList)
                {
                    RenderOne(node, writer);

                    foreach(XmlNode subNode in node.SelectNodes("node"))
                        Render(subNode, writer, true);
                }
            }
        }

        protected virtual void WriteListStart(HtmlTextWriter writer)
        {
            writer.Write(string.Format("
    ", ListClass)); } protected virtual void WriteListEnd(HtmlTextWriter writer) { writer.Write("
"); } protected virtual void RenderOne(XmlNode navNode, HtmlTextWriter writer) { NavigationItem navItem = BuildNavigationItem(navNode); writer.Write("
"); writer.Write("
"); writer.Write( string.Format( "{2}", (navItem.Selected) ? LinkSelectedClass : LinkClass, navItem.Url, navItem.Title ) ); writer.Write("
"); writer.Write("
"); writer.Write(" "); writer.Write("
"); counter++; } protected virtual void Render(XmlNode navNode, HtmlTextWriter writer, bool isHeader) { NavigationItem navItem = BuildNavigationItem(navNode); string navItemDivId = navItem.Title.Replace(" ", "-") + "ImgDiv"; if (isHeader) { writer.Write("
"); writer.Write("
0) writer.Write("onmouseover=\"ddMenu('" + counter.ToString() + "',1)\" onmouseout=\"ddMenu('" + counter.ToString() + "',-1)\""); writer.Write(">"); writer.Write( string.Format( "{2}", (navItem.Selected) ? LinkSelectedClass : LinkClass, navItem.Url, navItem.Title, navItemDivId ) ); writer.Write("
"); writer.Write("
0) writer.Write("onmouseover=\"cancelHide('" + counter.ToString() + "')\" onmouseout=\"ddMenu('" + counter.ToString() + "',-1)\""); writer.Write(">"); counter++; } else { writer.Write( string.Format( "
  • ", ListItemClass, (AppendLevel) ? string.Format(" {0}{1}", AppendLevelClass, navItem.Level) : "" ) ); writer.Write( string.Format( "{2}", (navItem.Selected) ? LinkSelectedClass : LinkClass, navItem.Url, navItem.Title, navItemDivId ) ); } if (navNode.ChildNodes.Count > 0 && isHeader) { WriteListStart(writer); XmlNodeList nodeList = navNode.SelectNodes("node"); foreach (XmlNode childNode in nodeList) Render(childNode, writer, false); WriteListEnd(writer); } if (isHeader) { writer.Write("
  • "); writer.Write("
    "); } else { writer.Write(" "); } } public string ListClass { get { return listClass; } set { listClass = value; } } public string LinkSelectedClass { get { return linkSelectedClass; } set { linkSelectedClass = value; } } public string LinkClass { get { return linkClass; } set { linkClass = value; } } public string ListItemClass { get { return listItemClass; } set { listItemClass = value; } } public bool AppendLevel { get { return appendLevel; } set { appendLevel = value; } } public string AppendLevelClass { get { return appendLevelClass; } set { appendLevelClass = value; } } } }

    Step 3: We need to add the styles to our ever-growing CSS file! Here they are.

    .dropdown {
    	float: left;
    }
    .dropdown dt {
    	width: 150px;
    	height: 22px;
    	border: 0px solid #000;
    	padding: 0px;
    	font-weight: bold;
    	cursor: pointer;
    	text-align: center;
    }
    .dropdown a{
    	color:white;
    }
    .dropdown dt:hover {
    	background: url('/_layouts/images/topnavhover.gif');
    }
    .dropdown dd {
    	position: absolute;
    	overflow: hidden;
    	margin-top: 0px;
    	margin-left: 0px;
    	width: 170px;
    	height: 22px;
    	display: none;
    	background: #fff;
    	z-index: 200;
    	opacity: 0;
    
    	border: 1px solid #25475A;
    	background: #DAE8EF;
    	border-top: 0px;
    }
    .dropdown ul {
    	margin: 0px;
    	width: 170px;
    
    	list-style: none;
    	border-top: none;
    }
    .dropdown li {
    	display: inline;
    	text-align: left;
    }
    
    .dropdown a, .dropdown a:active, .dropdown a:visited {
    	font-size: 0.8em;
    	padding: 2px;
    	padding-top: 3px;
    	display: block;
    	color: #2D2D2D;
    	font-weight: bold;
    	text-decoration: none;
    	width: 100%;
    }
    .dropdown a:hover {
    	/*background: #7BC143;*/
    	text-decoration: underline;
    	color: #000;
    }
    .dropdown .underline {
    }

    Step 4: Now what was that extra functionality? Well for our SharePoint site I have created an image slide animation that slides out from the top nav. This is so when users hover over the various departments, we can show some images about what the departments do in the whitespace of the site. Here is rollover.js, which again we put in the same directory as the other js file.

    The code is a modified version of the previous menu code to include resizing the margin instead of just the height. This will force the div to slide up rather than down which is what we want. I have also modified it to include a hack (currh < 20 instead of < 2) to make sure the div is hidden.

    var timerLen = 15;
    var slideSpeed = 5;
    
    function Roll(divId, dir)
    {
        var d = document.getElementById(divId);
    
        if(d == null)
            return;
    
        clearInterval(d.timer);
    
        if(dir == 1){
            if(d.maxh && d.maxh <= d.offsetHeight)
                return;
            else if(!d.maxh){
                d.style.height = 'auto';
                d.maxh = 100;
                d.style.height = '0px';
            }
            d.style.display = 'block';
            d.timer = setInterval(function(){DoSlide(d, 1)}, timerLen);
        }
        else{
            d.timer = setInterval(function(){DoSlide(d,-1)}, timerLen);
        }
    }
    
    function DoSlideIn(d){
        d.timer = setInterval(function(){DoSlide(d,-1)}, timerLen);
    }
    
    function DoSlide(d,dir)
    {
        var currh = d.offsetHeight;
    
        var dist;
    
        if(dir == 1)
            dist = ((d.maxh - currh) / slideSpeed);
        else
            dist = (currh / slideSpeed);
    
        if(dist <= 1 && dir == 1)
            dist = 1;
    
        d.style.height = currh + (dist * dir) + 'px';
        d.style.opacity = currh / d.maxh;
    
        d.style.marginTop = 100 - (currh * 100 / d.maxh) + 'px';
    
        d.style.filter = 'alpha(opacity=' + (currh * 100 / d.maxh) + ')';
    
        if((currh < 20 && dir != 1) || (currh > (d.maxh – 2) && dir == 1)){
            clearInterval(d.timer);
    
            if(dir != 1){
                d.style.marginTop = 100;
                d.style.display = ‘none’;
            }
        }
    }
    
    function RollOut(divId)
    {
        Roll(divId, 1);
    }
    
    function RollIn(divId)
    {
        Roll(divId, -1);
    }

    Step 5: Now that we have the code setup, we need to add the controls and js files to the masterpage! First we need the javascript files. I was having trouble with the second, but a manual include worked.