Creating custom tab Web Parts in SharePoint 2010

Summary:  Learn how to create and implement a custom tab Web Part in Microsoft SharePoint 2010 that is independent of the page layout.

Applies to: Business Connectivity Services | Open XML | SharePoint Designer 2010 | SharePoint Foundation 2010 | SharePoint Online | SharePoint Server 2010 | Visual Studio | Visual Studio 2010

Provided by:  Jinhui (Ivory) Feng and Saravana Kumar Ramachandran

Contents

  • Introduction to creating a custom tab Web Part

  • Designing the custom tab Web Part

  • Creating the visual Web Part SharePoint 2010 Project in Visual Studio

  • Adding the custom tab Web Part to a SharePoint 2010 Page

  • Conclusion

  • Additional resources

  • About the authors

Download the code

Introduction to creating a custom tab Web Part

SharePoint 2010 provides many out-of-the-box Web Parts, but none of those Web Parts provides a tabbed interface. Today’s web pages have lots of information; a tab Web Part would help organize that information in far less space.

This article describes how to design and implement a custom tab Web Part in Visual Studio 2010 that is independent of the page layout.

What are the challenges?

Although you might be tempted to define a tab UI on custom page layouts, that solution creates more problems than it solves. For some applications, the titles, the number of tabs, and the content format of a tab Web Part might change periodically, or differ by user. So if you define a tab UI on page layouts, you have too many layouts to maintain and the cost of training content authors to use Microsoft SharePoint Designer 2010 to modify and update page layouts.

The challenge is to design a custom tab Web Part that provides a tabbed UI and is as easy to use as any other out-of-the-box Web Parts that content authors can add to a page, configure, and display. The design should allow for multiple instances of the custom Web Part to appear on one page while some content information (Web Parts) appears under multiple tabs of the same or different tab Web Parts. The biggest challenge is that the code cannot add a Web Part zone within a custom Web Part, which is common sense. The question remains, how can you put Web Parts into tabs of the custom Web Part?

Designing the custom tab Web Part

This section discusses the design of the custom tab Web Part. The first step is to create a visual Web Part by using jQuery.

The following code shows repeater controls that render the tabs and tab contents.

<script type="text/javascript">
    $(function () {
        $("#tabs<%= this.ClientID%>").tabs();
    });
</script>


<div id="tabs<%= this.ClientID%>">

      <ul>
        <asp:Repeater runat="server" ID="TabRepeater">
            <ItemTemplate>
                <li>
                    <a href="#tabs<%# this.ClientID%>-<%# Container.ItemIndex + 1 %>"><%# Container.DataItem%></a>
                </li>
            </ItemTemplate>
        </asp:Repeater>
      </ul>

    <asp:Repeater runat="server" ID="TabContainerRepeater" onitemdatabound="TabContainerRepeater_ItemDataBound">
        <ItemTemplate>
              <div id="tabs<%# this.ClientID%>-<%# Container.ItemIndex + 1 %>" class="ui-tabs-hide">
                <asp:Panel runat="server" ID="TabContainer"></asp:Panel>
              </div>
        </ItemTemplate>
    </asp:Repeater>
</div>

The first TabRepeater repeater control shows the tabs with titles. The second TabContainerRepeater repeater control shows the tab contents. The TabContainer panel control is a placeholder for the tab content.

To configure the tabs, a string property, ConfigureTabs, is defined in the tab Web Part. It takes an XML format string, which has the information about the tabs and tab content Web Parts. The name of each tab shows on the UI. The title of each Web Part refers to a closed Web Part that is shown under a tab. Instead of using a string property, you can use a custom tool part.

<tabs>
  <tab name="Tab Title 1">
    <webPart title="WebPart1" />
    <webPart title="WebPart3" />
  </tab>
  <tab name="Tab Title 2">
    <webPart title="WebPart2" />
  </tab>
  <tab name="Tab Title 3">
    <webPart title="WebPart3" />
    <webPart title="WebPart2" />
  </tab>
</tabs>

There are three tabs defined in this ConfigureTabs string. The first tab shows as Tab Title 1 with two closed Web Parts (WebPart1 and WebPart3) in it. As you can see, the same closed Web Part can show in multiple tabs.

All the Web Parts shown under tabs have to be closed. The Close Web Part feature is an out-of-the-box feature of SharePoint 2010. See Figure 1 to see how to close a Web Part.

 

Figure 1. Close a Web Part

Close a Web Part

 

Closed Web Parts do not appear in the UI directly, but they stay in the Web Parts collection of the page so that code can access them. When you close a Web Part, it is not rendered on the page, which improves performance. Closing a Web Part moves the Web Part to the Closed Web Parts gallery, which preserves the configuration and customizations in the Web Part. You can return the closed Web Part (with all its configuration and customizations intact) to the page from the Closed Web Parts gallery. Using closed Web Parts gives content authors a native SharePoint 2010 experience authoring and configuring contents for tab Web Parts.

In the custom tab Web Part in the sample code, the closed Web Parts are "cloned" by exporting them and then importing them to display under tabs. The TabContainerRepeater repeater control’s ItemDataBound event does the work.

protected void TabContainerRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
    // Panel is the place holder to show Web Parts in a tab.
    Panel panel = (Panel)e.Item.FindControl("TabContainer");

    if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
    {
        if (panel != null)
        {
            using (SPLimitedWebPartManager wpManager = SPContext.Current.File.GetLimitedWebPartManager(PersonalizationScope.Shared))
            {
                try
                {
                    // Elevated privileges required for EXPORT and IMPORT. Else Users with normal read access will get errors.
                    SPSecurity.RunWithElevatedPrivileges(delegate()
                    {
                        // Retrieve the Web Part titles in the ConfigureTabs XML string for this tab.
                        var webPartTitles = from t in xDocument.Descendants("webPart")
                                            where (string)t.Parent.Attribute("name") == (string)e.Item.DataItem
                                            select (string)t.Attribute("title");

                        foreach (string wpTitle in webPartTitles)
                        {
                            foreach (System.Web.UI.WebControls.WebParts.WebPart webPart in wpManager.WebParts)
                            {
                                // Find the matched closed Web Part in WebParts collection
                                if (webPart.Title == wpTitle && webPart.IsClosed == true)
                                {
                                    string errorMessage;
                                    MemoryStream stream = new MemoryStream();
                                    XmlTextWriter writer = new XmlTextWriter(stream, System.Text.Encoding.UTF8);

                                    // Export the closed webPart to a memory stream.
                                    wpManager.ExportWebPart(webPart, writer);

                                    writer.Flush();
                                    stream.Position = 0;

                                    XmlTextReader reader = new XmlTextReader(stream);

                                    // Import the exported webpart.
                                    System.Web.UI.WebControls.WebParts.WebPart newWebPart = wpManager.ImportWebPart(reader, out errorMessage);

                                    reader.Close();
                                    writer.Close();

                                    // Show the imported webpart.
                                    panel.Controls.Add(newWebPart);
                                    break;
                                }
                            }
                        }
                    });
                }
                catch (Exception ex)
                {
                    // For debugging use only.
                    Label label = new Label();
                    label.Text = "Please check your XML configuration for error. " + Environment.NewLine + ex.Message;
                    panel.Controls.Add(label);
                }

            }
        }
    }
}

As the comments explain, the code gets the Web Part titles from the ConfigureTabs property. The code then loops through the WebParts collection of the WebPartManager to find the matching closed Web Parts. The code "clones" the Web Parts by exporting and importing them, and then adding the imported Web Parts to the panel control, which serves as a placeholder.

By using the export and import methods of a Web Part to clone a Web Part, you can add any predefined Web Parts that support export and import to a custom tab Web Part without a Web Part zone.

Creating the visual Web Part SharePoint 2010 Project in Visual Studio

In this section, you create a visual Web Part SharePoint 2010 project in Visual Studio 2010.

To set up a SharePoint 2010 project

  1. Run Visual Studio 2010 as an administrator.

  2. To create a SharePoint 2010 project, click File, point to New, and then click Project. In the Installed Templates tree, click Visual C#, click SharePoint, and then click Empty SharePoint Project. Name the project SharePoint2010CustomTabWebPart, and make it a farm solution.

  3. Right click the project, point to Add, and then click SharePoint Mapped Folder….

  4. In the Add SharePoint Mapped Folder dialog box, expand Template, expand LAYOUTS, select STYLES, and then click OK.

  5. Right click the newly mapped STYLES folder, point to Add, and then click New Folder. Name the folder jQuery-Tab.

  6. Right click the jQuery-Tab folder, point to Add, and then click Existing Item…. Add the following jQuery files.

    Note

    You can also find these files in the sample code package.

    This custom Web Part depends on the following jQuery files.

    • jquery-1.4.1.min.js

    • jquery-ui-1.8.custom.css

    • jquery-ui-1.8.custom.min.js

  7. Right click the project, point to Add, and then click New Item. In the Installed Templates tree, click Visual C#, click SharePoint, and then click Visual Web Part. Name the visual Web Part TabWebPart. Click OK.

  8. Right click the project, point to Add, and then click Class. Name the class Common. Click OK.

    Now the project appears as shown in Figure 2.

    Figure 2. Project setup

    Project setup

     

To add code to the project

  1. Open the user control file TabWebPartUserControl.ascx and append the following code.

    Note

    For more information about the following code, see Designing the custom tab Web Part.

    <link type="text/css" rel="stylesheet" href="{0}" />
    
    <script type="text/javascript">
        $(function () {
            $("#tabs<%= this.ClientID%>").tabs();
        });
    </script>
    
    
    <div id="tabs<%= this.ClientID%>">
    
          <ul>
            <asp:Repeater runat="server" ID="TabRepeater">
                <ItemTemplate>
                    <li>
                        <a href="#tabs<%# this.ClientID%>-<%# Container.ItemIndex + 1 %>"><%# Container.DataItem%></a>
                    </li>
                </ItemTemplate>
            </asp:Repeater>
          </ul>
    
        <asp:Repeater runat="server" ID="TabContainerRepeater" onitemdatabound="TabContainerRepeater_ItemDataBound">
            <ItemTemplate>
                  <div id="tabs<%# this.ClientID%>-<%# Container.ItemIndex + 1 %>" class="ui-tabs-hide">
                    <asp:Panel runat="server" ID="TabContainer"></asp:Panel>
                  </div>
            </ItemTemplate>
        </asp:Repeater>
    
    </div>
    
  2. Open the code behind file TabWebPartUserControl.ascx.cs for the user control. In the class TabWebPartUserControl, delete any code, if exists, and then add the following code.

    Note

    For more information about the following code, see Designing the custom tab Web Part.

            public TabWebPart tab { get; set; }
            public List<string> tabList = new List<string>();
            string tabsConfiguration = string.Empty;
            TextReader textReader = null;
            XDocument xDocument = null;
    
            protected void Page_Load(object sender, EventArgs e)
            {
                tabsConfiguration = this.tab.ConfigureTabs;
                if (string.IsNullOrEmpty(tabsConfiguration))
                {
                    tabsConfiguration = @"<tabs><tab name=""tab1""><webPart title=""HTML Form Web Part""></webPart></tab></tabs>";
                }
                try
                {
                    textReader = new StringReader(tabsConfiguration);
                    xDocument = XDocument.Load(textReader);
    
                    var tabNames = from t in xDocument.Descendants("tab")
                                   select (string)t.Attribute("name");
    
                    foreach (string tabName in tabNames)
                    {
                        tabList.Add(tabName);
                    }
                }
                catch
                {
                    tabList.Add("FirstTab");
                }
    
                // For tab titles
                this.TabRepeater.DataSource = tabList;
                this.TabRepeater.DataBind();
                // For tab contents
                this.TabContainerRepeater.DataSource = tabList;
                this.TabContainerRepeater.DataBind();
            }
    
            protected void TabContainerRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
            {
                // Panel is the place holder to show Web Parts in a tab.
                Panel panel = (Panel)e.Item.FindControl("TabContainer");
    
                if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
                {
                    if (panel != null)
                    {
                        using (SPLimitedWebPartManager wpManager = SPContext.Current.File.GetLimitedWebPartManager(PersonalizationScope.Shared))
                        {
                            try
                            {
                                // Elevated privileges required for EXPORT and IMPORT. Else Users with normal read access will get errors.
                                SPSecurity.RunWithElevatedPrivileges(delegate()
                                {
                                    // Retrieve the Web Part titles in the ConfigureTabs XML string for this tab.
                                    var webPartTitles = from t in xDocument.Descendants("webPart")
                                                        where (string)t.Parent.Attribute("name") == (string)e.Item.DataItem
                                                        select (string)t.Attribute("title");
    
                                    foreach (string wpTitle in webPartTitles)
                                    {
                                        foreach (System.Web.UI.WebControls.WebParts.WebPart webPart in wpManager.WebParts)
                                        {
                                            // Find the matched closed Web Part in WebParts collection
                                            if (webPart.Title == wpTitle && webPart.IsClosed == true)
                                            {
                                                string errorMessage;
                                                MemoryStream stream = new MemoryStream();
                                                XmlTextWriter writer = new XmlTextWriter(stream, System.Text.Encoding.UTF8);
    
                                                // Export the closed webPart to a memory stream.
                                                wpManager.ExportWebPart(webPart, writer);
    
                                                writer.Flush();
                                                stream.Position = 0;
    
                                                XmlTextReader reader = new XmlTextReader(stream);
    
                                                // Import the exported webpart.
                                                System.Web.UI.WebControls.WebParts.WebPart newWebPart = wpManager.ImportWebPart(reader, out errorMessage);
    
                                                reader.Close();
                                                writer.Close();
    
                                                // Show the imported webPart.
                                                panel.Controls.Add(newWebPart);
                                                break;
                                            }
                                        }
                                    }
                                });
                            }
                            catch (Exception ex)
                            {
                                // For debugging use only.
                                Label label = new Label();
                                label.Text = "Please check your XML configuration for error. " + Environment.NewLine + ex.Message;
                                panel.Controls.Add(label);
                            }
    
                        }
                    }
                }
            }
    
  3. Open the Web Part class file TabWebpart.cs and add the following attribute definition code above the class definition. These attributes specify the jQuery dependency files. Do it in this manner to ensure that each dependency file is loaded only one time on a page.

        [CSSDependecy("STYLES/jQuery-Tab/jquery-ui-1.8.custom.css", Priority = 0)]
        [ScriptDependecy("STYLES/jQuery-Tab/jquery-1.4.1.min.js", Priority = 2)]
        [ScriptDependecy("STYLES/jQuery-Tab/jquery-ui-1.8.custom.min.js", Priority = 20)]
    
  4. In the Web Part class file TabWebpart.cs, add the following code in the class definition. The code defines a custom property, ConfigureTabs, for users to configure the tab Web Part.

            private string tabsConfigurationString;
    
            [WebBrowsable(true),
            Category("Data"),
            Personalizable(PersonalizationScope.Shared),
            WebDisplayName("Configure Tabs"),
            Description(@"XML configuration for tabs. <tabs><tab name=""tab1""><webPart title=""Web Part""></webPart></tab><tab name=""tab2""><webPart title=""A Web Part""></webPart><webPart title=""B Web Part""></webPart></tab></tabs>")]
            public string ConfigureTabs { get { return tabsConfigurationString; } set { tabsConfigurationString = value; } }
    
  5. In the Web Part class file TabWebpart.cs, add the following code in the class constructor method TabWebPart(). The code provides a default value to the ConfigureTabs property via tabsConfigurationString.

                XElement xElement = new XElement("tabs",
                    new XElement("tab", new XAttribute("name", "tab1"),
                        new XElement("webPart", new XAttribute("title", "Web Part"))),
                    new XElement("tab", new XAttribute("name", "tab2"),
                        new XElement("webPart", new XAttribute("title", "A Web Part")),
                        new XElement("webPart", new XAttribute("title", "B Web Part")))
                    );
                tabsConfigurationString = xElement.ToString();
    
  6. In the Web Part class file TabWebpart.cs, delete the CreateChildControls() method if exists, and then add the following code in the class definition after the TabWebPart() method. The code adds the tab Web Part user control to the controls collection of the tab Web Part.

            protected override void CreateChildControls()
            {
                Control control = Page.LoadControl(_ascxPath);
                TabWebPartUserControl tabUserControl = control as TabWebPartUserControl;
                if (tabUserControl != null)
                {
                    tabUserControl.tab = this;
                }
    
                Controls.Add(control);
            }
    
  7. In the Web Part class file TabWebpart.cs, add the following code in the class definition after the CreateChildControls() method. The code reads the attributes, and adds only one link to a page for each dependency file, according to priority.

            protected override void OnInit(EventArgs e)
            {
                base.OnInit(e);
    
                IEnumerable<CSSDependecyAttribute> cssDependencies = this.GetType().GetCustomAttributes(typeof(CSSDependecyAttribute), true).OfType<CSSDependecyAttribute>().OrderBy(attr => attr.Priority);
                foreach (CSSDependecyAttribute attribute in cssDependencies)
                    if (Page.Items[attribute.Path] == null && Page.Header != null)
                    {
                        Page.Items[attribute.Path] = new Object();
                        Page.Header.Controls.Add(new Literal { Text = string.Format("<link type=\"text/css\" rel=\"stylesheet\" href=\"{0}\" />", Path.Combine(resourceBasePath, attribute.Path)) });
                    }
                IEnumerable<ScriptDependecyAttribute> scriptDependencies = this.GetType().GetCustomAttributes(typeof(ScriptDependecyAttribute), true).OfType<ScriptDependecyAttribute>().OrderBy(attr => attr.Priority);
                foreach (ScriptDependecyAttribute attribute in scriptDependencies)
                    if (Page.Items[attribute.Path] == null && Page.Header != null)
                    {
                        Page.Items[attribute.Path] = new Object();
                        Page.Header.Controls.Add(new Literal { Text = string.Format("<script src=\"{0}\" type=\"text/javascript\"></script>", Path.Combine(resourceBasePath, attribute.Path)) });
                    }
            }
    
            #endregion
    
  8. Open the TabbedWebPart.webpart file, change the value of the Title property to TabWebPart, and the value of Description property to My Tab WebPart.

       <property name="Title" type="string">TabWebPart</property>
       <property name="Description" type="string"> My Tab WebPart</property>
    
  9. Click Save All on the toolbar.

To build and deploy the project

  1. Click the project, in the Properties window, set the Site URL property to your SharePoint 2010 site URL.

  2. Right click the project, and then click Deploy to build the project and deploy the solution.

Adding the custom tab Web Part to a SharePoint 2010 Page

In this section, you create a SharePoint 2010 page and add three Web Parts to it to display in a tab Web Part. Then, you add a tab Web Part and configure it. Each tab in the tab Web Part shows one or more Web Parts in it.

To prepare a SharePoint 2010 page

  1. Create a SharePoint 2010 page.

  2. Add an out-of-the-box Web Part. Edit the Web Part and change the Title to WebPart1. Configure the Web Part to present some information.

  3. Add an out-of-the-box Web Part. Edit the Web Part and change the Title to WebPart2. Configure the Web Part to present some information.

  4. Add an out-of-the-box Web Part. Edit the Web Part and change the Title to WebPart3. Configure the Web Part to present some information.

  5. Now that the page displays all three Web Parts, it is time to put a custom tab Web Part on the page to re-organize the out-of-the-box Web Parts under tabs.

To add and configure a custom tab Web Part

  1. Add the custom tab Web Part to the page that you just created.

  2. Edit the tab Web Part, set the ConfigureTabs property to the following XML string value.

    <tabs>
      <tab name="Tab Title 1">
        <webPart title="WebPart1" />
        <webPart title="WebPart3" />
      </tab>
      <tab name="Tab Title 2">
        <webPart title="WebPart2" />
      </tab>
      <tab name="Tab Title 3">
        <webPart title="WebPart3" />
        <webPart title="WebPart2" />
      </tab>
    </tabs>
    
  3. Close the Web Parts WebPart1, WebPart2, and WebPart3.

  4. Save the page.

    The page shows the tab Web Part with the contents that you configured.

Conclusion

By using the Export and Import methods to clone Web Parts, you can add predefined closed Web Parts that support Export and Import features to custom tab Web Parts without Web Part zones. This custom tab Web Part is very flexible, is independent of the page layout, and empowers content authors to better organize information and to save space on a page.

Additional resources

For more information, see the following:

About the authors

Jinhui (Ivory) Feng is a senior software development engineer at Microsoft who works on internal human resources applications.

Saravana Kumar Ramachandran is a Technology Architect in Infosys with 10 years of experience developing applications and portals with Microsoft technologies.