Tuesday, September 8, 2009

Creating a Dynamically “Tabbifying” WebPart

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;

window.addEventListener('DOMContentLoaded', (event) => {
var curLoc = window.location.href.substr(window.location.href.lastIndexOf('#'));
if (curLoc === "#cookiePolicy") {
$([document.documentElement, document.body]).animate({
scrollTop: ($("#cookiePolicyId").offset().top)-100
});
$('.text-multiple-columns').removeClass("in-viewport--fade-in");
}
});