Click here to Skip to main content
15,880,796 members
Articles / Web Development / ASP.NET

SharePoint Page Navigation Web Part

Rate me:
Please Sign up or sign in to vote.
4.56/5 (4 votes)
25 Mar 2009CPOL6 min read 126.8K   540   19   41
Web Part for users to drop on their pages for navigation across the site collection.
Image 1

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:

    XML
    <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>".

C#
// 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.

C#
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:

C#
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.

C#
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.

HTML
<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:

JavaScript
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.

C#
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)


Written By
Software Developer (Senior)
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralRe: Error when clicking on browse button Pin
Karlheinz Ochs7-May-09 4:33
Karlheinz Ochs7-May-09 4:33 
GeneralRe: Error when clicking on browse button Pin
Sike Mullivan7-May-09 6:27
Sike Mullivan7-May-09 6:27 
GeneralRe: Error when clicking on browse button Pin
Sike Mullivan7-May-09 6:32
Sike Mullivan7-May-09 6:32 
GeneralRe: Error when clicking on browse button Pin
Elfroth8-May-09 4:42
Elfroth8-May-09 4:42 
GeneralRe: Error when clicking on browse button Pin
hninel13-Nov-09 8:26
hninel13-Nov-09 8:26 
GeneralRe: GAC both Mullivan.Shared.dll and Mullivan.SharePoint.WebParts.dll. Pin
MichealBarlow16-Mar-09 3:20
MichealBarlow16-Mar-09 3:20 
GeneralRe: GAC both Mullivan.Shared.dll and Mullivan.SharePoint.WebParts.dll. Pin
MichealBarlow16-Mar-09 3:47
MichealBarlow16-Mar-09 3:47 
Generallibrary navigation web oart Pin
kingn122-Feb-09 4:24
kingn122-Feb-09 4:24 
System.ArgumentException: Value does not fall within the expected range.
i kept on getting this when i get a query at the librarywebpart
what does it mean ?
GeneralRe: library navigation web oart Pin
Sike Mullivan23-Feb-09 16:45
Sike Mullivan23-Feb-09 16:45 
Generaldifficulty in downloading Pin
kingn122-Feb-09 3:35
kingn122-Feb-09 3:35 
GeneralSome Changes Pin
yomismo43244-Feb-09 23:43
yomismo43244-Feb-09 23:43 
GeneralMan oh man.. Pin
nEo.X20-Jan-09 8:02
nEo.X20-Jan-09 8:02 
GeneralRe: Man oh man.. Pin
Sike Mullivan20-Jan-09 10:39
Sike Mullivan20-Jan-09 10:39 
QuestionNice idea. How about code? Pin
TylerBrinks14-Jan-09 17:20
TylerBrinks14-Jan-09 17:20 
AnswerRe: Nice idea. How about code? Pin
Sike Mullivan14-Jan-09 17:24
Sike Mullivan14-Jan-09 17:24 
GeneralRe: Nice idea. How about code? Pin
TylerBrinks14-Jan-09 17:28
TylerBrinks14-Jan-09 17:28 

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

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