10

I'm working on a custom current (left) navigation on a SharePoint solution.

What I need is that the root of the navigation is a variation web, the immediate child of the root web. All the sites and pages which are immediate children of this variation should be visible, though not expanded. Only sites which are ancestors of the current site should be expanded... all the way down to the current site/page.

An example... if I start on page http://spsite.ex/variation/site2/subsite2.1/subsite2.1.1/subsite2.1.1.3/page.aspx I should see...

Site1
Site2
    SubSite2.1
        SubSite2.1.1
            SubSite2.1.1.1
            SubSite2.1.1.2
            SubSite2.1.1.3
                page.aspx (YOU ARE HERE)
    SubSite2.2
    Site2Page1
    Site2Page2
Site3
Site4
Site5

If I then click on the link for SubSite2.1 I should see something like...

Site1
Site2
    SubSite2.1 (YOU ARE HERE)
        SubSite2.1.1
    SubSite2.2
    Site2Page1
    Site2Page2
Site3
Site4
Site5

If I then navigate to http://spsite.ex/variation/site5/subsite5.1/page.aspx I should see something like...

Site1
Site2
Site3
Site4
Site5
    SubSite5.1
        SubSite5.1.1
        page.aspx (YOU ARE HERE)

I've written a solution, but I feel like it's not one I should feel proud of; I've given the AspMenu a near-inifinite StaticDisplayLevels and then extended PortalSiteMapProvider, overriding GetChildNode(node) to not get child nodes, except for ancestors of the current web.

Richard JP Le Guen
  • 28,364
  • 7
  • 89
  • 119
  • Does your solution work? – Paul Lucas Apr 08 '10 at 21:20
  • Yup! I guess I'm kind of looking for validation that I've understood what I'm doing and how I should be doing it, or if I need to go buy some bad code offsets :P I mean, near-inifinite `StaticDisplayLevels`... and if the `PortalSiteMapDataSource` has a `StartingNodeOffset` of 0 (from the root) I get exceptions... so it smells a little. – Richard JP Le Guen Apr 08 '10 at 21:35
  • 2
    This is the sort of thing that Sharepoint should really allow you to do with the out-of-the-box navigation control, seeing how commonly it is used on the internet - maybe in the next version after 2010... – Henry C Jun 25 '10 at 01:10
  • I'm looking for something similar. Did your solution work, and if so, can you share a bit of your code? – ScottE Nov 17 '10 at 11:56
  • @ScottE - Iwish I could, but I'm no longer at that company, and I didn't keep a copy as I wasn't proud of the solution... I'll see if some msdn reading will refresh my memory. – Richard JP Le Guen Nov 17 '10 at 13:44

4 Answers4

1

@ScottE, I think I've managed to reproduce the code I used to solve this problem:

using System;
using System.Web;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Publishing;
using Microsoft.SharePoint.Publishing.Navigation;

namespace StackOverflow.SharePoint
{
    public class Question2602537PortalSiteMapProvider : PortalSiteMapProvider
    {

        public override SiteMapNodeCollection GetChildNodes(System.Web.SiteMapNode node)
        {
            bool expandChildNodes = false;
            if (SPContext.Current != null)
            {
                expandChildNodes = NodeIsAncestorOfCurrentNode(node);
            }

            if (expandChildNodes)
            {
                return base.GetChildNodes(node);
            }
            else
            {
                return new SiteMapNodeCollection();
            }
        }

        private bool NodeIsAncestorOfCurrentNode(System.Web.SiteMapNode node)
        {
            bool returnvalue = false;
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                using (SPSite thisSite = new SPSite(SPContext.Current.Site.ID))
                {
                    using (SPWeb nodeWeb = this.OpenWeb(thisSite, node))
                    {
                        using (SPWeb currentWeb = this.OpenNavWeb(thisSite))
                        {
                            returnvalue = this.AncestorDescendantWebs(nodeWeb, currentWeb);
                        }
                    }
                }
            });
            return returnvalue;
        }

        private SPWeb OpenWeb(SPSite thisSite, System.Web.SiteMapNode node)
        {
            // need to use Uri objects, as sometimes the node URL contains a query string
            // but calling OpenWeb(...) with a ? in your URL throws an exception
            // using Uri.LocalPath removes the Query String
            Uri siteUri = new Uri(thisSite.Url);
            Uri nodeUri = new Uri(siteUri, node.Url);
            return thisSite.OpenWeb(nodeUri.LocalPath.Split(new string[] { "/_" }, StringSplitOptions.RemoveEmptyEntries)[0], false);
        }

        private SPWeb OpenNavWeb(SPSite thisSite)
        {
            using (SPWeb currentWeb = thisSite.OpenWeb(this.CurrentWeb.ID))
            {
                SPWeb web = currentWeb;
                PublishingWeb publishingWeb = PublishingWeb.GetPublishingWeb(web);

                // Loop all the way up the webs until we find the one which doesn't inherit
                // (there's gotta be a better way of doing this)
                while (publishingWeb.InheritCurrentNavigation &&
                    !web.ID.Equals(thisSite.RootWeb.ID))
                {
                    web = web.ParentWeb;
                    publishingWeb = PublishingWeb.GetPublishingWeb(web);
                }

                return web;
            }
        }

        private bool AncestorDescendantWebs(SPWeb ancestor, SPWeb descendant)
        {
            // check the URLs to determine if descendant is a subweb or ancestor
            // (there's gotta be a better way...)
            if ((descendant.ServerRelativeUrl + "/").ToUpper().StartsWith(ancestor.ServerRelativeUrl.ToUpper() + "/"))
            {
                return true;
            }
            return false;
        }

    }
}

Perhaps not the best solution... but a solution.

Richard JP Le Guen
  • 28,364
  • 7
  • 89
  • 119
  • Looking at [@Pauli Østerø's answer](http://stackoverflow.com/questions/2602537/sharepoint-custom-current-navigation-portalsitemapprovider/4612334#4612334) I wonder if method `SiteMap.CurrentNode.IsEqualToOrDescendantOf(SiteMapNode)` could be used to avoid creating so many `SPWeb` objects. – Richard JP Le Guen Feb 06 '11 at 04:43
0

If you want to do a custom coded solution you could create a class inheriting from HierarchicalDataBoundControl. Hook it up with the PortalSiteMapDataSource in you masterpage/pagelayout. This will give you full control of the output and is as optimized as can be.

jeslas
  • 41
  • 1
  • 4
0

What does your code look like... a typical menu like this using standard SiteMapProvider can't be made much simpler than this

public class SideMenu : Control
{
    private SiteMapNode _rootNode = SiteMap.RootNode;
    public SiteMapNode RootNode
    {
        get { return this._rootNode; }
        set { this._rootNode = value; }
    }

    public SideMenu()
    {
        ID = "SideMenu";
    }

    protected override void CreateChildControls()
    {
        var div = new HtmlGenericControl("div");
        div.Attributes.Add("id", ID);
        Controls.Add(div);

        CreateMenuNodes(RootNode, div);

        base.CreateChildControls();
    }

    protected override void Render(HtmlTextWriter writer)
    {
        if (!ChildControlsCreated)
        {
            CreateChildControls();
        }

        base.Render(writer);
    }

    private void CreateMenuNodes(SiteMapNode node, HtmlGenericControl container)
    {
        if (node.HasChildNodes)
        {
            var ul = new HtmlGenericControl("ul");
            container.Controls.Add(ul);

            foreach (SiteMapNode child in node.ChildNodes)
            {
                var li = new HtmlGenericControl("li");
                ul.Controls.Add(li);

                var a = new HtmlAnchor()
                {
                    InnerHtml = HttpUtility.HtmlEncode(child.Title),
                    Title = child.Title,
                    HRef = child.Url
                };

                li.Controls.Add(a);

                if (SiteMap.CurrentNode.IsEqualToOrDescendantOf(child))
                {
                    li.Attributes["class"] = "selected";

                    CreateMenuNodes(child, li);
                }
            }
        }
    }
}
Pauli Østerø
  • 6,878
  • 2
  • 31
  • 48
0

Here is another option that is a lot more elegant. http://sharepoint2010customnavigation.blogspot.com/

bhavinp
  • 823
  • 1
  • 9
  • 18