All posts tagged Webparts

SharePoint Metro Weather – Sneak Peak

weather_banner

I would like to just show off a new WebPart that is nearly ready for public release / testing by the community.  After a long time of SharePoint development I have become bored and disappointed at the styling of the various “Weather WebParts” that are available.  I decided to build my own which is not only free, but is styled my much loved “Metro Windows 8″ style.

metroweather

The Weather displays the current weather and up to 5 days forecast.  I am getting my weather data from http://www.worldweatheronline.com/ which provides a free Weather API which has a 500 requests/hr limit.  Therefore i have implemented caching into the webpart to ensure that this is not an issue.  I hope to finish the WebPart soon and it will also be rendered using XSLT to ensure that it is extensible to your requirements.

Let me know your initial thoughts on the design and the release will be coming VERY soon.

iGoogle UI for SharePoint – Part Five : SharePoint 2010 Integration

part5

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 Adaptersc
  5.     Part Five – SharePoint 2010 Integration – Current Article
  6.     Part Six – Bringing it all together
  7.     Bonus – Saving WebPart States using the Client Object Model

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 ControlAdaptersNOTE: 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. iGoogle UI for SharePoint 2010 – Part Four: 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

iGoogle UI for SharePoint – Part Two : Dragging, Dropping, Sorting and Collapsing

iGoogle-Part-Two

Series Content

  1.     Part One – Overview, Concept,  HTML Structure & jQuery Basics
  2.     Part Two – Dragging, Dropping,  Sorting and Collapsing – Current Article
  3.     Part Three – Saving WebPart states using Cookies
  4.     Part Four – Control Adapters
  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 Two we will take the basic html from part one and using some Javascript magic make it into a more compelling and interactive page.  Firstly we will identify the various techniques individually and then put them all together at the end of this post.

Javascript

As mentioned in the previous post, a decision was made to use the jQuery Library with the jQuery UI abstraction layer to provide the functionality for our “iGoogle” interface.  It is using this library that will speed up development and keep everything cross-browser capable.  In our solution we will be using the following effects / methods from the jQuery & jQuery UI library.

We will begin by modifying our script.js file to include a new global object called “iSharePoint“.  This will be our namespace that we will use to contain all of the functionality for our interface.  Within this object we will create a set of functionality that uses the libraries and methods described above.

    var iSharePoint = {
        settings : {
           // All settings will be defined here
        },
        init : function(){
            // This init function kicks off the whole thing
        }
    };

This method will be called from the jQuery Document Ready function which will ensure all the items are ready to be manipulated before we do anything.

jQuery(document).ready(function(){
		iSharePoint.init();
});

iSharePoint Settings

The settings object we have defined will contain all of the global settings we require to make this item as functional as possible.

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

There are quite a few settings which I will now explain each in detail:

  • columns – this is the class selector for each of the columns that we would like to be sortable and drag and drop between.
  • zones – these are the unique ID’s of each of the columns. (This is blank because we are going to dynamically populate this based on the DOM contents)
  • widgetSelector – this is the wrapper div of each of our draggable widgets.
  • handleSelector – this is the grab handle class for the div we will use to drag the widget around the page
  • contentSelector – this defines the class of the widget content wrapper which is used for the collapsing and expanding functionality.
  • editSelector – this defines the class of the edit panel wrapper which is used to contain the edit functions.
  • toolboxSelector – this defines the footer toolbox where unwanted or unused widgets can sit out of the way.
  • widgetPlaceholder – this is the name of the class that is assigned to the drag target div so you know where your div will end up.

Bring the Widgets to life

The next part is a little more complicated but we will take each part individually.  We now need to add another method to our global object that we will call “buttonFunctions

	buttonFunctions : function() {
             //All functions for the buttons will belong in this method
        });

To this method we will add first create a new reference to our global object.  This is useful if multiple instances are required as well as if you ever need to rename the global object.  We will then need to create a new variable so we can reference jQuery without using the “$” symbol in noConflict() mode as that symbol it is used by SharePoint’s internal js files and can cause clashes. (Thanks to Chris O’Brien for explaining this here)  We will then create a local reference to the settings only as a shortcut so you don’t need to type “this.settings” every time.

	buttonFunctions : function() {
	    var iSharePoint = this,
            $j = jQuery.noConflict(),
            settings = this.settings;
        });

The next step is to define our button click handlers so that each widget has independent click events that can be raised.  One thing to be careful of is what is known as “Event Bubbling“.  This is when, upon clicking on an element in the DOM the event will bubble upwards through to the highest level element which an event the same as the ones you have recently triggered.  To prevent this from happening we need to add a line of code (e.stopPropagation();) which will prevent this from happening.  Below is a skeleton framework of the button click handlers.

	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
				alert('Toolbox to be expanded');
			// Return false, prevent default action:
			return false;
			}) 

			$j('.reset').mousedown(function (e) {
				// Stop event bubbling:
				e.stopPropagation();
			}).click(function () {
				//To Do - Reset layout
				alert('Reset Layout');
			// 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
                               alert('Toolbox Toggle');
                               return false;
                        })		

			// Create new anchor element with class of 'remove':
			$j('.remove').mousedown(function (e) {
				// Stop event bubbling:
				e.stopPropagation();
			}).click(function () {
				//To Do - Code to remove widget and place in toolbox
				alert('Remove Widget from page');
			// Return false, prevent default action:
			return false;
			})  	

			$j('.collapse').mousedown(function (e) {
				e.stopPropagation();
			}).click(function () {
				//To Do - What to do when clicked first time
				alert('Collapse Toggle');
				return false;
			})
	}

As you can see there is quite alot of framework code added to contain the button functionality.

Demo

You can see and example of where we are currently by clicking here for a demo

Button Functions in More Detail

We will now flesh out each of the functions in the method to provide them with some actual functionality starting with the widget toolbox slider at the bottom.

			$j('.slide').mousedown(function (e) {
				// Stop event bubbling:
				e.stopPropagation();
			}).click(function () {
				$j(settings.toolboxSelector).slideToggle("slow");
			// Return false, prevent default action:
			return false;
			})

As you can see we have changed line 5 from the alert to

$j(settings.toolboxSelector).slideToggle("slow");

This simple line will enable use to open and close the toolbox at the bottom.  Nice and simple.

The reset button will not be functional at this point as in future posts we will be resetting the cookies and refreshing the page.  At this stage we will simply ask for confirmation of the action and then refresh the page.

 

			$j('.reset').mousedown(function (e) {
				// Stop event bubbling:
				e.stopPropagation();
			}).click(function () {
				if(confirm('Are you sure you want to reset your layout?')) {
				location.reload();
				}
			// Return false, prevent default action:
			return false;
			})

We have again removed the alert placeholder from the framework above and instead added three lines of code 5-7 which will ask the user to confirm that they want to reset and if they click yes then the page will reload otherwise nothing will happen.  Two down, Three to go.

Next we will add the toggle functionality to the edit toolbar.  This again is another single line but with some nested functions which will be explained.

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

As you can see we have once again replaced the alert placeholder with the single line.  If we look at the html of a widget we can explain how this works.

			<div class='widget' id='6'>
				<div class="widget-head">
					<a class='collapse'>collapse</a>
					<h3>Widget title 6</h3>
					<a class='remove'>remove</a>
					<a class='edit'>edit</a>
				</div>
				<div class="widget-edit">
					<input type='text' type="text" class="iColorPicker" id='color6' />
					<div class='clearfix'></div>
				</div>
				<div class="widget-content">
					<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse lorem orci, ornare at malesuada a, pulvinar lobortis neque. Integer et felis vel augue suscipit vestibulum id nec nulla.</p>
				</div>
			</div>

What we are trying to do is traverse the DOM to open and close the widget-edit div.  Our starting location is <a class=’edit’>edit</a> which is the first part of the code $j(this).  We then want to get the reference to the current buttons parent only, with a selector of settings.widgetSelector (which in our settings is set to “.widget”).  We then want to find the widget-edit div which is this part of the code find(settings.editSelector) and then we finally ask the code to perform a slideToggle(“slow”).  The slow property creates a nice visible slide.  Alternate values for this can be fast or to not provide a property at all.

So..Whats next.  The next item on the list is the remove functionality.  What we want this to do is remove the widget from the current div that it sits in and place it in the toolbox div.  This div has some specific CSS attached to it which will collapse the whole widget and just show the title bar with no buttons.

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

This time we are using a similar method to the previous function by traversing the DOM to find the current buttons parent.  However this time we want to take this whole DIV and using the appendTo() method we can attach it to the toolboxSelector div defined in the settings.  Simple!

The final one to do is the collapse button.  This once again is very similar to the edit button but with an extra line afterwards.

			$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");
				return false;
			})

Once again you can see that we are getting the reference to the parent and then this time finding the contentSelector to perform the slide toggle on.  Afterwards we are once again getting the parent widget wrapper but this time we are now adding a class via the toggleClass method.  We need to do this so in future posts when we are saving the various states we can easily identify which widgets are minimised.

Thats is it for the button configuration.  We can now delete, open the edit box and toolbox, we can delete a widget from the main view and we can reset the page and force a page refresh.

Demo

To view a demo of where we are so far click here

Adding Drag and Drop functionality

So next up is the nicest piece which is the ability to drag and drop widgets between the columns.  Firstly like we did for the buttons we are going to add another method to our global object that we will call “makeSortable”.  Once again like above we will also add the iSharePoint reference, noConflict and settings references to the top.

	makeSortable: function() {
	    var iSharePoint = this,
            $j = jQuery.noConflict(),
            settings = this.settings;
        });

To this function we will now add a reference to the jQuery sortable function.  This function takes a set of parameters which will be explained below the code sample.

			$j(settings.columns).sortable({
				connectWith: $j(settings.columns),
				handle: settings.handleSelector,
				cursor: 'move',
				revert: true,
				placeholder: settings.widgetPlaceholder,
				forcePlaceholderSize: true,
				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");
				}
			})
  • Line 1 : take the reference of the columns from the settings and runs the sortable method on it.
  • Line 2 : this line enables us to connect all columns together so you are able to drag and drop between all instances
  • Line 3 : defines the element that you use to drag and drop
  • Line 4 : defines what the cursor looks like when dragging
  • Line 5 : If set to true, the item will be reverted to its new DOM position with a smooth animation.
  • Line 6 : The class that gets applied to the otherwise white space when you are dragging.
  • Line 7: Self explanitory, forces the placeholder to have a size.
  • Line 8: Time in milliseconds to define when the sorting should start.
  • Line 9: Opacity of the element currently being dragged.

The following lines 10 – 12 and 13-16 are two functions that get run at the start and end fo the dragging process.  The start function is used to change the colour of the columns so that they are highlighted when you drag so you know what the boundaries are.  The stop function reverses this and resets the toolbox and columns back to their previous state.  In future posts we will be using the stop function to save the state of the widgets.

Notifications

You may have noticed by now that there is a div called “Badge” which is displayed in the HTML as a red circle.  This does indeed have a purpose to display the number of items currently in the toolbox.  To do this we once again need to add alittle piece of jQuery magic to bring it all together.

Firstly we need to create a global function which is not in the iSharePoint object.

function updateBadge()
{
            $j = jQuery.noConflict(),
			$j(iSharePoint.settings.badgeSelector).text($j(iSharePoint.settings.toolboxSelector + " > " + iSharePoint.settings.widgetSelector).size());
}

What this does is count the number of widget divs that are in the toolbox at the bottom using the .Size() method.  The references to the divs are taken from the iSharePoint settings.  All that is needed to do now is to add the updateBadge(); to the ready function.

jQuery(document).ready(function(){
		iSharePoint.init();
		updateBadge();
});

Then add it to the .remove click event.

			// 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);
					updateBadge();
				}
			});

And then finally add it to the stop event from the sortable method.

				stop: function (event, ui) {
					$j(settings.columns).css("background-color","transparent");
					$j(settings.toolboxSelector).css("background-color","#fff");
					updateBadge();
				}

Colour Picker – Edit toolbar

The final piece of this post is to enable the colour of the header to be changed using the toolbox.  To do this we need to add another function to the global iSharePoint object called activateColours which will be used to process the colours.

The Markup for the edit toolbox is as follows:

				<div class="widget-edit">
					<input type='text' type="text" class="iColorPicker" id='color111' />
					<div class='clearfix'></div>
				</div>

As you can see this is nothing different from previous code but as you can see there is an LI with a class of each of the colours that you would like to use.  You are also able to put hex colours into the class names and they will work just fine.  The code for the function is shown below:

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

The first 4 lines should be nothing new as it is the same as previous functions.  The first loop goes through every unordered list on the page with a class of color and sets the background colour of the list item to what is in the class name.  Once all the items have their colour all we need to do is to attach a click handler to each of the list items themselves so they can control their parents header which is what the next loop then does.

Finally we need to add the function to the init function so that it will become active.

    init : function () {
		settings = this.settings;
		this.buttonFunctions();
		this.makeSortable();
		this.activateColours();
    },

Demo

So want to see the whole thing working?  Click here to see the final result of this quite epic blog post.

Summary

In this post we have outlined how to add the various functionality to our page using javascript and the jQuery Libraries.  Next time I will explain how to save the state of the page using Cookies.  I hope this post has been useful and please leave some comments about what you would like to see in future posts.

 

iGoogle UI for SharePoint – Part One : Overview, Concept, HTML Structure & Jquery Basics

iGoogle---Part-One

Series Content

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

Overview

This is the first in a series of posts which will explain how to create an iGoogle style interface for SharePoint 2010.  More and more clients are asking for an iGoogle or BBC Homepage style homepage for their intranets and out of the box in SharePoint 2010 there is no method to do this.  While you can drag and drop webparts in “Edit Mode” in a WebPart page, end user however is stuck on where to place their webparts on the page.  This series will aim to provide a mechanism where end users are able to take control of their page and make the SharePoint experience more personal.

There are many sites on the internet which have the ability to drag and drop components around the page and save their locations for your next visit.  Some of the most well known examples of this interface are:

iGoogle – http://www.google.com/ig?hl=

BBC Homepage – http://www.bbc.co.uk/

Both these sites give you the ability to drag and drop various widgets around the page.  You can also close widgets you do not want to see and minimise others to maximise space on the page.  This is the kind of interface that we are going to create for use in SharePoint using jQuery and some C# code.

The Plan

First, let’s list exactly what we’ll be creating here and what features it will have:

  • The interface will contain several widgets (WebParts).
  • Each widget can be collapsed and removed via controls on the page.
  • The widgets can be sorted into an unlimited number of columns.
  • WebParts will be have their rendering controlled via a control adapter which will modify their look and feel.
  • Widgets will have their location and states saved using cookies.
  • Creating a simple Visual Studio 2010 solution to deploy an example.

This post will provide an overview of what we are planning to build as well as getting some development environments configured for your own personal demos.

Getting Started

To get started in this post we will be creating a demo environment to ensure that the Javascript, HTML and CSS are all working together for use in future posts.  Initially we will not be touching SharePoint as it is not necessary at this stage.  Firstly we will need to create a base HTML template that will load a specific CSS stylesheet, images and Javascript libraries.

HTML

Below is the base HTML that will be used in our initial demo.  We have a wrapper div that surrounds three div columns called “Left”, “Middle” and “Right”.  Within each column is the widget HTML that will be used to wrap each WebPart.  Each widget has a wrapper div as well as a header and body content divs.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

	<!-- Always force latest IE rendering engine (even in intranet) & Chrome Frame. Remove this if you use the .htaccess -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />

	<title>Life In SharePoint - Drag and Drop - Part One</title>

	<link rel="stylesheet" type="text/css" media="all" href="css/style.css" />

</head>	

<body>

	<div class='wrapper'>

		<div class='column' id='left'>
			<div class='widget' id='111'>
				<div class="widget-head">
					<a class='collapse'>collapse</a>
					<h3>Widget title 1</h3>
					<a class='remove'>remove</a>
					<a class='edit'>edit</a>
				</div>
				<div class="widget-edit">
					<input type='text' type="text" class="iColorPicker" id='color111' />
					<div class='clearfix'></div>
				</div>
				<div class="widget-content">
					<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse lorem orci, ornare at malesuada a, pulvinar lobortis neque. Integer et felis vel augue suscipit vestibulum id nec nulla.</p>
				</div>
			</div>
			<div class='widget' id='2'>
				<div class="widget-head">
					<a class='collapse'>collapse</a>
					<h3>Widget title 2</h3>
					<a class='remove'>remove</a>
					<a class='edit'>edit</a>
				</div>
				<div class="widget-edit">
					<input type='text' type="text" class="iColorPicker" id='color2' />
					<div class='clearfix'></div>
				</div>
				<div class="widget-content">
					<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse lorem orci, ornare at malesuada a, pulvinar lobortis neque. Integer et felis vel augue suscipit vestibulum id nec nulla.</p>
				</div>
			</div>
		</div>		

		<div class='column' id='middle'>
			<div class='widget' id='3'>
				<div class="widget-head">
					<a class='collapse'>collapse</a>
					<h3>Widget title 3</h3>
					<a class='remove'>remove</a>
					<a class='edit'>edit</a>
				</div>
				<div class="widget-edit">
					<input type='text' type="text" class="iColorPicker" id='color3' />
					<div class='clearfix'></div>
				</div>
				<div class="widget-content">
					<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse lorem orci, ornare at malesuada a, pulvinar lobortis neque. Integer et felis vel augue suscipit vestibulum id nec nulla.</p>
				</div>
			</div>
			<div class='widget' id='4'>
				<div class="widget-head">
					<a class='collapse'>collapse</a>
					<h3>Widget title 4</h3>
					<a class='remove'>remove</a>
					<a class='edit'>edit</a>
				</div>
				<div class="widget-edit">
					<input type='text' type="text" class="iColorPicker" id='color4' />
					<div class='clearfix'></div>
				</div>
				<div class="widget-content">
					<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse lorem orci, ornare at malesuada a, pulvinar lobortis neque. Integer et felis vel augue suscipit vestibulum id nec nulla.</p>
				</div>
			</div>
		</div>		

		<div class='column' id='right'>
			<div class='widget' id='5'>
				<div class="widget-head">
					<a class='collapse'>collapse</a>
					<h3>Widget title 5</h3>
					<a class='remove'>remove</a>
					<a class='edit'>edit</a>
				</div>
				<div class="widget-edit">
					<input type='text' type="text" class="iColorPicker" id='color5' />
					<div class='clearfix'></div>
				</div>
				<div class="widget-content">
					<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse lorem orci, ornare at malesuada a, pulvinar lobortis neque. Integer et felis vel augue suscipit vestibulum id nec nulla.</p>
				</div>
			</div>
			<div class='widget' id='6'>
				<div class="widget-head">
					<a class='collapse'>collapse</a>
					<h3>Widget title 6</h3>
					<a class='remove'>remove</a>
					<a class='edit'>edit</a>
				</div>
				<div class="widget-edit">
					<input type='text' type="text" class="iColorPicker" id='color6' />
					<div class='clearfix'></div>
				</div>
				<div class="widget-content">
					<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse lorem orci, ornare at malesuada a, pulvinar lobortis neque. Integer et felis vel augue suscipit vestibulum id nec nulla.</p>
				</div>
			</div>
		</div>
	</div>
	<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>	

	<!-- Load the jQuery Libraries -->
	<script src="js/jquery-1.6.2.min.js" type="text/javascript"></script>
	<script src="js/jquery-ui-1.8.16.custom.min.js" type="text/javascript"></script>
	<script type="text/javascript" src="js/iColorPicker.js"></script>
	<script type="text/javascript" src="js/jquery.cookie.js"></script>
	<script type="text/javascript" src="js/jquery.corner.js"></script>
	<!-- script file to add your own JavaScript -->
	<script type="text/javascript" src="js/script.js"></script>	

</body>
</html>

As you can see the HTML is very simple but at the moment it will not look very attractive.  We have each of the widgets in their own Div and in the header we have three images which will be our “buttons” to control each widget.  On the left we have the collapse icon, next we have the edit icon and finally we have the remove icon.  Underneath the header we have an edit panel which will contain in this example some colour selections for the header bar which will be hidden in the css shown below.  So the next task is to now style the page and make it look neater.

CSS

The CSS is fairly simple and will be used for the SharePoint implementation.  We start with a global reset of the page to ensure that all DOM elements are reset.

html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center,dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed,  figure, figcaption, footer, header, hgroup,  menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; font-size: 100%;	font: inherit; vertical-align: baseline; } article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section {	display: block;} body { line-height: 1; } ol, ul { list-style: none; } blockquote, q { quotes: none; } blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; } table { border-collapse: collapse; border-spacing: 0; } 

/** Base Body Styles **/
body{ background:#ddd; color:#000; font:14px Arial, Helvetica, "Trebuchet MS", sans-serif;}

h1,h2,h3,h4,h5,h6{ font-weight:bold; }
h1{ font-size:30px;}
h2{ font-size:24px;}
h3{ font-size:18px;}
h4{ font-size:14px;}
h5{ font-size:12px;}
h6{ font-size:10px;}

a{ text-decoration:none; }
a:active, a:visited { color: #607890; }
a:hover { color: #036; }

.wrapper
{
	clear:both;
}

.column
{
	float:left;
	width:300px;
	margin:10px;
	min-height:100px;
}

.column .widget {
	border:1px solid #ddd;
	margin:5px;
	width:288px;
	float:left;
	background-color:#fff;
}

.widget-head
{
	background-color:#60b51c;
	padding:5px;
	margin:5px 5px 5px 5px;
	height:20px;
}

.widget-head h3
{
	float:left;
    padding: 0 5px;
	color:#fff;
	font-family: verdana, arial;
	font-weight:normal;
	font-size:1em;
}

.widget-head a.remove  {
    float: right;
    display: inline;
    background: url(../images/controls.png) no-repeat -14px 0;
    width: 14px;
    height: 14px;
    margin: 3px 4px 0px 0;
    text-indent: -9999em;
    outline: none;
}  

.widget-head a.edit  {
    float: right;
    display: inline;
    background: url(../images/controls.png) no-repeat -28px 0;
    width: 14px;
    height: 14px;
    margin: 3px 4px 0px 0;
    text-indent: -9999em;
    outline: none;
}  

.widget-head a.collapse  {
    float: left;
    display: inline;
    background: url(../images/controls.png) no-repeat 0;
    width: 14px;
    height: 14px;
    text-indent: -9999em;
    margin: 3px 0 0px 4px;
    outline: none;
}
.widget-edit
{
	background-color:#efefef;
	border:1px solid #ddd;
	padding:5px;
	margin:0px 5px 5px 5px;
	display:none;
}
.widget-edit ul
{

}
.widget-edit ul.colors li {
    width: 20px;
    height: 20px;
    border: 1px solid #EEE;
    float: left;
    display: inline;
    margin: 0 5px 0 0;
    cursor: pointer;
	background-color:red;
}  

.widget-content
{

	padding:5px;
}
.widget-placeholder
{
	border:2px dashed #999;
	margin:5px;
	width:283px;
	float:left;
}

#toolbox-wrapper
{
	clear:both;
	width:900px;
	margin:10px;
}

#toolbox-controls
{
	float:left;
}

#toolbox
{
	background-color:#fff;
	padding:5px;
	width:930px;
	min-height:auto;
	margin:0px;
	margin-top:-1px;
	border:1px solid #ddd;
	float:left;
	position:relative;
}

#toolbox .widget
{
	float:left;
}

#toolbox .widget-content, #toolbox .widget-head .collapse, #toolbox .widget-head .remove, #toolbox .widget-head .edit
{
	display:none;
}

.badge {
	background-image: url('../images/badge.png');
	width:20px;
	height:17px;
	float:right;
	color:#fff;
	padding-top:4px;
	text-align:center;
	font-size:10px;
	font-weight:bold;
	position:absolute;
	z-index:10;
	margin-left:-5px;
	font-family:verdana;
}

.slide {
                margin: 5px 5px 0px 0px;
                background-color:#fff;
                float:left;
                color:#fff;
				border:1px solid #ddd;
				position:relative;
}

.slide a, .slide a:active, .slide a:visited
{
                float:left;
                color:#fff;
                padding: 5px;
				margin:5px;
				background-color:#187069;
}

.reset a, .reset a:active, .reset a:visited
{
                float:left;
                color:#fff;
                padding: 5px;
				margin:5px;
				background-color:#082167;
}

.reset {
				margin: 5px 5px 0px 5px;
                background-color:#fff;
                float:left;
                color:#fff;
				border:1px solid #ddd;
				position:relative;
}

The CSS helps style the page into three even columns and each of the widgets are styled with some buttons and styled headers.

Javascript

To provide the cool functionality, we will need to get the latest jQuery libraries and jQuery plugins. We will also create our own custom javascript file which we will be used to store our script.  The versions that we are using are below with links to download them.

Our own script.js file at this stage will contain only a couple of lines of code to test that jQuery is working;

jQuery(document).ready(function(){
	alert('Jquery is Working');
});

Images

The images for the close and collapse buttons we will use a simple sprite which has a close, max and min symbols on it.

Live Demo

A live demo of the base structure can be found here.

Summary

In this post we have outlined what we will be covering and have managed to get a demo environment working for the next phase.  We will add some jQuery functionality and make our page come alive in the next posts.  I hope this post has been useful and please leave some comments about what you would like to see in future posts.

 

SPLongOperation – Explained & Branded

Processing

Overview

When you are performing out of the box operations in SharePoint 2010 normally in Central Administration you will get a nice loading screen from SharePoint with an animated loading gif.  Have you ever wondered how you can use this in your own SharePoint Solution?  I have been doing SharePoint for a while now and only recently discovered the SPLongOperation method and it is quite the little gem.

SPLongOperation.BeginOperation beginOperation = null;

if (beginOperation == null)
{
    beginOperation = delegate(SPLongOperation longOperation)
    {
        // Long running code here ..

        longOperation.End("settings.aspx", SPRedirectFlags.RelativeToLayoutsPage, HttpContext.Current, null);
    };
}
SPLongOperation.Begin(beginOperation);

I have been writing a site provisioning service recently and have used the above code to help provide the end users some feedback while a new site collection is being created.  Only a couple of lines of code but it transformed the solution.

Want to change the text that is displayed in the loading screen?  No problem.

// BEGIN SPLongOperation
SPLongOperation longoperation = new SPLongOperation(this.Page);
longoperation.LeadingHTML = "<div><h2>This is the main Title</h2></div>";
longoperation.TrailingHTML = "<div><h3>This is the second line of text underneath</h3></div>";
longoperation.Begin();

// long running code here

// END SPLongOperation
longoperation.End(Customers.DefaultViewUrl, Microsoft.SharePoint.Utilities.SPRedirectFlags.Default, this.Context, "");

This will change the text on the page to reflect your specific requirements.

Branding the Loading Page

Do you want to match the loading page to your corporate colour scheme?  No problem.  Below is the output of the page in raw html.

<body onload="setGearPageFocus();gotoNextPage();" class="s4-simple-gearpage">
	<div id="GearPage">
		<div id="s4-simple-card" class="s4-simple-gearpage">
			<div id="s4-simple-card-content">
				<h1>
					<a id="gearsImageLink" href="javascript:;" onclick="hideGears();" title="This animation indicates the operation is in progress. Click to remove this animated image." >
						<img id="gearsImage" alt="This animation indicates the operation is in progress. Click to remove this animated image." src="/_layouts/images/gears_anv4.gif" style="width:24px; height:24px; font-size:0px;" align="absmiddle" />
					</a>
					Processing...
				</h1>
				<div>
					Life In SharePoint Long Operation Demo
				</div>
				<div>
					<br /><br />This is a custom long operation text
				</div>
			</div>
		</div>
	</div>
</body>

As you can see the markup is very simple.  What you can then do is create a new CSS style sheet and inject the reference to it in the LeadingHTML property as the property is not escaped.

longoperation.LeadingHTML = "<link rel='stylesheet' href='/_layouts/LifeInSharePoint/LongOperation.css' type='text/css'  />Life In SharePoint Long Operation Demo";

This will then enable you to customize the page as you need to changing background colours, fonts images etc.  This is the kind of result that you will get.

Do download a working solution with branding included please use the link below.

LISP.LongOperationDemo.zip