Click here to Skip to main content
Click here to Skip to main content

SharePoint Page Navigation Web Part

, 25 Mar 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
Web Part for users to drop on their pages for navigation across the site collection.

Introduction

I'm having a great start to a new job. I'm getting the chance to explore SharePoint in many new ways. Working in a corporate environment (contrary to a software company) has exposed me directly to the needs of the users. Quite a few of our users have gone through SharePoint training, and know how to create and add pages to their departmental sites. One thing that SharePoint lacks is a Web Part that will allow users to drop a navigation part onto each of their pages to allow them to move from one page to the next. I've created a solution to that problem by creating a navigation part that will query libraries within a site collection and display these links to them using a JavaScript menu bar.

Installation

Hopefully, most of you already know how to install Web Parts, but if you need help, follow these steps:

  1. GAC both Mullivan.Shared.dll and Mullivan.SharePoint.WebParts.dll.
  2. Register Mullivan.SharePoint.WebParts.dll in the SharePoint Web Config.

    Go to C:\inetpub\wwwroot\wss\VirtualDirectories\<Port Number>\web.config. Add the following in between the SafeControls node:

    <SafeControl 
      Assembly="Mullivan.SharePoint.WebParts, 
                Version=1.0.0.0, Culture=neutral, PublicKeyToken=c37a514ec27d3057" 
      Namespace="Mullivan.SharePoint.WebParts" TypeName="*" Safe="True" />
  3. Go to Site Settings -> Webparts -> click New on the menu.

    Scroll to the bottom and check Mullivan.SharePoint.WebParts.LibraryNavigationWebPart, and then scroll back to the top and click Populate Gallery.

Done! You should see it in your Web Parts collection.

Configuration

After you've dropped the Web Part on the page, you'll want to modify its settings. You'll see a Navigation category at the top of the property pane. This is where you are going to configure each tab in the navigation control. Click the plus to add or double click any existing items to edit.

    Nav_Bar_Config.png

    Nav_Bar_Config_2.png

  • Title - What you want to appear as the text for the tab. If you leave it empty, then it will default to the list title.
  • List URL - Click Browse to point to the list you want to query.

    Nav_Bar_Select_List.png

  • Display Field - The internal SharePoint name of the list field that you want to use as the display text for each item that is displayed when the user mouses over the tab.
  • Query - The Camel query that will execute against that list to filter items. Make sure you use the internal name for the columns when building your query.

Example:

Nav_Bar_Set_Query.png

Click OK, and your first tab is created. You can then move it around and order the tabs appropriately.

Using the Code

We have three different environments at this company, and being able to easily deploy these Web Parts is a must. That means that there can be no resources that have to be dropped into the layouts directory in SharePoint such as images, pages, etc. So, there are three things we want to do when creating this Web Part. We want to embed our JavaScript, CSS, and images.

The first is to embed all JavaScript into this DLL and extract it using the ScriptManager. So, select your JS item (JS/LibraryNavigation.js) and go to the property pane and set the Build Action to Embedded Resource.

Now, we need to add a reference to the embedded resource to our AssemblyInfo.cs. Just append that line all the way at the bottom of the file. So, we need to give the WebResource the location of the resource and the mime type that it is. The location is formatted as "<DefaultNameSpace>.<Folder>.<FileName>". If you have multiple folders, then the format would be "<DefaultNameSpace>.<Folder1>.<Folder2>.<FileName>".

// Setting ComVisible to false makes the types in this assembly not visible 
// to COM components. If you need to access a type in this assembly from 
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("c0e90cc1-f501-4b02-97aa-fffccacfa00f")]

// Version information for an assembly consists of the following four values:
//
//      Major Version
//      Minor Version 
//      Build Number
//      Revision
//
// You can specify all the values or you can default the Build and Revision Numbers 
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")] 

[assembly: WebResource("Mullivan.SharePoint.WebParts.JS.Utility.js", "text/javascript")]

So now, we can get a URL to the JavaScript resource and register it to the page.

ClientScriptManager csm = this.Page.ClientScript;
Type lnType = this.GetType();
//Register our JavaScript file
if (!csm.IsClientScriptIncludeRegistered(
     @"Mullivan.SharePoint.WebParts.JS.LibraryNavigation.js"))
{
    string url = csm.GetWebResourceUrl(lnType, 
                 @"Mullivan.SharePoint.WebParts.JS.LibraryNavigation.js");
    csm.RegisterClientScriptInclude(lnType, 
                  "Mullivan.SharePoint.WebParts.JS.LibraryNavigation.js", 
                   ResolveClientUrl(url));
}

Great! Now we have our JavaScript attached. Let's move on to registering our CSS. We go through the same steps to embed our CSS file as we did with our JavaScript file. That is to set the Build Action to Embedded Resource and add the WebResource object to the AssemblyInfo.cs. Registering a CSS file can be tricky in SharePoint. This is because we can use the CSSRegistration class, but there is one problem. SharePoint will always load the Core.css after our registered CSS file. You might ask why that matters? Well, in CSS, the last file to load in the page is the winner. Its CSS styles will override anything before it. In our case, we wanted to change the style to the hyperlinks in the Web Part. If we don't get our CSS to load last, then the Core.css styles for hyperlinks will override ours. So, here is the trick to getting our CSS file to load last:

protected override void OnPreRender(EventArgs e)
{
    ClientScriptManager csm = this.Page.ClientScript;
    Type lnType = this.GetType();
    RegisterStyleSheet(ResolveUrl(csm.GetWebResourceUrl(lnType, 
                    @"Mullivan.SharePoint.WebParts.CSS.LibraryNavigation.css")));
    
    base.OnPreRender(e);
} 

private void RegisterStyleSheet(string cssUrl)
{
    //Find the HtmlHead control in the masterpage file
    HtmlHead header = FindControl<HtmlHead>(this.Page.Master.Controls);

    if (header != null)
    {
        Literal literal = new Literal();
        literal.Text = string.Format("<link href=\"{0}\" type" + 
                       "=\"text/css\" rel=\"stylesheet\"/>", cssUrl);
        //Add our link to our CSS file to the header
        header.Controls.Add(literal);
    }
}

private T FindControl<T>(ControlCollection controlCollection)
    where T : Control
{
    foreach (Control c in controlCollection)
    {
        if (c.GetType() == typeof(T))
            return (T)c;
        else
        {
            T child = FindControl<T>(c.Controls);
            if (child != null)
                return child;
        }
    }
    return null;
}

So now, you need to go and set your images and embedded resources and add the WebResource object to the AssemblyInfo.cs. Now, we can render our HTML and set our image tags to point to our web resource URL.

ClientScriptManager csm = this.Page.ClientScript;
Type lnType = this.GetType();
string libNavLeftUrl = ResolveUrl(csm.GetWebResourceUrl(lnType, 
       "Mullivan.SharePoint.WebParts.Images.LibNav_Left.jpg"));
string libNavMidUrl = ResolveUrl(csm.GetWebResourceUrl(lnType, 
       "Mullivan.SharePoint.WebParts.Images.LibNav_Mid.jpg"));
string libNavRightUrl = ResolveUrl(csm.GetWebResourceUrl(lnType, 
       "Mullivan.SharePoint.WebParts.Images.LibNav_Right.jpg"));
string libNavOverUrl = ResolveUrl(csm.GetWebResourceUrl(lnType, 
       "Mullivan.SharePoint.WebParts.Images.LibNav_Over.jpg"));
string libNavSubItemMidUrl = ResolveUrl(csm.GetWebResourceUrl(lnType, 
       "Mullivan.SharePoint.WebParts.Images.LibNav_SubItem_Mid.jpg"));
string libNavSubItemOverUrl = ResolveUrl(csm.GetWebResourceUrl(lnType, 
       "Mullivan.SharePoint.WebParts.Images.LibNav_SubItem_Over.jpg"));

writer.WriteLine("<div class=\"lnwp_Container\">");
writer.WriteLine("<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\">");
//create td with middle background image
writer.WriteLine(string.Format("<tr style=\"background" + 
                 "-image:url('{0}')\">", libNavMidUrl));
writer.WriteLine("<td>");
writer.WriteLine(string.Format("<div class=\"lnwp_LeftImage\" style" + 
                 "=\"background-image:url('{0}')\" />", libNavLeftUrl));
writer.WriteLine("</td>");

OK, now that we have all of our resources embedded, we can worry about how the users are going to configure this Web Part. It would have been really easy to just drop two text boxes into our Web Part config and make them give us a SPWeb URL and a list name. But, I figured it would be a lot cooler to let them use the List/Web selector that the Content Query Web Part uses! So, let's steal some JavaScript!

Here is our HTML for our Browse button in our configuration panel for the Web Part. The {n} tags are string replaces that I execute later. {0} is the ClientID for our web part. {1} is the ClientID for our View State textbox that holds the XML for our configuration. Finally, {7} is the RelativeServerUrl for our SPSite.

<input type="button" 
id="{0}_ListUrl_BUILDER" 
value="Browse..." 
title="Click to use list picker." 
tabindex="0" 
class="ms-PropGridBuilderButton" 
style="display:inline;cursor:pointer;width:55px;text-align:center"
onclick="javascript:LPNW_LaunchListPicker('{1}','{0}', '{7}')" />

Here is the OnClick method that we call:

function LPNW_LaunchListPicker(viewStateId, clientId, serverUrl) {

    var callback = function(results) {
        LPNW_SetList(clientId, results);
    };

    LaunchPickerTreeDialog('CbqPickerSelectListTitle', 'CbqPickerSelectListText', 
                           'listsOnly', '', serverUrl, lastSelectedListId, '', 
                           '', '/_layouts/images/smt_icon.gif', '', callback);
}

function LPNW_SetList(clientId, results) {
    var listTextBox = document.getElementById(clientId + "_ListUrl_EDITOR");

    if (results == null
        || results[1] == null
        || results[2] == null) return;

    if (results[2] == "") {
        alert("You must select a list!.");
        return;
    }

    lastSelectedListId = results[0];
    var listUrl = '';
    if (listUrl.substring(listUrl.length - 1) != '/')
        listUrl = listUrl + '/';
    if (results[1].charAt(0) == '/')
        results[1] = results[1].substring(1);
    listUrl = listUrl + results[1];
    if (listUrl.substring(listUrl.length - 1) != '/')
        listUrl = listUrl + '/';
    if (results[2].charAt(0) == '/')
        results[2] = results[2].substring(1);
    listUrl = listUrl + results[2];
    listTextBox.value = listUrl;
}

So, what we are going to get out of this is a string that looks like the following:<WebRelativeServerUrl>\<ListTitle>. You might be wondering ListTitle!?!?! What if the administrator changes the name of the list? The URL to the list stays the same, but the title changes!! Well, we are going to need to do some parsing in the C# code-behind to be able to get our SPList object form SharePoint. I'll go ahead and demonstrate.

private NavLink GetNavLink(LibNavLink link)
{
    NavLink navLink = new NavLink();
    SPSite site = SPContext.Current.Site;

    using (SPWeb web = site.OpenWeb(GetWebUrl(link.ListUrl)))
    {
        string listName = GetListName(link.ListUrl);
        SPList list = web.Lists[listName];

        if (list == null)
            throw new Exception(string.Format("List {0} could not be found.", 
                                link.ListUrl));

        //Set Defaults
        if (string.IsNullOrEmpty(link.DisplayField))
        {
            if (list.BaseType == SPBaseType.DocumentLibrary)
                link.DisplayField = "BaseName";
            else
                link.DisplayField = "Title";
        }

        navLink.Title = link.Title;
        navLink.Url = MullivanUtility.GetServerUrl(this.Page.Request) + 
                      list.DefaultViewUrl;

        SPQuery query = new SPQuery();
        if (!string.IsNullOrEmpty(link.Query))
            query.Query = link.Query;
        query.ViewAttributes = "Scope=\"Recursive\"";
        query.ViewFields = string.Format("<FieldRef Name=\"EncodedAbsUrl\" />" + 
                           "<FieldRef Name=\"{0}\" />", link.DisplayField);

        SPListItemCollection items = list.GetItems(query);
        foreach (SPItem item in items)
        {
            NavLink subLink = new NavLink();
            subLink.Title = item[link.DisplayField].ToString();
            subLink.Url = item["EncodedAbsUrl"].ToString();
            navLink.SubLinks.Add(subLink);
        }
    }

    return navLink;
}

private string GetWebUrl(string listUrl)
{
    string webUrl = string.Empty;
    listUrl = listUrl.TrimEnd('/');
    int lastIndex = listUrl.LastIndexOf("/");
    if (lastIndex > -1)
        webUrl = listUrl.Substring(0, lastIndex);

    return webUrl.TrimEnd('/');
}

private string GetListName(string listUrl)
{
    string listName = string.Empty;
    listUrl = listUrl.Trim('/');
    int lastIndex = listUrl.LastIndexOf("/");
    if (lastIndex > -1)
        listName = listUrl.Substring(lastIndex + 1, listUrl.Length - lastIndex - 1);

    return listName;
}

Points of Interest

There are two other Web Parts included in the project. The weather Web Part was taken from a blog made by the Mossman here. The other is a Web Part that I just blogged about. It's a really great Web Part that queries the Analytics database and pulls back the most used content either by a user or anonymously. I call it the Most Viewed Web Part. Check it out here.

Conclusion

Let me know what you think. If you have any ideas, then I'll be sure to take a look and see if I can provide an update. I hope you found this Web Part useful.

History

  • 14th January, 2009: Initial post
  • 19th March, 2009: Updated source code
  • 24th March, 2009: Updated source code

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Sike Mullivan
Software Developer (Senior)
United States United States
I am a Sr SharePoint Developer in Saint Louis, MO. I've been developing professionally for about four years now. For three years I worked for a Document Imaging company that developed applications for Scanning, Indexing, Migrating, and Searching SharePoint (MOSS, WSS). I'm now working on a team for a Cable company that customizes their internal and external SharePoint implementations.

Comments and Discussions

 
Generalconfiguring Sharepoint page navigation web part Pinmembersarath.chelika20-Oct-09 14:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141220.1 | Last Updated 25 Mar 2009
Article Copyright 2009 by Sike Mullivan
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid