Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part V : SharePoint 2010 Integration


Series Content

  1. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part I : Overview, Concept, HTML Structure & Jquery Basics

  2. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part II : Dragging, Dropping, Sorting and Collapsing

  3. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part III : Saving WebPart states using Cookies

  4. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part IV : Control Adapters

  5. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part V : SharePoint 2010 Integration


Overview

In Part Five, we will take the previous posts and show you how to get it into SharePoint 2010. I’ll show how to create the Visual Studio Project, and then deploy the assets into SharePoint to create a working example.

Visual Studio Project & Assets

For this post will be using Visual Studio 2010 as our development platform. As part of my default development build i like to have the following VS plug-ins installed.
For this post, we will be using Visual Studio 2010 as our development platform. As part of my default development build, I like to have the following VS plug-ins installed:

SPI’s & Features

Our project will contain the following SPI’s (SharePoint Item) to deploy the required assets.
  • Module – This will deploy an iGoogle.aspx page with the configured WebPart Zones, and some dummy content editor webparts (CEWP) to show a working example.
  • Mapped Folder – A Mapped folder to the /_layouts directory will be used to deploy the css, images, and javascript files required and created from the first three parts of this series.
  • Control Adapter – Although technically not an SPI, the control adapter code from the previous post will also be created.
The Project will contain only a single feature which will deploy all the assets required for the iGoogle interface. This will be a SITE scoped feature with an event receiver to manage the addition of values to the compact.browser file.

Visual Studio

The first step for this and pretty much every other SharePoint Project is to fire up Visual Studio 2010 and create a new SharePoint 2010 Project. Call the project LifeInSharePoint.iGoogle. On the next screen we would also like to create this as a FARM solution. Sandbox solutions will not work as control adapters cannot be deployed using a Sandbox Solution.
Now that we have a project created, we first need to create some folders to contain our SPI’s. I like to organise my folders in a manner that I feel makes it easier to understand, so I will first create a Common folder which will contain a sub folder called ControlAdapters. NOTE: I do not have spaces in my folder names as visual studio will replace them with “_” in namespaces. I will now create another top level folder called Root and within this I will create another folder called Content. These two folders will contain the module that will deploy the iGoogle.aspx page and place WebParts onto the page. To ensure that we can access the images, js and css from anywhere, we will place them in the /_layouts folder. To deploy these to the Layouts folder from Visual Studio is very simple. Firstly you will need to right click on the project in Visual Studio > Add > SharePoint “Layouts” Mapped Folder.
This will create you a project named sub folder which we can use to place our css etc. Once this has been done your folder structure should look like this.
Now that I have the basic folder structure, I will now create a new a new class file for my ControlAdapter called WebPartRenderControlAdapter.cs. For the info on how to create and what goes into this class file, please see the previous post where I go into a lot more detail. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part IV : Control Adapters
The next step is to add the CSS, JS, and Image that we created in the first three parts of this blog series. (These will be available at the end of this post) in the supplied zip file.

Adding Content & Pages

Next, we need to create the root content module. This module contains two items. The first is the Elements.xml file which will contain the XML required to deploy our page, and the second item is the default.aspx page which we will provision. This default.aspx page contains the HTML snippets from the first couple of posts in this series as well as the references to our javascript and css which we are storing above in the /_layouts folder. Below is a snippet from within the default.aspx page.


<asp:Content ContentPlaceHolderId="PlaceHolderMain" runat="server">
<table cellspacing="0" border="0" width="100%">
<tr class="s4-die">
<td class="ms-pagebreadcrumb">
<asp:SiteMapPath SiteMapProvider="SPContentMapProvider" id="ContentMap" SkipLinkText="" NodeStyle-CssClass="ms-sitemapdirectional" runat="server"/>
</td>
</tr>
<tr>
<td class="ms-webpartpagedescription"><SharePoint:ProjectProperty Property="Description" runat="server"/></td>
</tr>
<tr>
<td>
<table width="100%" cellpadding="0" cellspacing="0" style="padding: 5px 10px 10px 10px;">
<tr>
<td valign="top">
<div class='column' id='leftCol'>
<WebPartPages:WebPartZone runat="server" FrameType="TitleBarOnly" ID="Left" Title="iGoogle Left" />
</div>
<div class='column' id='middleCol'>
<WebPartPages:WebPartZone runat="server" FrameType="TitleBarOnly" ID="Center" Title="iGoogle Center" />
</div>
<div class='column' id='rightCol'>
<WebPartPages:WebPartZone runat="server" FrameType="TitleBarOnly" ID="Right" Title="iGoogle Right" />
</div>
</td>
</tr>
<tr>
<td colspan="7">
<div id='toolbox-wrapper'>
<div id='toolbox-controls'>
<div class='badge'></div>
<p class="slide"><a href="#" class="btn-slide">Toolbox</a></p>
<p class="reset"><a href="#" class="btn-reset">Reset Widgets</a></p>
</div>
<div class='column' id='toolbox'>
</div>
<div style='clear:both;'></div>
</div>
</td>
</tr>
</table>
</td>
</tr>
</table>
<link rel="stylesheet" type="text/css" media="all" href="/_layouts/LifeInSharePoint.iGoogle/css/style.css" />
<!-- Load the jQuery Libraries -->
<script src="/_layouts/LifeInSharePoint.iGoogle/js/jquery-1.6.2.min.js" type="text/javascript"></script>
<script src="/_layouts/LifeInSharePoint.iGoogle/js/jquery-ui-1.8.16.custom.min.js" type="text/javascript"></script>
<script type="text/javascript" src="/_layouts/LifeInSharePoint.iGoogle/js/iColorPicker.js"></script>
<script type="text/javascript" src="/_layouts/LifeInSharePoint.iGoogle/js/jquery.cookie.js"></script>
<script type="text/javascript" src="/_layouts/LifeInSharePoint.iGoogle/js/jquery.corner.js"></script>
<!-- script file to add your own JavaScript -->
<script type="text/javascript" src="/_layouts/LifeInSharePoint.iGoogle/js/script.js"></script>
</asp:Content>

As you can see I have made some small changes by placing our three columns within a table to keep things nice and neat. The script references have also been updated to point to our deployed assets. The elements.xml file is very simple. It takes the default.aspx page and deploys it to the root of the current site creating an iGoogle.aspx page at that location.



<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">  
<Module Name="Root" Url="" Path=""> 
   <!-- iGoogle PAGE -->      
<File Name="iGoogle.aspx" Url="Root.Content.Pages/default.aspx" NavBarHome="True">      
<Property Name="Title" Value="SharePoint iGoogle Interface" />      
<Property Name="ContentType" Value="Document" /> 
      </File>
</Module>
</Elements>


As you can see there is not a lot to it. We are setting the name of the deployed file to iGoogle.aspx and the Url in this case is the relative url within the project, NOT the location it will appear on the site, a common mistake I have made many times. If you wanted to place the page in another location you can modify URL and Path attributes in the <module> tag to point to another location. Since we want to place the page on the root, these are left blank.

Adding WebParts

The final addition to this elements.xml file is to add some default WebParts on to the page. For this demo we are going to use some Content Editor WebParts which will have some dummy Lorem Ipsum text within. (You can replace the xml with some other WebParts if you like, as long as you know the XML) The XML element you need to add WebParts on to the page is the <AllUsersWebPart> Node. This node has attributes which we use to define the order on the page, as well as the WebPart Zone the WebPart is to appear in. The Snippet below shows a single item.


<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">  
<Module Name="Root" Url="" Path=""> 
   <!-- iGoogle PAGE -->      
<File Name="iGoogle.aspx" Url="Root.Content.Pages/default.aspx" NavBarHome="True">      
<Property Name="Title" Value="SharePoint iGoogle Interface" />      
<Property Name="ContentType" Value="Document" />
        <AllUsersWebPart WebPartOrder="1" WebPartZoneID="Left">          
<![CDATA[          
<WebPart xmlns="http://schemas.microsoft.com/WebPart/v2"   
       xmlns:cewp="http://schemas.microsoft.com/WebPart/v2/ContentEditor"> 
         <Assembly>Microsoft.SharePoint,
 Version=14.0.0.0, 
Culture=neutral, PublicKeyToken=71e9bce111e9429c
</Assembly>          
<TypeName>
Microsoft.SharePoint.WebPartPages.ContentEditorWebPart
</TypeName>          
<Title>
Left Content Editor
</Title>          <FrameType>None</FrameType>          <cewp:Content>              
Duis sit amet turpis risus. Proin turpis orci, tristique porttitor venenatis eget, lacinia vitae dolor. Maecenas ultrices elementum est, ut dictum velit interdum vel. Maecenas vel urna eu quam rutrum aliquam eget quis dui. Nunc fermentum neque quis dolor facilisis hendrerit. Curabitur semper fermentum tortor, ut consequat turpis facilisis vel. Aliquam erat volutpat. Nam viverra molestie ipsum, vel tempus magna suscipit id.
                  </cewp:Content>
          </WebPart>       
 ]]>        
</AllUsersWebPart> 
     </File>
</Module>
</Elements>


You can also see from the code above that we are surrounding the WebPart XML with a <![CDATA[]]> tag which means that the text within will be ignored by the XML Parser.

Creating the Feature

Now that we have nearly all the pieces of the puzzle, the next step is to create a feature in our solution which will deploy the items to SharePoint. You should notice in your project there is a Web scoped default feature called Feature1. We need to rename our feature to something more meaningful, so in the Solution Explorer right click and rename the feature. My preference for naming Features is as follows:
SCOPE.ProjectName.FeatureName
The reason for this is that there is no quick and easy way to know the scope of a feature from glancing at the solution explorer as all icons are the same. Therefore in our solution the feature will be:
SITE.LifeInSharePoint.iGoogle.Assets
The next step is to double click on this feature and it should open the feature management screen on the left side of the window. Within this window you are able to change the Display Title and Description as well as manage the items in the feature. We will call our feature LifeInSharePoint.iGoogle, the description can be what ever you please and the Scope should be set to SITE. Finally add the Root.Content.Pages SPI into the feature and we are nearly complete.

Writing the Feature Receiver

For those who remember the last post, the control adapter requires an entry into the compact.browser file. This entry registers our control adapter for use and it would be very useful if this was added automatically as part of our deployment. To do this we will need to create a small feature receiver to do this for us. To add a receiver, right click on the feature and click the Add Event Receiver link.
We are only going to manage the addition of the code to the compact.browser and not the retraction from the solution. This can be added to your solution if you wish but to save time I will ignore it.
Our first step is to create two string constants which will contain the Control Adapter Type and the Assembly Name of the Solution. The Assembly name will only contain the first part as the full assembly name will be retrieved later through reflection.



private const string WEBPART_ADAPTER_CONTROL_TYPE = "System.Web.UI.WebControls.WebParts.WebPartZone";
private const string WEBPART_ADAPTER_TYPE = "LifeInSharePoint.iGoogle.Common.ControlAdapters.WebPartRenderControlAdapter,";

The next step is to uncomment the FeatureActivated method and add the following code in.



public override void FeatureActivated(SPFeatureReceiverProperties properties){    
// Load up the Compat.browser and add the the control adaptor    
// Get the site collection    
SPSite SiteCollection = (SPSite)properties.Feature.Parent; 
   UpdateCompatBrowser(SiteCollection);
}

This code simply gets the current Site Collection from the features property collection and then passes that SPSite object to the UpdateCompactBrowser method which is explained below in the code comments for each line.


/// <summary>
/// Add our control adaptor to the Compat.Browser file
/// </summary>
/// <param name="SiteCollection"></param>
private void UpdateCompatBrowser(SPSite SiteCollection){    
//Loop through each IISSetting in the current site collections Web App
    foreach (var IISSetting in SiteCollection.WebApplication.IisSettings)    
{        
//Create a path to the compact.browser        
String pathToCompatBrowser = IISSetting.Value.Path + @"\App_Browsers\compat.browser"; 
        //Load the Xml element
        XElement compatBrowser = XElement.Load
(pathToCompatBrowser);         
//Select the correct element  
      XElement existingElement = compatBrowser.XPathSelectElement(String.Format(                        "./browser[@refID = \"default\"]/controlAdapters/adapter[@controlType=\"{0}\" and @adapterType=\"{1}\"]",                        WEBPART_ADAPTER_CONTROL_TYPE,                        WEBPART_ADAPTER_TYPE + Assembly.GetExecutingAssembly().FullName));         
//If it cannot find the element then it will add it        
if (existingElement == null)        {            
// Get the node for the default browser.            

XElement controlAdapters = compatBrowser.XPathSelectElement("./browser[@refID = \"default\"]/controlAdapters");             
// Create and add the markup
            XElement newAdapter = new XElement("adapter");  
          newAdapter.SetAttributeValue("controlType", WEBPART_ADAPTER_CONTROL_TYPE); 
           newAdapter.SetAttributeValue("adapterType", WEBPART_ADAPTER_TYPE + Assembly.GetExecutingAssembly().FullName);      
       controlAdapters.Add(newAdapter);             
// Overwrite the old version of compat.browser with the new version           
 compatBrowser.Save(pathToCompatBrowser);        
}    
}
}


If we save all the items in the project, we are now ready to deploy our project to our site. When the feature activates it will run the code above which will make the necessary changes to the compact.browser file and our solution should work as expected.

Deployment & Testing

To deploy the solution we need to build the solution by right clicking on the project and clicking Build. After the project has been built and no errors are found, we can then deploy by again right clicking on the project and clicking Deploy. The default deployment configuration in Visual Studio will automatically activate the feature on the destination site. After deployment, navigate to the site and view the site collection features. We should see our feature deployed and activated.
If we now navigate to the root of the site collection and change the url to http://[SITE URL]/igoogle.aspx, then you should see our newly deployed interface with 5 different CEWP with some Lorem Ipsum text.
You should now be able to drag and drop these WebParts around the page, close, and change the colour. When you have finished and navigate away, refresh the page and the WebParts will remember their states. If you edit the page you will see how the Control Adapter does not render in edit mode enabling you to add new WebParts. You can see below that I have added a new Image WebPart to show how easy it is to create new “Widgets”.
NOTE: It is important to understand that this interface is designed for “Rollup” style WebParts. Due to how SharePoint 2010 and the Ribbon works with WebParts you may find some OOTB WebParts do not function fully. (Calendar WebPart, ListViewWebPart) The reason for some WebParts not working is that we are replacing the Chrome around the WebParts with our custom HTML (ControlAdapter). Many of the required ID’s etc are removed and therefore the Javascript that works with the Ribbon & Ajax fails. I am working on this and will post an update when I find a solution.

Summary

In this post we have outlined how to get the iGoogle interface into a SharePoint environment. Using a Visual Studio 2010 Project we have deployed css, images and javascript, created and deployed a Control Adapter, and added a page full of WebParts on to a site. I hope this post gives you a stepping stone on how to implement something similar on your SharePoint Deployments. Below I have uploaded a link to my Solution ZIP file that you can use and test on your environments. I have not done lots of cross browser or different environment testing of the solution so should you find an issue let me know and I will try my best to find a solution. In the next post I will show you how you can use the techniques shown in this series to come up with some innovative designs and implementations.

Download

LifeInSharePoint.iGoogle.zip



Series Content

  1. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part I : Overview, Concept, HTML Structure & Jquery Basics

  2. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part II : Dragging, Dropping, Sorting and Collapsing

  3. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part III : Saving WebPart states using Cookies

  4. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part IV : Control Adapters

  5. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part V : SharePoint 2010 Integration

Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part IV : Control Adapters


Series Content

  1. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part I : Overview, Concept, HTML Structure & Jquery Basics

  2. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part II : Dragging, Dropping, Sorting and Collapsing

  3. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part III : Saving WebPart states using Cookies

  4. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part IV : Control Adapters

  5. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part V : SharePoint 2010 Integration


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);                
}            
}        
}    }}

Series Content

  1. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part I : Overview, Concept, HTML Structure & Jquery Basics

  2. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part II : Dragging, Dropping, Sorting and Collapsing

  3. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part III : Saving WebPart states using Cookies

  4. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part IV : Control Adapters

  5. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part V : SharePoint 2010 Integration

Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part III : Saving WebPart states using Cookies


Series Content

  1. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part I : Overview, Concept, HTML Structure & Jquery Basics

  2. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part II : Dragging, Dropping, Sorting and Collapsing

  3. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part III : Saving WebPart states using Cookies

  4. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part IV : Control Adapters

  5. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part V : SharePoint 2010 Integration


Overview

In Part Three we will take the code from our previous post and enable the page to remember the state of the various settings that we have applied such as the WebPart positions, the order, and minimise states and the webpart header colours.

Javascript

For this post we will require a new javascript library to help manage the Cookies stored for each user. This library has been already included at the bottom of the existing demo’s. This time however we will finally use it. This Post will contain a lot of new Javascript and I will try my best to explain all of the items.

Saving the Position

The First thing that we will do is to Save the Position of all of the widgets at different times in the page life-cycle. The first thing we need to do is dynamically set the Zones setting previously defined in previous posts. as well as add another setting into the definition to store the Cookie settings.

settings : {
columns : '.column',
zones : '',
widgetSelector: '.widget',
handleSelector: '.widget-head',
contentSelector: '.widget-content',
editSelector: '.widget-edit',
toolboxSelector: '#toolbox',
widgetPlaceholder: 'widget-placeholder',
badgeSelector: '.badge',
positionCookie: 'widgetPositionsCookie'
},

This parameter was left blank on purpose to give the script the maximum flexibility. So to find out where the widgets are we need to find out what “zones” that the widgets are occupying. To do this we will create a new function called “defineZones” which will be used to iterate through the DOM and create a comma separated list of zones and their ID’s. Now a Zone in this tutorial is the ID associated with each column.




<div class='column' id='left'>    
<!-- Widgets Go here -->
</div>         
<div class='column' id='middle'>    
<!-- Widgets Go here -->
</div>         
<div class='column' id='right'>    
<!-- Widgets Go here -->
</div>

The full function is shown below. We will then dive into the details of the code.


defineZones : function () {        
var iSharePoint = this,        
$j = jQuery.noConflict(),        settings = this.settings;         
var temp = "";        
$j(settings.columns).each(function () {            
temp += "#" + $j(this).attr('id') + ",";        
});         
settings.zones = temp.substring(0, temp.length-1);
},


columns property and then creates the string based on the current items ID. In line 11 i then set the dynamic string to the zones parameter only after trimming the end of the string again.
Now that we have a set of defined zones we need to now set up a new function to save the state of all the widgets contained with each column.


function SaveState(){    
//Place SharePoint in No Conflict Mode    
var $j = jQuery.noConflict();    
//Update the Badge    
updateBadge();     
// Create/write a cookie and store it for 1 day    
setCookie(iSharePoint.settings.positionCookie, GetPositionState(), 1);  
} 
function GetPositionState(){        
//Get a local array of the Zones    
var zones = iSharePoint.settings.zones.split(',');    
//Holder variable for widget Positions    
var widgetPositions = "";    
//Loop through each zone to create a pipe delimited string    
for(z in zones)    {        
//Get the array of the current zone as CSV        
widgetPositions += $j(zones[z]).sortable('toArray') + "|";    
}    
//Trim the end off the string
    widgetPositions = widgetPositions.substring(0, widgetPositions.length-1);    
return widgetPositions;
}


This new function SaveState does the following actions. To this method we will add first create a new reference to our global object and then we update the Badge Details in-case something new has changed.
We then create a new cookie with the setCookie() method which will use the positionCookie setting to store the cookie key and then call the GetPositionState() method which will return the positions as a comma separated & pipe separated list.
The GetPositionState() method firstly gets a local array of all of the zones we found using the previous method and add it into a new zone variable. Then we create a blank string value to store our positions. Next we loop through each zone and for that we use the jQuery sortable methods with the “toArray” parameter which will get a comma seperated list of all of the zones and seperate each zone with a pipe character. Finally we will create a javascript alert of the widgetPositions found.
Next we place the SaveState(); method call into the remove button event handler, as well as the stop method of the makeSortable method so that when we remove or move a widget on the page it will provide us with the new position.
At this stage we now can save the position of each widget when we close and move them on the page between columns.

Retrieving the Position & Order

The next step is to now to retrieve the position of the locations when you load the page so that the widgets appear in their previous positions. To do this we need to firstly create a new method in our iSharePoint Namespace called loadStateFromCookie. This method will do two things. Firstly it will load the position state from the cookie and place it into a variable, and then it will pass this to another method ProcessWidgetPositionData which will place the widgets in the correct locations.


loadStateFromCookie : function() {        
var iSharePoint = this,        
$j = jQuery.noConflict(), 
       
settings = this.settings;  
       
var PositionStates = getCookie(settings.positionCookie);
             
this.ProcessWidgetPositionData(PositionStates); 
             updateBadge();
},

As you can see we firstly get a reference to the settings and the local jQuery object. We then set a custom variable PositionStates to the cookie value by using the getCookie method. We then pass this into the ProcessWidgetPositionData method and then finally we update the badge method which will ensure that the number of “closed” widgets is correct. We will now dissect the processing function.


ProcessWidgetPositionData :  function(PositionStates){        
var iSharePoint = this,        
$j = jQuery.noConflict(),        settings = this.settings;        
// Configure cookie        
if (PositionStates) {            
//Get the Array of Orders
            
var order = PositionStates.split('|');            
//Get the Array of Zones            
var zonearray = settings.zones.split(',');            
for(o in order)            {                
//Position the boxes in the correct locations                
$j(settings.widgetSelector).each(function () {                    
//Loops through each PositionState in order object                    
if (order[o].search($j(this).attr('id')) != -1)                    {                        
$j(zonearray[o]).append($j(this));                    
}                
});                
//Reorder the boxes in each zone                
if (order[o] != "") {                    
var _workOrder = order[o].split(',');                    
for (i = 0; i < _workOrder.length; i++)                        {                            
$j(zonearray[o]).append($j('#' + _workOrder[i]));                        
}                
}            
}        
}},

This method is quite long but is easier than it appears. As we have done for each method so far we need to get our local references to jQuery etc, and then we need to check that we have received some data in the PositionStates variable. Next we need to split the array of values which we created in the SaveState() method back into an array so that we can loop through them. We will also need to get the array of zones defined so that we can match up each of the arrays.


//Get the Array of Orders                
var order = PositionStates.split('|');
//Get the Array of Zones
var zonearray = settings.zones.split(',');

Next step is to loop through each of positions in the order array and perform two important tasks. The first task is to place the correct widgets in their correct columns and then the next step is to ensure that they are in the correct order in that column. We will firstly tackle the positions:


for(o in order){    
//Position the boxes in the correct locations    
$j(settings.widgetSelector).each(function () {        
//Loops through each PositionState in order object        
if (order[o].search($j(this).attr('id')) != -1)        {            
$j(zonearray[o]).append($j(this));        }    
});                
//More code to come...


What this first part of the function does is loops through all of the widgets on the page when it loads and search if the current widget is found in the order variable (using the .search method, if an item is not found then it will return -1) then we want to add that item to the current column (defined in the zonearray). NOTE: Because we are in control of the order that values are saved into the cookie the retrieval of the items can be predictable.
The next step is to then reorder the widgets in the current column.


//Reorder the boxes in each zone    
if (order[o] != "") {        
var _workOrder = order[o].split(',');        
for (i = 0; i < _workOrder.length; i++)            
{                
$j(zonearray[o]).append($j('#' + _workOrder[i]));            
}    
}}

This will again ensure that there is a value in the current order value (there can be a column with no items) and then it will split the widget values into an array. We now have an array of values which are in the order that they were saved so we can now process the array by appending the widgets in the current column again but this time in the correct order.  The complete code for this method is shown below:
ProcessWidgetPositionData :  function(PositionStates){        
var iSharePoint = this,        
$j = jQuery.noConflict(),        settings = this.settings;        
// Configure cookie        
if (PositionStates) {            
//Get the Array of Orders            var order = PositionStates.split('|');            
//Get the Array of Zones            
var zonearray = settings.zones.split(',');            
for(o in order)            {                
//Position the boxes in the correct locations                
$j(settings.widgetSelector).each(function () {                    
//Loops through each PositionState in order object                    
if (order[o].search($j(this).attr('id')) != -1)                    {                        
$j(zonearray[o]).append($j(this));                    
}                
});                
//Reorder the boxes in each zone                
if (order[o] != "") {                    
var _workOrder = order[o].split(',');                    
for (i = 0; i < _workOrder.length; i++)                        {                            
$j(zonearray[o]).append($j('#' + _workOrder[i]));                        
}         
       }      
      }        
}},


Demo

To view a demo of where we have got to thus far click here

Saving the Collapsed State

The next step is to save the collapsed state of each of the widgets on the page. The first step is to modify the SaveState(); function to include the following line at the end.


setCookie(iSharePoint.settings.minimiseCookie, GetMinimisedState(), 1);

This line does what it say and sets the minimiseCookie to the value that is returned from the GetMinimisedState() method. The GetMinimisedState() is a new method which is going to be added outside of the iSharePoint namespace and will simply go through each widget which has the minimised class attached to it and add it to an array.


function GetMinimisedState(){    
var $j = jQuery.noConflict();    
var list = new Array();    
$j('.minimised').each(function () {        
list.push($j(this).attr('id'));    
});    
return list;
}


That’s it for saving the collapsed state ..nice and simple.

Retrieving the Collapsed State

To retrieve the collapse state is also another simple addition. To do this we will load the state from the cookie set above and then for each of the widgets defined in the array it will set them to minimised and collapse the widget.
So the first step is to modify the loadStateFromCookie function to include two lines. The first line is to set a local variable “MinimisedStates” to the cookie value:


var MinimiseStates = getCookie(settings.minimiseCookie);

the next step is then to process that data by passing it into a ProcessMinimiseData() method.


this.ProcessMinimiseData(MinimiseStates);

What this processing method does is split the array of MinimiseStates and then for each item in that array it will locate the widget with that ID.


ProcessMinimiseData :  function(MinimiseStates){        
var iSharePoint = this,        
$j = jQuery.noConflict(),        settings = this.settings;    
if (MinimiseStates != null) {  
      MinimiseStates = MinimiseStates.split(',');         
for (i = 0; i < MinimiseStates.length; i++) {            
var id = "#" + MinimiseStates[i];            
$j(id).find(settings.contentSelector).slideToggle("slow");            
$j(id).toggleClass("minimised");        
}    
}},

If there is a widget in the array it will perform a slideToggle and then add the minimised class to the current widget. The final step to this piece of the puzzle is to ensure that when you click on the button to minimise each widget is to run the save state method. To do this we just modify the buttonFunctions method with the highlighted item below.


$j('.collapse').mousedown(function (e) {    
e.stopPropagation();}).click(function () {    
$j(this).parents(settings.widgetSelector).find(settings.contentSelector).slideToggle("slow");    
$j(this).parents(settings.widgetSelector).toggleClass("minimised");
//ADD THIS NEXT LINE IN    
SaveState();    
return false;
})

That’s it for this section.

Demo

To view a demo of where we have got to thus far click here

Saving the Colour

This section we will save the current colour set in the webparts colour settings panel. As we built in previous posts the colour widget can be any colour you can think of so we need to find a way to process this data on the fly. Once again like above we need to modify the SaveState(); function to include another line at the end.


setCookie(iSharePoint.settings.colorCookie, GetWidgetColor(), 1);

Like before this will set the cookie to the output from the GetWidetColor method. This method simply goes through each widget and gets the value that is stored in the text box with the class iColorPicker.


function GetWidgetColor() {    
var $j = jQuery.noConflict();    
var list = new Array();    
$j(iSharePoint.settings.widgetSelector).each(function () {        
list.push($j(this).attr('id') + "|" + $j(this).find('.iColorPicker').val());    });    
return list;
}


When the method loops through each text box it will also pipe delimit the widget id along with the colour so we are able to match them up on retrieval. Next step..getting the colour back on load.

Retrieving the Colour

To retrieve the colour of the widget is very similar to the previous “retrieve” methods. To do this we will load the state from the cookie set above and then for each of the widgets defined in the array it will set them to colour defined and then re-color the widget.
So the first step is to modify the loadStateFromCookie function to include two lines. The first line is to set a local variable “ColorStates” to the cookie value:


var ColorStates = getCookie(settings.colorCookie);

the next step is then to process that data by passing it into a ProcessWidgetColorData() method.


this.ProcessWidgetColorData(ColorStates);

What this processing method does is split the array of ColorStats and then for each item in that array it will find the widget with that ID using jQuery.Find() and set the Text Box Value, CSS and Handle Selector CSS. The first couple of lines should be all too familiar by this point. Below is the full method:


ProcessWidgetColorData :  function(ColorStates){        
var iSharePoint = this,        
$j = jQuery.noConflict(),        settings = this.settings;        
if (ColorStates != null) {
        ColorStates = ColorStates.split(',');        
for (i = 0; i < ColorStates.length; i++) {             
var id = "#" + ColorStates[i].split('|')[0];            
var color = ColorStates[i].split('|')[1];            
$j(id).find('.iColorPicker').val(color);            
$j(id).find('.iColorPicker').css("background-color",color);            
$j(id).find(settings.handleSelector).css('backgroundColor', color);          
}    
}},

That is all that is required for the colour modifications..we are almost done now .

Demo

To view a demo of where we have got to thus far click here

Resetting Layout

The final step we need to implement for the base functionality is the ability for the user to “reset” his / her layout back to the predefined default. To do this we need to add 5 lines of javascript into the buttonFunctions method into the reset button click event.


deleteCookie(settings.positionCookie);
deleteCookie(settings.minimiseCookie);
deleteCookie(settings.colorCookie);
$j('.iColorPicker').each(function () {    
$j(this).val('');
});


All this does (its pretty obvious) is delete each of the user set cookies and loop through each colour picker text box and clear the values which will reset the colours.

Summary

So that’s the end of part 3 of this blog series. It has been quite a long one but i hope that you have all learned something from this and can see how easy it is to combine jQuery and HTML to create a really great interface. The full js for this post is shown below. I will be hoping to get the next posts completed faster next time. Please leave your comments..they are always appreciated.


var iSharePoint = {     
jQuery : $,     
settings : { 
     columns : '.column', 
     zones : '', 
       widgetSelector: '.widget',
        handleSelector: '.widget-head',
        contentSelector: '.widget-content',
        editSelector: '.widget-edit',
        toolboxSelector: '#toolbox',
        widgetPlaceholder: 'widget-placeholder',
        badgeSelector: '.badge',
        positionCookie: 'widgetPositionsCookie',        minimiseCookie: 'widgetMinimisedCookie',        colorCookie: 'widgetColorCookie'    
},     
init : function () {        
settings = this.settings;        this.defineZones();        this.buttonFunctions();        this.makeSortable();        this.loadStateFromCookie();        this.activateColours();    },       defineZones : function () {            var iSharePoint = this,            
$j = jQuery.noConflict(),            settings = this.settings;             var temp = "";            
$j(settings.columns).each(function () {                
temp += "#" + $j(this).attr('id') + ",";            
});             
settings.zones = temp.substring(0, temp.length-1);     
},       
loadStateFromCookie : function() {            
var iSharePoint = this,            
$j = jQuery.noConflict(),            settings = this.settings;
             var PositionStates = getCookie(settings.positionCookie);
            var MinimiseStates = getCookie(settings.minimiseCookie);
            var ColorStates = getCookie(settings.colorCookie);
               this.ProcessWidgetPositionData(PositionStates);
            this.ProcessMinimiseData(MinimiseStates);
            this.ProcessWidgetColorData(ColorStates);
             updateBadge();    
},           ProcessWidgetPositionData :  function(PositionStates){            
var iSharePoint = this,            
$j = jQuery.noConflict(),            settings = this.settings;            
// Configure cookie            
if (PositionStates) {                
//Get the Array of Orders                
var order = PositionStates.split('|');                
//Get the Array of Zones                var zonearray = settings.zones.split(',');                
for(o in order)                {                    
//Position the boxes in the correct locations                    
$j(settings.widgetSelector).each(function () {                        
//Loops through each PositionState in order object                        
if (order[o].search($j(this).attr('id')) != -1)                        {                            
$j(zonearray[o]).append($j(this));                        
}                    
});                    
//Reorder the boxes in each zone                    
if (order[o] != "") {                        
var _workOrder = order[o].split(',');                        
for (i = 0; i < _workOrder.length; i++)                            {                                
$j(zonearray[o]).append($j('#' + _workOrder[i]));                            
}                    
}                
}            
}    
},           
ProcessMinimiseData :  function(MinimiseStates){            
var iSharePoint = this,            
$j = jQuery.noConflict(),            settings = this.settings;        
if (MinimiseStates != null) {            
MinimiseStates = MinimiseStates.split(',');             
for (i = 0; i < MinimiseStates.length; i++) {                
var id = "#" + MinimiseStates[i];                
$j(id).find(settings.contentSelector).slideToggle("slow");                
$j(id).toggleClass("minimised");            
}        
}    
},         
ProcessWidgetColorData :  function(ColorStates){            
var iSharePoint = this,            
$j = jQuery.noConflict(),            settings = this.settings;            
if (ColorStates != null) {            ColorStates = ColorStates.split(',');            
for (i = 0; i < ColorStates.length; i++) {                 
var id = "#" + ColorStates[i].split('|')[0];                
var color = ColorStates[i].split('|')[1];                
$j(id).find('.iColorPicker').val(color);                
$j(id).find('.iColorPicker').css("background-color",color);                
$j(id).find(settings.handleSelector).css('backgroundColor', color);              
}        
}    
},       
activateColours : function () {            
var iSharePoint = this,            
$j = jQuery.noConflict(),            settings = this.settings;             
$j(".pickerTable").click(function () {                
$j('.iColorPicker').each(function () {                    
var str = "";                    
str += $j(this).val();                    
$j(this).parents(settings.widgetSelector).find(settings.handleSelector).css('backgroundColor', str);                
});                
SaveState();            
});    
},       
makeSortable : function () {            var iSharePoint = this,            
$j = jQuery.noConflict(),            settings = this.settings;             
$j(settings.columns).sortable({                connectWith: 
$j(settings.columns),                handle: settings.handleSelector,                cursor: 'move',                
revert: true,                placeholder: settings.widgetPlaceholder,                forcePlaceholderSize: true,                revert: 300,                delay: 100,                opacity: 0.8,                start: function (event, ui) {                    
$j(settings.columns).css("background-color","#e7e5e5");                
},                
stop: function (event, ui) {                    
$j(settings.columns).css("background-color","transparent");
                    
$j(settings.toolboxSelector).css("background-color","#fff"); 
                   
SaveState();                
}            
})           
},           
buttonFunctions : function() {            
var iSharePoint = this,            
$j = jQuery.noConflict(),            settings = this.settings;             
$j('.slide').mousedown(function (e) {                
// Stop event bubbling:                e.stopPropagation();            
}).click(function () {                
//To Do - Slide toolbox down                
$j(settings.toolboxSelector).slideToggle("slow");            
// Return false, prevent default action:            
return false;            
})              
$j('.reset').mousedown(function (e) {                
// Stop event bubbling:                e.stopPropagation();            
}).click(function () {                
if(confirm('Are you sure you want to reset your layout?')) {                deleteCookie(settings.positionCookie);
                deleteCookie(settings.minimiseCookie);
                deleteCookie(settings.colorCookie); 
               $j('.iColorPicker').each(function () {                    
$j(this).val('');                
});                
location.reload();                
}            
// Return false, prevent default action:            
return false;            
})              
$j('.edit').mousedown(function (e) {                
e.stopPropagation();            
}).click(function () {                
//To Do - What to do when clicked first time                
$j(this).parents(settings.widgetSelector).find(settings.editSelector).slideToggle("slow");                
return false;            
})             
// Create new anchor element with class of 'remove':            
$j('.remove').mousedown(function (e) {                
// Stop event bubbling:                e.stopPropagation();            
}).click(function () {                
// Confirm action - make sure that the user is sure:                
if(confirm('Please confirm the removal of the widget.  This can be restored from the widget toolbox at the bottom of the screen.')) {                    $j(this).parents(settings.widgetSelector).appendTo(settings.toolboxSelector);                    
SaveState();                
}            
});             
$j('.collapse').mousedown(function (e) {                
e.stopPropagation();            
}).click(function () {                
$j(this).parents(settings.widgetSelector).find(settings.contentSelector).slideToggle("slow");                
$j(this).parents(settings.widgetSelector).toggleClass("minimised");                
SaveState();                
return false;            
})    },}; 
function updateBadge(){            
$j = jQuery.noConflict(),            
$j(iSharePoint.settings.badgeSelector).text($j(iSharePoint.settings.toolboxSelector + " > " + iSharePoint.settings.widgetSelector).size());} 
function SaveState(){    
//Place SharePoint in No Conflict Mode    
var $j = jQuery.noConflict();    
//Update the Badge    
updateBadge();     
// Create/write a cookie and store it for 1 day    
setCookie(iSharePoint.settings.positionCookie, GetPositionState(), 1);    
setCookie(iSharePoint.settings.minimiseCookie, GetMinimisedState(), 1);    
setCookie(iSharePoint.settings.colorCookie, GetWidgetColor(), 1);} 
function GetPositionState(){    
//Get a local array of the Zones    
var zones = iSharePoint.settings.zones.split(',');    
//Holder variable for widget Positions    
var widgetPositions = "";    
//Loop through each zone to create a pipe delimited string    
for(z in zones)    {        
//Get the array of the current zone as CSV        
widgetPositions += $j(zones[z]).sortable('toArray') + "|";    }    
//Trim the end off the string    widgetPositions = widgetPositions.substring(0, widgetPositions.length-1);    
return widgetPositions;} 
function GetMinimisedState(){    
var $j = jQuery.noConflict();   
 var list = new Array();    
$j('.minimised').each(function () {        
list.push($j(this).attr('id'));    });    
return list; } 
function GetWidgetColor() {    
var $j = jQuery.noConflict();    
var list = new Array();    
$j(iSharePoint.settings.widgetSelector).each(function () {        
list.push($j(this).attr('id') + "|" + $j(this).find('.iColorPicker').val());    });    
return list;
} 
jQuery(document).ready(function(){        
var $j = jQuery.noConflict();        
$j(iSharePoint.settings.widgetSelector).corner();        
updateBadge();        
iSharePoint.init();  
});


Series Content

  1. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part I : Overview, Concept, HTML Structure & Jquery Basics

  2. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part II : Dragging, Dropping, Sorting and Collapsing

  3. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part III : Saving WebPart states using Cookies

  4. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part IV : Control Adapters

  5. Creating iGoogle UI for SharePoint 2010/SharePoint 2013 - Part V : SharePoint 2010 Integration