I recently was given the task of making SharePoint look less like SharePoint. This post will not discuss how to properly create master pages, layouts, and styles. There are plenty of posts already written that do a quality job explaining how to best create the desired aesthetic. This post addresses a specific design element that was requested, namely a tabbed interface to wrap various web parts.
After going down many different paths, the solution I ended up with was creating a custom WebPart that dynamically generates tabs for subsequent other WebParts in the same zone and then proceeds to skin these parts such that they are visually drawn into the tab control. JavaScript is also embedded within the WebPart to handle the actual transitions between the tabs.
The Code
Setting Things Up
A couple of items to note before we get into the meat of this. First, we will need to keep track of what web parts we are going to be “tabbifying” so we create a list to tuck the parts away in. We also want to make sure we have a nice ID set for styling (more on this later) and that we clear out any chrome set for our tab web part.
Now, since we will be manipulating how other web parts look and feel within our tab control, we need to know whether or not we are presenting or editing the page and act accordingly.
1: using System;
2: using System.Runtime.InteropServices;
3: using System.Web.UI;
4: using System.Web.UI.WebControls;
5: using Parts = System.Web.UI.WebControls.WebParts;
6: using System.Xml.Serialization;
7: using System.Collections.Generic;
8:
9: using Microsoft.SharePoint;
10: using Microsoft.SharePoint.WebControls;
11: using Microsoft.SharePoint.WebPartPages;
12:
13: namespace TabWebPart
14: {
15: [Guid("c00ca5be-90a2-4afe-b0d4-97df17db88cc")]
16: public class TabControl : System.Web.UI.WebControls.WebParts.WebPart
17: {
18: private List<Parts.WebPart> _WebParts = new List<Parts.WebPart>();
19:
20: public TabControl()
21: {
22: this.ID = "TabControl";
23: this.ChromeType = System.Web.UI.WebControls.WebParts.PartChromeType.None;
24: }
25:
26: protected override void CreateChildControls()
27: {
28: try
29: {
30: if (SPContext.Current.FormContext.FormMode == SPControlMode.Display)
31: RenderDesignMode();
32: else if (SPContext.Current.FormContext.FormMode == SPControlMode.Edit)
33: RenderEditMode();
34: }
35: catch(Exception ex)
36: {
37: System.Diagnostics.EventLog.WriteEntry("TabControl", ex.ToString(), System.Diagnostics.EventLogEntryType.Error);
38: }
39: }
Adding the Tabs
For simplicity’s sake, we will assume that any web part above our tab part is outside of our concern. And let’s also assume we want to include all web parts below ours through the end of the zone our part is contained in. Now, obviously, if there are no other web parts in our zone that meet this criteria, there’s no point of continuing on.
Assuming we are though, for each web part that we do want to wrap up, we need a tab for them. For this case, the tabs were designed to span the width of the zone and are specified as such, but this is not an essential aspect. Our tabs are also generated in the same order that they appear in the zone. That way if you want to reorder them, all you need to do is shift the order of the WebParts within the zone. Also, in order for our tabs to work, we need to be able to easily program against them. So, we class them.
1: private void RenderDesignMode()
2: {
3: foreach (Parts.WebPart part in this.Zone.WebParts)
4: {
5: if (part.ZoneIndex > this.ZoneIndex)
6: {
7: part.ChromeType = System.Web.UI.WebControls.WebParts.PartChromeType.None;
8: this._WebParts.Add(part);
9: }
10: }
11:
12: if (this._WebParts.Count <= 1)
13: return;
14:
15: RenderTabs();
16: RenderDesignJQuery();
17: }
18:
19: private void RenderTabs()
20: {
21: Panel Container = new Panel();
22: Container.CssClass = "TabControl";
23:
24: foreach (Parts.WebPart part in this._WebParts)
25: {
26: Container.Controls.Add(Tab((int)Math.Floor((decimal)(100 / _WebParts.Count)), part.Title));
27: }
28:
29: (Container.Controls[0] as Panel).CssClass += " SelectedTab";
30: this.Controls.Add(Container);
31: }
32:
33: private Control Tab(int TabWidth, string TabLabel)
34: {
35: Panel div = new Panel();
36: div.CssClass = "Tabs";
37: div.Attributes.Add("style", "width: " + TabWidth + "%");
38: Literal lit = new Literal();
39: lit.Text = TabLabel;
40: div.Controls.Add(lit);
41: return div;
42: }
The JQuery
A quick side note before jumping into this section. JQuery is amazing. It really is.
Moving on, we use JQuery to identify the various parts, handle the functionality, and assign styles accordingly. In my case, I had a column I was specifically concerned with that had an ID of “MiddleColumn” and it is reflected in this code, but it may be omitted.
When the end user clicks on a tab, the script hides all of the WebParts (the ones below the tab control) and then based on the index of the tab selected, shows that particular part. The script then adjusts the classes for the tabs to reflect the change visually.
If the page is being edited, the WebPart renders a different script that goes through and ensures the subsequent web parts render without any changes from our control.
1: private void RenderDesignJQuery()
2: {
3: Literal lit = new Literal();
4: lit.Text = @"
5: <script language=""javascript"" type=""text/javascript"">
6: $(document).ready(function(){
7: var Tabs = $(""#MiddleColumn .Tabs"");
8: var Zone = $(""#MiddleColumn td[id*=TabControl]"").eq(0).parent().parent().parent();
9: var TabWebPart = Zone.find(""td[id*=TabControl]"").eq(0).parent();
10: var WebParts = TabWebPart.nextAll();
11:
12: Tabs.click(function(){
13: WebParts.hide();
14: WebParts.eq(Tabs.index(this)).show();
15: Tabs.removeClass(""SelectedTab"");
16: $(this).addClass(""SelectedTab"")
17: });
18:
19: WebParts.hide();
20: WebParts.eq(0).show();
21: WebParts.addClass(""TabContent"");
22: TabWebPart.find("".ms-PartSpacingVertical"").css(""margin-top"",""0px"");
23: Zone.css(""margin-bottom"",""14px"");
24: });
25: </script>
26: ";
27: this.Controls.Add(lit);
28: }
29:
30: private void RenderEditMode()
31: {
32: RenderEditingJQuery();
33: }
34:
35: private void RenderEditingJQuery()
36: {
37: Literal lit = new Literal();
38: lit.Text = @"
39: <script language=""javascript"" type=""text/javascript"">
40: $(document).ready(function(){
41: var Zone = $(""#MiddleColumn td[id*=TabControl]"").eq(0).parent().parent().parent();
42: var TabWebPart = Zone.find(""td[id*=TabControl]"").eq(0).parent();
43: var WebParts = TabWebPart.nextAll();
44:
45: WebParts.removeClass(""TabContent"");
46: WebParts.removeClass(""SelectedTabContent"");
47: });
48: </script>
49: ";
50: this.Controls.Add(lit);
51: }
Making It Pretty
The styles are purely subjective, but this is what I did. The WebParts have a border along the sides and bottom and the tabs close the top off except for the selected tab which seems to flow into the content area.
1: .TabControl
2: {
3: width: 100%;
4: margin: 0px;
5: padding: 0px;
6: }
7: .Tabs
8: {
9: text-align: center;
10: color: #6f87a4;
11: background: url('../images/TabBackground.png') repeat-x top left;
12: height: 19px;
13: border: solid 1px;
14: border-color: #b0bec7;
15: border-bottom: solid 1px #cccccc;
16: float: left;
17: cursor: pointer;
18: }
19: .SelectedTab
20: {
21: color: #00244d;
22: background: url('../images/TabSelectedBackground.png') repeat-x top left;
23: border-color: #cccccc;
24: border-bottom: none 0px;
25: cursor: default;
26: }
27: .TabContent > td
28: {
29: border: solid 1px #cccccc;
30: padding: 6px;
31: border-top: none 0px;
32: }
33: #TabContentSet > div
34: {
35: border: solid 1px #cccccc;
36: padding: 6px;
37: border-top: none 0px;
38: }
39: .TabContent .ms-PartSpacingVertical
40: {
41: margin-top: 0px;
42: }
43: .TabContent .ms-WPHeader
44: {
45: display: none;
46: }
47: .TabContent .ms-WPBorder, .TabContent .ms-WPBorderBorderOnly
48: {
49: border: none 0px;
50: }
51: .TabContentSet
52: {
53: clear: both;
54: }
And The End Result
And there you have it. Deploy the web part, add it to a zone, add other web parts, and tada! Tabs!
