iGoogle UI for SharePoint 2010 – Part Four: Control Adapters

Series Content

  1.     Part One – Overview, Concept,  HTML Structure & jQuery Basics
  2.     Part Two – Dragging, Dropping,  Sorting and Collapsing
  3.     Part Three – Saving WebPart states using Cookies
  4.     Part Four – Control Adapters – Current Article
  5.     Part Five – SharePoint 2010 Integration
  6.     Part Six – Bringing it all together
  7.     Bonus – Saving WebPart States using the Client Object Model

Overview

In Part Four we will take the take a look at how we will create our widgets in SharePoint 2010.  As the title of this post mentions we are going to use what is known as a Control Adapter.  This Post will outline what they are, how they work, and how we are going to use them in this series.  There will also be a code snippet to explain how we can use it.

Control Adapters

Rather than try to explain these myself I thought it would be easier to grab a snippet from a Microsoft Article which i think does a great job of explaining what and how they work from an architectural level and in more detail than I could probably achieve :).

At their core, control adapters are simply a way of providing different renderings for controls without actually modifying the controls themselves. Because control adapters are designed to provide alternate renderings for different clients, you specify control adapter mappings in a .browser file, which is where associations between User Agent strings and browser capabilities are defined. The control adapter class itself must inherit from the System.Web.UI.Adapters.ControlAdapter, which is an abstract base class that looks much like the Control base class, with events for Init, Load, PreRender, and Unload, as well as a virtual Render method.

The next step to use a control adapter is to derivatively bind your adapter to a specific control.  To do this you use a Browser Definition File Schema which is found in the App_Browsers folder of the IIS WebSite you are using.

How are we going to use Control Adapters?

Now we know what a control adapter is, what do we need them for?  Well, if we are going to have our WebPart rendering like we have built in the previous three post then we will need to use a Control Adapter to do the hard work for us.  WebParts can be placed onto a SharePoint 2010 Page in many ways.  They can be either added to content inline using the rich content editor, they can be added directly into a page layout or masterpage, or (the most common way) is that they can be placed into a WebPart zone.  It is this final method that we are going to use to modify the rendering of our WebParts.

A basic WebPart Control Adapter Code looks like this:

using System.Web.UI;
using System.Web.UI.WebControls.WebParts;

namespace LifeInSharePoint.iGoogle.Common.ControlAdapters
{
    public class WebPartRenderControlAdapter : System.Web.UI.Adapters.ControlAdapter
    {
        protected override void Render(HtmlTextWriter writer)
        {
            WebPartZone wpz =
                Control as WebPartZone;
            if (wpz != null)
            {
                // Render the WebPartZone
                writer.Indent++;
                // Render the web parts
                if (wpz.WebParts.Count > 0)
                {
                    WebPartCollection wpColl = new WebPartCollection(wpz.WebParts);
                    foreach (WebPart wp in wpColl)
                    {
                        wp.RenderControl(writer);
                    }
                }
                writer.Indent--;
                writer.WriteLine();
            }
        }
    }
}

As you can see from the code above we have a class which inherits from System.Web.UI.Adapters.ControlAdapters.  We first get a reference to the current WebPartZone on the Adapter.  If this is not null then we can start to override the WebPart rendering.  We then check how many WebParts exist in the current WebPartZone that we are in and if there are some then we create a new WebPartCollection object with all the WebParts in the current zone.

WebPartCollection wpColl = new WebPartCollection(wpz.WebParts);

We can then loop through each WebPart in the collection and render the WebPart control.   This alone will remove all the tables for each WebPart rendered in a WebPartZone.  The final step to get this basic Control Adapter working is to update the compact.browser file stored (in my case) in the inetpub > webapp > App_Browsers > compact.browser file.

We need to add a single line into the <controlAdapters> node to register our new custom adapter.

  <browser refID="default">
    <controlAdapters>
		<adapter controlType="System.Web.UI.WebControls.WebParts.WebPartZone" adapterType="LifeInSharePoint.iGoogle.Common.ControlAdapters.WebPartRenderControlAdapter, LifeInSharePoint.iGoogle, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4077a3c2ee13ed4a" />
    </controlAdapters>
  </browser>

Save this file and ensure that the dll is in the GAC and then the control adapter should work.  One thing that is important to know about Control Adapters is that when they are in use they will by default process EVERY WebPart on the site.  For our implementation however we want to be able to choose which WebParts are rendered as our widgets.  To do this we will place some logic into our control adapter which will check the title of the WebPartZone to ensure it contains the text “iGoogle” and only process WebParts that are contained within those specific zones.  Another piece of logic that we need to place into our zones is that we only want our rendering to process WebParts when the page is in the Display mode and not in Edit mode.  The code below shows the updated adapter with the new pieces of logic included.

using System.Web.UI;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint.WebPartPages;
using WebPart = System.Web.UI.WebControls.WebParts.WebPart;
using WebPartZone = System.Web.UI.WebControls.WebParts.WebPartZone;

namespace LifeInSharePoint.iGoogle.Common.ControlAdapters
{
    public class WebPartRenderControlAdapter : System.Web.UI.Adapters.ControlAdapter
    {
        protected override void Render(HtmlTextWriter writer)
        {
            WebPartZone wpz =
                Control as WebPartZone;
            if (wpz != null)
            {
                SPWebPartManager swpm = (SPWebPartManager)SPWebPartManager.GetCurrentWebPartManager(wpz.Page);
                bool inDisplayMode = !swpm.GetDisplayMode().AllowPageDesign;
                if (inDisplayMode && wpz.DisplayTitle.Contains("iGoogle"))
                {
                    // Render the WebPartZone
                    writer.Indent++;
                    // Render the web parts
                    if (wpz.WebParts.Count > 0)
                    {
                        WebPartCollection wpColl = new WebPartCollection(wpz.WebParts);
                        foreach (WebPart wp in wpColl)
                        {
                            wp.RenderControl(writer);
                        }
                    }
                    writer.Indent--;
                    writer.WriteLine();
                }
                else
                {
                    // If we are not editing the page --> render the web part as usual.
                    base.Render(writer);
                }
            }
        }
    }
}

As you can now see we have first added a line to get a reference to the current WebPartManager on the page which will enable use to get the state of the page and check if we are in display or edit mode.

    SPWebPartManager swpm = (SPWebPartManager)SPWebPartManager.GetCurrentWebPartManager(wpz.Page);

We are then able to set a boolean value to the state of the page.

    bool inDisplayMode = !swpm.GetDisplayMode().AllowPageDesign;

The final step is to wrap a new if statement around the render code which will control when the table removal is processed.

                if (inDisplayMode && wpz.DisplayTitle.Contains("iGoogle"))
                {
                    //Old code to go here
                }
                else
                {
                    // If we are not editing the page --> render the web part as usual.
                    base.Render(writer);
                }

When this code is run only WebPartZones with the iGoogle text value in the title will be rendered.

Adding the Widget Code Wrapper

Now that we have the basics sorted for our Control Adapter we now need to wrap our widget code around the render control and this can be done like it would be done in a normal WebPart.  We need to add the following code and replace it within the foreach loop around for each WebPart.

                        writer.WriteLine();
                        writer.Write("<div id='" + wp.ID + "' class='widget'>");
                            writer.Write("<div class='widget-head'>");
                                writer.Write("<a class='collapse'>collapse</a>");
                                writer.Write("<h3>" + wp.Title + "</h3>");
                                writer.Write("<a class='remove'>remove</a>");
                                writer.Write("<a class='edit'>edit</a>");
                            writer.Write("</div>");
                            writer.Write("<div class='widget-edit'>");
                            writer.Write("<input type='text' type='text' class='iColorPicker' id='color" + wp.ID + "' />");
                                writer.Write("<div class='clearfix'></div>");
                            writer.Write("</div>");
                            writer.Write("<div class='widget-content'>");
                                wp.RenderControl(writer);
                            writer.Write("</div>");
                        writer.Write("</div>");

Those who have been following the previous three posts will recognize the HTML from above.  I have use the writer object to inject the HTML and have also ensured that the ID of my widget wrapper div is generated from the current WebPart ID – (wp.ID), and the title of the WebPart is injected into the <H3> tag.

Summary

That wraps up part four of the iGoogle series.  The next post will be to integrate the code above into a SharePoint 2010 solution and include some of the extra pieces such as CSS to enable the this solution to come to life.  The final code for this Control Adapter is shown below.  Thanks for reading and all the positive feedback is greatly appreciated.

using System.Web.UI;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint.WebPartPages;
using WebPart = System.Web.UI.WebControls.WebParts.WebPart;
using WebPartZone = System.Web.UI.WebControls.WebParts.WebPartZone;

namespace LifeInSharePoint.iGoogle.Common.ControlAdapters
{
    public class WebPartRenderControlAdapter : System.Web.UI.Adapters.ControlAdapter
    {
        protected override void Render(HtmlTextWriter writer)
        {
            WebPartZone wpz =
                Control as WebPartZone;
            if (wpz != null)
            {
                SPWebPartManager swpm = (SPWebPartManager)SPWebPartManager.GetCurrentWebPartManager(wpz.Page);
                bool inDisplayMode = !swpm.GetDisplayMode().AllowPageDesign;
                if (inDisplayMode && wpz.DisplayTitle.Contains("iGoogle"))
                {
                    // Render the WebPartZone
                    writer.Indent++;
                    // Render the web parts
                    if (wpz.WebParts.Count > 0)
                    {
                        WebPartCollection wpColl = new WebPartCollection(wpz.WebParts);
                        foreach (WebPart wp in wpColl)
                        {
                            writer.WriteLine();
                            writer.Write("<div id='" + wp.ID + "' class='widget'>");
                            writer.Write("<div class='widget-head'>");
                            writer.Write("<a class='collapse'>collapse</a>");
                            writer.Write("<h3>" + wp.Title + "</h3>");
                            writer.Write("<a class='remove'>remove</a>");
                            writer.Write("<a class='edit'>edit</a>");
                            writer.Write("</div>");
                            writer.Write("<div class='widget-edit'>");
                            writer.Write("<input type='text' type='text' class='iColorPicker' id='color" + wp.ID + "' />");
                            writer.Write("<div class='clearfix'></div>");
                            writer.Write("</div>");
                            writer.Write("<div class='widget-content'>");
                            wp.RenderControl(writer);
                            writer.Write("</div>");
                            writer.Write("</div>");
                        }
                    }
                    writer.Indent--;
                    writer.WriteLine();
                }
                else
                {
                    // If we are editing the page --> render the web part as usual.
                    base.Render(writer);
                }
            }
        }
    }
}

 

I am the SharePoint Development Lead at ICS Solutions Ltd. As well as broad knowledge in SharePoint 2007, 2010 & 2013 my specialities lie with SharePoint Branding, WebPart Development and JQuery integration.

6 Comments on "iGoogle UI for SharePoint 2010 – Part Four: Control Adapters"

  1. AR says:

    When is the next installment due? Waiting for it! :)

  2. Soon :). Need to get some time to check the quality.

  3. Andy says:

    Can’t wait to see the next installment!

  4. chaitanya says:

    Great To See I Google webpart in SharePoint 2010 …………Looking Forward for some more development in Igoogle Web parts

  5. Jay says:

    What is the best way to apply control adapter branding in smallsearchinput box?

  6. Why would you want to use a control adapter to brand the smallsearchinput box? Why not just use a delegate control to replace it with a version you require instead?

Got something to say? Go for it!