Thursday, March 12, 2009

Registering CSS and Javascript files within Sharepoint WebParts

While working on my latest Sharepoint project, Ben Padgett and I discovered that we needed an easy and reusable way to import custom stylesheets and javascript files for individual web parts. We wrote the following two static methods, which use built in ASP.Net and Sharepoint functionality to register stylesheets and javascript files within a webpart.

By default, Sharepoint master pages have a ContentPlaceHolder named “PlaceHolderAdditionalPageHead”. This ContentPlaceHolder is where you can register your own additional stylesheets. Sharepoint goes a step further and also includes a CssRegistration control for registering css files. We set the name of the CssRegistration control to the path where our style sheet is located Below is the code for registering a stylesheet:

   1: public static void RegisterCSS(string cssPath, WebPart webpart)
   2: {           
   3:     ContentPlaceHolder _Header = (ContentPlaceHolder)webpart.Page.Master.FindControl("PlaceHolderAdditionalPageHead");
   4:     if (_Header != null)
   5:     {
   6:         CssRegistration cssControls = new CssRegistration();
   7:         cssControls.Name = cssPath;
   8:         _Header.Controls.Add(cssControls);
   9:     }
  10: }

Our method for importing javascript files takes advantage of the ASP.Net ClientScriptManager. Similar to the RegisterCss method, the two required parameters are the relative path to the javascript file and the webpart which registers the javascript file. Below is the code for registering a custom javascript file:

   1: public static void RegisterScript(string scriptPath, WebPart webpart)
   2: {
   3:     string IncludeScriptFormat = @"";
   5:     if (!webpart.Page.ClientScript.IsClientScriptBlockRegistered(scriptPath))
   6:     {
   7:         string includescript = String.Format(IncludeScriptFormat, "javascript", scriptPath);
   8:         webpart.Page.ClientScript.RegisterClientScriptBlock(webpart.GetType(), scriptPath, includescript);
   9:     }
  10: }

These methods should be called within OnInit or OnPreRender so that the stylesheet and or javascript file is included within the page head prior to loading. In order to register a style sheet located in the Layouts hive directory, you just need to pass the relative path to the style sheet file as well as the WebPart that uses the style sheet. Below is an example OnInit method within a WebPart that makes use of our static functions.

   1: protected override void OnInit(EventArgs e)
   2: {
   3:         MySharepointLibrary.RegisterCSS("/_layouts/css/MyStyleSheet.css", this);
   5:         MySharepointLibrary.RegisterScript("/_layouts/js/MyJavascript.js", this);
   7:         base.OnInit(e);
   8: }

Overall, these methods ultimately help create organization for stylesheets and javascript files, by allowing individual WebParts to import their own styles and scripts.

Feel free to leave any questions or concerns in the comments.


Cross-browser drag and drop “ListBoxes” using jQuery.

My latest project involved creating a public facing website on top of Microsoft Sharepoint. For one of the custom administrative pieces, I determined that drag and droppable lists provided the best solution for managing collections of objects. However, I ran into several problems while trying to implement this using jQuery with support for all browsers.

The ideal option was to use an ASP.Net ListBox. This control is rendered as a select html element, with all of the ListItems rendered as html option elements. Unfortunately, I quickly learned that this was a limiting factor in what I was trying to accomplish. It is not possible to bind events to option elements in any browser except Firefox. Therefore the following jQuery statement does not work in most browsers:


After discovering this limitation, I decided to use ASP.Net BulletedList controls. These controls are rendered as unordered list elements (<ul>), with each ListItem rendered as an html listitem element (<li>). Internet Explorer, Chrome, Safari and Firefox all support binding events to ListItem elements. Therefore, the following statement was possible in all browsers:


Now that I had discovered how to get jQuery’s drag and drop working for lists, my next goal was to create a decent looking user interface. I needed to somehow get my unordered lists to look similar to a standard ListBox.

After some googling, I stumbled upon a great jQuery plugin, jScrollPane (not to be confused with a Java jScrollPane!). jScrollPane allows you to convert div containers to fully customizable scrollable boxes. Implementing jScrollPane is incredible easy as well. All that is required is calling .jScrollPane() on the div element you want to convert.

Now that I had created a “ListBox” with draggable items, the final step was implementing droppable for my jScrollPane’s. My layout consisted of a jScrollPaneDiv containing a SelectedItemsDiv. Within the SelectedItemsDiv, there was a <ul> with the id of SelectedItemsList. With this setup, the following jQuery snippet created a droppable container within my jScrollPane.

   1: $("#SelectedItemsDiv").droppable({ 
   2:     accept: 'li', 
   3:     hoverClass: 'ui-state-hover',
   4:     drop: function(event, ui) {    
   5:         (ui.draggable).clone().appendTo("#SelectedItemsList");
   6:     } 
   7: });    
I hope this helps some of the issues and requirements for implementing drag and droppable lists using jQuery. If anyone has any questions or would like to get a more complete example, feel free to leave a comment!