SharePoint 2010: Custom Provider – cross site collection navigation

Recently I get a request to have a consistent top navigation across site collection, in which I need to retrieve the navigation items from a site collection to be used in different site collections (in the same web application).  As it is known that SharePoint is using Provider framework to build its navigation, and for Publishing site it is using PortalSiteMapProvider that provides additional caching mechanism. The issue with the provider is that it cannot live cross site collection boundary.

In this post, I would like to share how to build the custom navigation provider utilizing the PortalSiteMapProvider from different site collection (in this post I call it source site collection), I provide the source code at the end of this post. Let’s get started:

  1. We need to create a custom HttpHandler (ashx) that hosts the source site collection’s PortalSiteMapProvider. The ashx is deployed to layouts directory. This excellent post shows how to build the custom HttpHandler, in fact the source code is based on the post with some modification. Basically the handler will build JSON object tree based on the navigation structure of the source site collection.
  2. Then create a custom SiteMap provider that calls the ashx created at step 1. The provider will call the handler using WebRequest with the user credential, and deserialize the received JSON into a object structure used by the provider.
  3. Replace the implementation of OOB site map data source with our own. In v4.master the navigation data sources (SiteMapDataSource) are enclosed in delegate controls, below are the delegate controls in the master page
    Navigation Location Delegate Ctl ID
    Top navigation TopNavigationDataSource
    Quick launch QuickLaunchDataSource

    With this knowledge, we can replace the OOB data source with my own implementation. Below is the definition in my elements.xml to replace the top navigation data source with our own implementation.

    <Control Sequence="30"
       Id="TopNavigationDataSource"
       ControlClass="System.Web.UI.WebControls.SiteMapDataSource"
       ControlAssembly="System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
       <Property Name="ID">topSiteMap</Property>
       <Property Name="SiteMapProvider">SymprgNavigationProvider</Property>
       <Property Name="EnableViewState">false</Property>
       <Property Name="ShowStartingNode">true</Property>
     </Control>
    

    You can see that I replaced the top navigation data source implementation to use my custom provider: SymprgNavigationProvider. And also make sure the control sequence number is less than the OOB publishing control defined in Navigation feature (14 hive/TEMPLATE/FEATURES/Navigation /NavigationSiteSettings.xml- the OOB Publishing sequence number is 50).  Put this elements.xml to a web scoped feature. By activating the feature we can replace the top navigation.

  4. The last step is adding the custom navigation handler to the web.config inside siteMap > providers element as shown below. The custom provider (Symprg.NavProvider.NavigationProvider) has two custom attributes:
    • sourceUrl: the source site collection url that provides the navigation items
    • depth: the depth of the navigation, by default is 1 if not specified.
      <siteMap defaultProvider="CurrentNavigation" enabled="true">
        <providers>
           ---------------------
           <add name="SymprgNavigationProvider" type="Symprg.NavProvider.NavigationProvider, Symprg.NavProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d7699941b48f79bc" sourceUrl="http://aSiteCollection" depth="2"/>
        </providers>
      </siteMap>
    

You can find the source code here.

Ribbon: insert any Web Part using javascript

In this article I will show how to add a web part using SharePoint 2010′s javascript. As we know we can add web parts on a page using command in Contextual Ribbon Tab called Insert Web Part. Behind the scene there is a server control called WebPartAdder, this control registers a javascript file called wpadder.js containing functions for selecting  web part from web part gallery and inserting it. Tapping into these functions we can add any web parts purely using javascript.

As an example I will add a custom control in Insert Web Part Contextual Ribbon as shown below. The control would insert Contet Editor WebPart into a web part zone on a page.

Here is the ribbon definition for the Insert Content Editor command.

<CustomAction Id="AddCustomWebPart"
                    RegistrationId="100" RegistrationType="List"
                    Location="CommandUI.Ribbon">
    <CommandUIExtension>
      <CommandUIDefinitions>
        <CommandUIDefinition Location="Ribbon.WebPartInsert.WebParts.Controls._children">
          <Button
            Id="Ribbon.DL.WebPartInsert.WebParts.WebPart"
            Sequence="40"
            Command="insertMyWebPart"
            LabelText="Insert Content Editor"
            Image16by16="/_layouts/images/edit.gif"
            Image32by32="/_layouts/images/placeholder32x32.png"
            TemplateAlias="o1"
            ToolTipTitle="Insert my web part"
            ToolTipDescription="$Resources:core,cui_STT_ButInsertWebPart;"/>
        </CommandUIDefinition>
      </CommandUIDefinitions>
      <CommandUIHandlers>
        <CommandUIHandler Command="insertMyWebPart" CommandAction="javascript: addMyWebPart('Media and Content','Content Editor');"/>
      </CommandUIHandlers>
    </CommandUIExtension>
  </CustomAction>

In this example our CustomAction is applicable for general/custom list (RegistrationType=List and RegistrationId=100) . This msdn article lists all OOB ListTemplates Id. The location of the custom command is in  Ribbon.WebPartInsert.WebParts.Controls (Contextual Tab Insert Web Part Group). The command handler for the web part is our javascript function call addMyWebPart. Below is the the implementation of addMyWebPart.
function addMyWebPart(wpCategory, wpTitle) {
   var webPartAdder = SP.Ribbon.WebPartComponent.getWebPartAdder();
   var webPart = findWebPart(webPartAdder, wpCategory, wpTitle);
   if (webPart) {
     var selectedZone = $get('_wzSelected').value;
     if (!selectedZone) selectedZone = "Main";
       webPartAdder.addItemToPageByItemIdAndZoneId(webPart.id, selectedZone);                           
   }
   else
       alert('web part not found!');
}

function findWebPart(webPartAdder, category, wpTitle){
  if(webPartAdder){
    var wpCategory = findByTitle(webPartAdder._cats, category); 
    if(wpCategory)
      return findByTitle(wpCategory.items, wpTitle);
  }
}

function findByTitle(list, title){
   for (i=0; i < list.length; i++){
     var item = list[i];
     if(item.title == title)
       return item;
   }
}

The function’s arguments are the  web part’s Category and web part’s Title of the web part defined in web part gallery (_catalogs/wp).  First thing it would try to get the WPAdder instance. WPAdder class are defined in wpadder.js. It has several properties and method such as :

  • addItemToPageByItemIdAndZoneId method: this would add the web part into a page given the web part item id and zone id
  • _cats: containing the list of web part categories
  • Each web part category has items properties containing the list of web parts in the category
  • Both category and item have title property

After getting the WPAdder instance it would try to find the web part by iterating the web part category list and matching the title of the category. If the category found it would iterate the items in the category and match the title of the item. Having found the item, it would try to get the selected web part zone by checking the hidden variable called _wzSelected. Then after that called the method to add the webpart to the page.

We can provision the javascript above using another CustomAction with Location attribute sets to ScriptLinks as shown below.

 <CustomAction Id="AddWpScript" ScriptSrc="RibbonTest/RibbonScript.js" Location="ScriptLink" Sequence="100"></CustomAction>

This picture shows the project structure in visual studio. Put the 2 CustomActions (ribbon definition and javascript def) into the elements.xml and the javascript into the RibbonScript.js. Off-course, it needs a feature that contains the WpAdderElement to provision it.

To see how it works, go to any custom list and click Modify Form Web Part from the ribbon or add query string DisplayMode=Design to any view for example to see the design mode of the default view we can do  AllItems.aspx?DisplayMode=Design. This would open the page on Design mode  and then we can insert the Content Editor Web Part using our custom command in the Insert Web part Contextual Tab.

With this mechanism we can add any web part to page purely using javascript.

Microsoft SharePoint 2010,SharePoint Designer, SharePoint Workflow, Web Part

CustomAction: Open UrlAction in Modal Dialog

As we know that CustomAction can have UrlAction that points to an application page. In SharePoint 2007, whenever the CustomAction is clicked it simply navigates to the application page defined in UrlAction.

As I am migrating to SharePoint 2010 I want to have some of my CustomAction especially the ECB (Edit Control Block) ones to open in modal dialog. SharePoint 2010 provides several javascript methods to open an url in a modal dialog. One of them is OpenPopUpPageWithTitle method defined in core.js. Below is the signature of the method:

OpenPopUpPageWithTitle(url, callback, width, height,title)

The method parameters are self explained. Internally the OpenPopUpPageWithTitle method is calling SP.UI.ModalDialog.showModalDialog(options) where the options are the parameters passed to the method.  To see how it works below is my ECB definition.

<CustomAction Id="Custom.ProvisionGroup"
                Location="EditControlBlock"
                Title="My Custom Action"
                RegistrationType="List"
                RegistrationId="20002"
                Rights="ManagePermissions" 
               ImageUrl="/_layouts/images/editicon.gif">
 <UrlAction Url="javascript:OpenPopUpPageWithTitle('{SiteUrl}/_layouts/myCustom/ProvisionGroup.aspx?List={ListId}&ID={ItemId}', RefreshOnDialogClose, 600, 400,'My Custom Action')"/>
  </CustomAction>

Basically it is an ECB for a custom list definition, on the UrlAction I use the OpenPopUpPageWithTitle method where the url is a custom application page, the callback is a SharePoint method called RefreshOnDialogClose (this method is defined in core.js, it will refresh the underlying page when the dialog close). Basically the callback can be anything such as display notifaction(SP.UI.Notify). The other parameters specified the width, height and title of the dialog box. Below is the screen shot of the my custom ECB.

CustomAction EditControlBlock

CustomAction ECB

CustomAction Modal Dialog

CustomAction Modal Dialog

In the custom application page we need to close the dialog box whenever it has finished executing. Below is the code to close the dialog box. It needs to be called at the end of the execution.

private void ClosePopUp()
{
            this.Context.Response.Write("<script type='text/javascript'>window.frameElement.commitPopup();</script>");
            this.Context.Response.Flush();
            this.Context.Response.End();
}

Off-course, we can achieve the same result by using SP.UI.ModalDialog directly as shown in this post as OpenPopUpPageWithTitle method internally is using SP.UI.ModalDialog

Microsoft SharePoint 2010,SharePoint Designer, SharePoint Workflow, Web Part