Multi-Level Top Link Bar Navigation (Sub Menus)

Applies To: SharePoint 2010

The Top Link Bar (sometimes called the Tab Bar) allows you to include a mix of links and headers (with child links). By default, this results in a single sub menu and handles most situations well.

IMG_0347

However, the need to have multiple levels (additional sub menus) often comes up. There are several solutions out there that suggest replacing the Top Navigation Menu control altogether using javascript or CSS menus. These work but are also totally unnecessary. The Top Link Bar uses a standard ASP.NET menu control and comes with several attributes that can be customized.

In the v4.master (SharePoint 2010) the section we’re interested in can be found inside the PlaceHolderTopNavBar ContentPlaceHolder (Specifically the SharePoint:AspMenu control TopNavigationMenuV4):

					<asp:ContentPlaceHolder id="PlaceHolderTopNavBar" runat="server">
					<h2 class="ms-hidden">
					<SharePoint:EncodedLiteral runat="server" text="<%$Resources:wss,topnav_pagetitle%>" EncodeMethod="HtmlEncode"/></h2>
							<asp:ContentPlaceHolder id="PlaceHolderHorizontalNav" runat="server">
<SharePoint:AspMenu
  ID="TopNavigationMenuV4"
  Runat="server"
  EnableViewState="false"
  DataSourceID="topSiteMap"
  AccessKey="<%$Resources:wss,navigation_accesskey%>"
  UseSimpleRendering="true"
  UseSeparateCss="false"
  Orientation="Horizontal"
  StaticDisplayLevels="2"
  MaximumDynamicDisplayLevels="1"
  SkipLinkText=""
  CssClass="s4-tn"/>
<SharePoint:DelegateControl runat="server" ControlId="TopNavigationDataSource" Id="topNavigationDelegate">
	<Template_Controls>
		<asp:SiteMapDataSource
		  ShowStartingNode="False"
		  SiteMapProvider="SPNavigationProvider"
		  id="topSiteMap"
		  runat="server"
		  StartingNodeUrl="sid:1002"/>
	</Template_Controls>
</SharePoint:DelegateControl>
							</asp:ContentPlaceHolder>
					</asp:ContentPlaceHolder>

The key attribute can be found on line 355 above. The MaximumDynamicDisplayLevels defaults to 1. Setting this to 2 or higher will control how many levels of sub menus you want to allow. Once you’ve made this change to your Master Page (saved, checked-in, and approved), SharePoint will be able to display multiple sub menus!

Unfortunately, the navigation settings found in Site Actions > Site Settings > Navigation (under Look and Feel) only allow top level Header entries and won’t display or allow you to edit navigation nodes at levels greater than 2:

NavigationSettingsArrgg

So even though we made the correct change to the control we have no native way to edit these sub menus. Fortunately this can be done with PowerShell (Or just plain .NET) which will be the subject of the next post. However, this is a necessary first step towards Multi-Level Top Bar Links.

Links List with Favicons and Under the QuickLaunch

Applies To: SharePoint

SharePoint has a handy list called Links that makes putting together a list of links with a display name pretty simple. Since it’s a normal list you can use views or even XSLT to make it look nice wherever you display it on the page. By default, here’s what a small links list looks like using the Summary View:

It’s not too bad, especially for a simple team site. But with just a little extra work you can have that same list of links display with their favicons and you can move them to some relatively unused real estate – under the QuickLaunch, and on every page in your site.

I’m combining these techniques because that was what I did. Fortunately, you can use the bulk of my tips to get nearly any web part to show up below the QuickLaunch. You can also just use the Favicon information to make your link display snazzy. Also, although I’m demonstrating all of this in SharePoint 2010, you should be able to do everything in SharePoint 2007 as well.

Displaying a Web Part Beneath the QuickLaunch

In order to place a Web Part below the QuickLaunch, you’re going to have to edit the Master Page. There are a couple of options. You can add a Web Part Zone and then customize this area on a page by page basis, or you can do what I’m going to demonstrate: add a specific web part to every page on your site.

Open your site in SharePoint Designer (Site Actions -> Edit in SharePoint Designer). Choose Master Pages in the Navigation pane and right-click on v4.master and choose Copy then right-click and choose Paste. Right-click on the new Master Page, v4_copy(1).master, and choose Rename. Once you’ve renamed it, right-click on it and select Edit File in Advanced Mode:

Depending on your site’s settings, you might have to check it out. If so, make sure you check it back in when done and verify you’ve published a major version so that those without full control can see your changes.

We’re going to place our web part right below the quicklaunch. So scroll down to approximately line 594 (in Code view) where you should see two closing divs shortly below the PlaceHolderQuickLaunchBottomV4 UIVersionedContent control. If you want your web part to be included in the leftpanel then press enter after the closing div in line 592, if you want it placed below the box press enter after the closing div in line 594:

Type <br /> and press enter again. Press Save. You’ll get a warning about customizing the page, go ahead and click Yes.

Now switch to the Insert ribbon and select Web Part > Content Query:

Switch to the Design view and right-click on your new web part and choose Web Part Properties. In the dialog window expand the Query section. Choose Show items from the following list under Source and click Browse… and choose your Links list.

Expand the Presentation section. Set Sort items by to <None> (This is to ensure the custom ordering allowed by Links lists is used). Uncheck the Limit the number of items to display checkbox.

In the Fields to display section enter Url [Custom Columns]; for the Link and remove the Title entry:

Choose any other display options you want (I expanded Apperance and chose Chrome Type: None). Press OK to close the dialog. Save the master page. In the navigation pane on the left, right-click on your master page and choose Set as Default Master Page:

Now when you refresh your site you should see the changes (Be sure to publish a major version and/or check in the file if required to ensure everyone can see it):

Adding Favicons to the Links

The above screenshot is pretty cool. Unfortunately, instead of using the display text, it just uses the link. It also doesn’t open the links in a new window. We’ll fix these issues and add a favicon using some simple XSL.

I found the basic XSL to fix the Links display on Marc D Anderson’s blog who apparently got it from this Microsoft forum thread. We’re going to straight up copy that XSL and tweak it just a little to add our favicons. Here’s our customized XSL:

<xsl:template name="LinkList" match="Row[@Style='LinkList']" mode="itemstyle">
	<xsl:variable name="SafeLinkUrl">
		<xsl:call-template name="OuterTemplate.GetSafeLink">
			<xsl:with-param name="UrlColumnName" select="@URL"/>
		</xsl:call-template>
	</xsl:variable>
	<xsl:variable name="DisplayTitle">
		<xsl:call-template name="OuterTemplate.GetTitle">
			<xsl:with-param name="Title" select="@URL"/>
			<xsl:with-param name="UrlColumnName" select="'LinkUrl'"/>
		</xsl:call-template>
	</xsl:variable>
	<xsl:variable name="TheLink">
		<xsl:value-of select="substring-before($DisplayTitle,',')"/>
	</xsl:variable>
	<div id="linkitem" class="item link-item" style="padding-left:10px;">
		<xsl:call-template name="OuterTemplate.CallPresenceStatusIconTemplate"/>
		<img src="http://www.google.com/s2/favicons?domain_url={$TheLink}" align="middle" style="padding-right:2px;" />
		<a href="{$TheLink}" target="_blank" title="This link opens in a new window">
			<xsl:value-of select="substring-after($DisplayTitle,',')"/>
		</a>
	</div>
</xsl:template>

The main changes I made were the additional padding added to the div in line 16 to get everything to line up with the QuickLaunch links and the img element in line 18.

The img element uses a special link from Google (found on the Coding Clues blog) concatenated with our link’s URL. This link allows us to dynamically retrieve the favicons without having to store them within SharePoint or maintain them as links get added or changed.

So where do we put the above XSL? In your site collection’s Style Library there is a folder called XSL Style Sheets. Open the ItemStyle.xsl file and scroll all the way to the bottom. Just before the final node, </xsl:stylesheet>, paste the above XSL. Since this is just a named template, this won’t affect anything else within your site collection. Upload the changed ItemStyle to the XSL Style Sheets folder and make sure to Publish a major version of the file so everyone can see it:

Now we need to tell our Links Content Query web part to use this item style. So, back in SharePoint Designer, right-click on your Content Query web part and choose Properties. Scroll down to ItemStyle and change it from Default to LinkList:

Save the master page and refresh your site and you should see something similar to this:

Isn’t that pretty!? Now everyone loves you!

Hiding the Recently Modified Section in SharePoint 2010

Applies To: SharePoint 2010, CSS

I recently added a Wiki Pages Library to a site for some end users and they really like it. However, they had a seemingly straight forward request to hide the Recently Modified section that was showing up above the Quick Launch:

This may come up as a requirement when using some of the default templates that automatically include a Site Pages library or if a user adds a new page and is prompted to create the Site Pages library automatically.

I assumed there was a setting somewhere either for the library or the site in order to turn off this “feature”. Nope. Somebody decided that this was not only a feature everyone would want, but it was so great they put it in the left actions content place holder (PlaceHolderLeftActions) of the master page – which puts it on top of the quick launch.

Some quick searching turned up “solutions” that suggested setting the contentplaceholder’s visible property to false within the master page. This works; however, it also hides anything that uses that contentplaceholder such as some of the Blog tools. This makes it a very poor candidate for a farm wide branding solution.

The other option is to use some CSS (cascading style sheets). If you’re pushing this as part of a branding solution, just add this to one of your style sheets:

.s4-recentchanges{
	display:none;
}

That’s it. Microsoft provided a very handy class just for this section and some quick use of the CSS Display property takes care of it.

So what if this is just a one off thing – You aren’t currently using any custom branding or just want it to affect one site? For a single site you can use SharePoint Designer 2010 to open the master page (v4.master – choose edit in advanced mode). Then somewhere on the page add the following:

<style>
.s4-recentchanges{
	display:none;
}
</style>

If you just want to apply it page by page, you can put the style directly in the HTML of the page. Since this is a Wiki page, choose to edit the page (Under the Page Ribbon assuming you have the rights). Click anywhere on the page and choose the HTML drop down and pick Edit HTML Source:

Somewhere on the page add the following:

<style>
.s4-recentchanges{
	display:none;
}
</style>
You can also do this in a content editor web part using the same Edit HTML Source option.

If you don’t hide this thing, I would suggest editing the master page to at least move that contentplaceholder below the quicklaunch so your navigation doesn’t get all wonky or at least displaced by a relatively unused feature.

Auto Publish and Approve Your Solution Files

Applies To: SharePoint 2010

By default, every file you deploy using a sandboxed solution is left checked out. This can lead to problems depending on the type of site you are deploying to and/or the permissions of your end users.

This post focuses on Branding solutions, but anytime you are deploying a sandboxed solution these techniques should help you. This is especially important for Master Pages since these often need an approved/published version in order to be visible to anyone but the site administrators.

I found an interesting approach by Waldek Mastykarz where he suggests using a “Stamp” (Feature ID property) on each file and using that to find and check in each file. This was very cool, but requires you to modify the Elements.xml entry for each file to ensure that property is added and he also never addressed Master Pages which can be a bit of a special case.

For my needs, I generally take a simpler approach of just deploying my resources to one root folder and creating sub folders as needed. This makes it easier to find stuff, but it also means I don’t need to track each file individually. Obviously if you are doing something a little more extensive then you may need to take a hybrid approach of using the featureid property and/or just tracking the various folders you are deploying too. But for a simple branding solution you really just need to:

  1. Apply your branding to each site
  2. Publish and Approve each resource file
  3. Publish and Approve each Master Page

All of this can be done in the FeatureActivating event with a simpler helper method:

    Public Overrides Sub FeatureActivated(ByVal properties As SPFeatureReceiverProperties)
        Dim siteCollection As SPSite = CType(properties.Feature.Parent, SPSite)
        If siteCollection IsNot Nothing Then
            Dim topSite As SPWeb = siteCollection.RootWeb

            'Calculate relative path to site from Web Application root
            Dim WebAppRelativePath As String = topSite.ServerRelativeUrl
            If Not WebAppRelativePath.EndsWith("/") Then WebAppRelativePath &= "/"

            'Enumerate through each site and apply branding
            For Each site As SPWeb In siteCollection.AllWebs
                If Not site.MasterUrl.EndsWith("minimal.master") Then
                    site.MasterUrl = WebAppRelativePath & "_catalogs/masterpage/BSmain.master"
                Else
                    site.MasterUrl = WebAppRelativePath & "_catalogs/masterpage/BSminimal.master"
                End If
                If Not site.CustomMasterUrl.EndsWith("minimal.master") Then
                    site.CustomMasterUrl = WebAppRelativePath & "_catalogs/masterpage/BSmain.master"
                Else
                    site.CustomMasterUrl = WebAppRelativePath & "_catalogs/masterpage/BSminimal.master"
                End If
                site.AlternateCssUrl = WebAppRelativePath & "Style%20Library/BSResources/BS.css"
                site.SiteLogoUrl = WebAppRelativePath & "Style%20Library/BSResources/Images/BSlogo.png"
                site.UIVersion = 4
                site.Update()
            Next

            'Publish and Approve each file
            Dim styleLibrary As SPList = topSite.Lists.TryGetList("Style Library")
            If styleLibrary IsNot Nothing Then
                Dim folders As SPListItemCollection = styleLibrary.Folders
                Dim item As SPListItem = DirectCast((From i In folders Where DirectCast(i, SPListItem).Url = "Style Library/BSResources" Select i).FirstOrDefault(), SPListItem)
                ApproveAndPublish(item.Folder, styleLibrary.EnableModeration)
            End If

            'Publish and Approve the Master Pages
            Dim mpGallery As SPList = siteCollection.GetCatalog(SPListTemplateType.MasterPageCatalog)
            If mpGallery IsNot Nothing Then
                Dim mpages As SPListItemCollection = mpGallery.GetItems(New SPQuery With {.Query = "<Where><Or><Eq><FieldRef Name='FileLeafRef' /><Value Type='Text'>RegalIC.master</Value></Eq><Eq><FieldRef Name='FileLeafRef' /><Value Type='Text'>RegalICminimal.master</Value></Eq></Or></Where>"})
                If mpages IsNot Nothing Then
                    For Each i As SPListItem In mpages
                        If Not i.File.CheckOutType = SPFile.SPCheckOutType.None Then
                            i.File.CheckIn("Feature Activation", SPCheckinType.MajorCheckIn)
                            If mpGallery.EnableModeration Then
                                i.File.Approve("Feature Activation")
                            End If
                        End If
                    Next
                End If
            End If

        End If
    End Sub

    Private Sub ApproveAndPublish(folder As SPFolder, Approve As Boolean)
        If folder Is Nothing Then Return
        For Each subfolder As SPFolder In folder.SubFolders
            ApproveAndPublish(subfolder, Approve)
        Next
        For Each file As SPFile In folder.Files
            If Not file.CheckOutType = SPFile.SPCheckOutType.None Then
                file.CheckIn("Feature Activation", SPCheckinType.MajorCheckIn)
                If Approve Then
                    file.Approve("Feature Activation")
                End If
            End If
        Next
    End Sub

1. Apply your branding to each site

After gathering basic information about where the feature is being deployed and getting the correct reference URLs, we begin looping through every site in the sitecollection and setting the master page, sitelogo, and CSS settings to use our custom branding beginning in line 11.

The only thing different than the approach described in the Microsoft article, Deploying Branding Solutions for SharePoint 2010 Sites Using Sandboxed Solutions is that I am checking if the current master page is the minimal.master and if so, using my BSminimal.master file instead. This allows me to have both master pages deployed correctly, but it also allows me to restore these settings more accurately in the deactivating event (See my previous post).

2. Publish and Approve each resource file

To keep things simple, I keep all of my resource files in a single root folder within the Style Library. This makes looping through each subfolder and resource very simple to ensure that each one gets checked in and/or approved as necessary.

Lines 29-34 get a reference to the resource folder within the Style Library and pass that information over to a helper method called ApproveAndPublish. This method takes an SPFolder reference and a boolean indicating if approval is necessary or not. For the initial call, the folder is our resource folder and the approval setting comes directly from the Style Library and is found in the EnableModeration property of the SPList object.

The ApproveAndPublish method (Lines 55-68) is a recursive function that loops through every subfolder and checks in every file found. If Approval is required, it also marks them as approved.

This means you don’t have to track each file (either through stamping or keeping a list). This really cuts down on all the plumbing that is often necessary when working on a SharePoint solution.

3. Publish and Approve each Master Page

Unfortunately, Master Pages aren’t usually deployed to a sub folder and so the above technique for approval and check in has to be tweaked slightly. Lines 37-50 take care of this. Basically, we get a reference to the Master Page Catalog and use some basic CAML to isolate our master pages and then loop through them to check them in and/or activate them if required.

That’s all that’s required. You now have your files successfully deployed and ready to be used. Be sure to check out my previous post Branding Solution Cleanup. In that post I describe how to remove all of your solution files when your solution gets deactivated.

Branding Solution Cleanup

Applies To: SharePoint 2010

I followed the Microsoft article, Deploying Branding Solutions for SharePoint 2010 Sites Using Sandboxed Solutions and I was able to quickly get the bones of a Branding project put together. Unfortunately, I found that when the solution was deactivated all the files I deployed remained exactly where they were.

I found various solutions for removing your files ranging from individual file lists to marking every file with your feature ID, but for a simple Branding project all you really need to do is:

  1. Remove usage of your Master Pages from every site referencing them
  2. Remove your files from the Style Library
  3. Remove your Master Page files from the Master Page Catalog

The first two can be done in the FeatureDeactivating event handler and the third can be done in the FeatureUninstalling event handler. For those that just want the code, here it is:

    Public Overrides Sub FeatureDeactivating(ByVal properties As SPFeatureReceiverProperties)
        Dim siteCollection As SPSite = CType(properties.Feature.Parent, SPSite)
        If siteCollection IsNot Nothing Then
            Dim topSite As SPWeb = siteCollection.RootWeb

            'Calculate relative path to site from Web Application root
            Dim WebAppRelativePath As String = topSite.ServerRelativeUrl
            If Not WebAppRelativePath.EndsWith("/") Then WebAppRelativePath &= "/"

            'Enumerate through each site and remove branding
            For Each site As SPWeb In siteCollection.AllWebs
                If Not site.MasterUrl.EndsWith("minimal.master") Then
                    site.MasterUrl = WebAppRelativePath & "_catalogs/masterpage/v4.master"
                Else
                    site.MasterUrl = WebAppRelativePath & "_catalogs/masterpage/minimal.master"
                End If
                If Not site.CustomMasterUrl.EndsWith("minimal.master") Then
                    site.CustomMasterUrl = WebAppRelativePath & "_catalogs/masterpage/v4.master"
                Else
                    site.CustomMasterUrl = WebAppRelativePath & "_catalogs/masterpage/minimal.master"
                End If
                site.AlternateCssUrl = String.Empty
                site.SiteLogoUrl = String.Empty
                site.Update()
            Next

            'Kill Style Library Folder
            Dim styleLibrary As SPList = topSite.Lists.TryGetList("Style Library")
            If styleLibrary IsNot Nothing Then
                Dim folders As SPListItemCollection = styleLibrary.Folders
                Dim item As SPListItem = DirectCast((From i In folders Where DirectCast(i, SPListItem).Url = "Style Library/BSResources" Select i).FirstOrDefault(), SPListItem)
                item.Delete()
            End If

        End If
    End Sub

    Public Overrides Sub FeatureUninstalling(ByVal properties As SPFeatureReceiverProperties)
        Dim siteCollection As SPSite = properties.UserCodeSite
        If siteCollection IsNot Nothing Then

            'Kill Master Pages
            Dim mpGallery As SPList = siteCollection.GetCatalog(SPListTemplateType.MasterPageCatalog)
            If mpGallery IsNot Nothing Then
                Dim mpages As SPListItemCollection = mpGallery.GetItems(New SPQuery With {.Query = "<Where><Or><Eq><FieldRef Name='FileLeafRef' /><Value Type='Text'>BSmain.master</Value></Eq><Eq><FieldRef Name='FileLeafRef' /><Value Type='Text'>BSminimal.master</Value></Eq></Or></Where>"})
                If mpages IsNot Nothing Then
                    For i As Integer = mpages.Count - 1 To 0 Step -1
                        mpages(i).Delete()
                    Next
                End If
            End If

        End If
    End Sub

Wow code! Alright Scriptkitties, copy away! Everyone else, here’s what we’re doing and why:

1. Remove usage of your Master Pages from every site referencing them

After gathering basic information about where the feature was deployed and figuring out the correct reference URLs, we begin looping through every site in the sitecollection and resetting the master page to the defaults beginning in line 11.

We are just undoing what was done in the FeatureActivating event. The only thing of note is that I hate when a Branding solution replaces every MasterPage with theirs and then just blindly restores v4.master. Mostly this is fine, but if one of your subsites is an Enterprise Search site or anything else using the minimal.master you’ve just wrecked it. Obviously if you know you aren’t using minimal.master then you can simplify this section. Also, I always name my minimal.master replacement in the form [Something]minimal.master to ensure this works out.

2. Remove your files from the Style Library

To keep things simple, I keep all of my resource files in a single root folder within the Style Library. Obviously I have subfolders to organize images, fonts, etc. but all of those are within my one folder. This makes finding stuff much easier, but more than that it makes removing the files super easy – Just delete that folder.

Lines 27-33 do just that. After getting a reference to the Style Library (Every sitecollection in SharePoint 2010 has one of these), grab the folder (just replace the “Style Library/BSResources” string in line 31 with your folder path) and delete.

3. Remove your Master Page files from the Master Page Catalog

There are lots of guides for deleting deployed master pages and I didn’t find any that worked. Basically every time I tried to delete a master page from within the FeatureDeactivating event I got an error about them still being used. I’m sure there’s a good reason for this (feel free to let me know in the comments), but it doesn’t really matter because as long as you followed step 1 above, it’ll work in the FeatureUninstalling event.

We simply grab a reference to the sitecollection’s Master Page Gallery and use some simple CAML to grab references to our Master Pages. Then we walk through them and delete them.

That’s it, you now have a self-cleaning solution and you are a responsible member of the SharePoint community.