26/02/2010

SharePoint Folder Icon Changer JSON MetaData Provider Control

This is a follow up to my original post on How To Change Folder Icons in SharePoint MOSS Using JavaScript.

I'd encourage you to read that post first so you have at least a basic understanding of the JavaScript involved in actually changing the folder icons.

The .Net Custom Control is much simpler. Here is the complete code C# file fully commented:

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using EventPhotoGallery.Data;
using Microsoft.SharePoint;

namespace EventPhotoGallery
{

// Create a sub control object we can use on our ASPX page to define content type to icon mappings
[AspNetHostingPermission(System.Security.Permissions.SecurityAction.Demand,
Level = AspNetHostingPermissionLevel.Minimal)]
public class IconMapping : WebControl
{

public string ContentTypeName { get; set; }

public string IconUrl { get; set; }

public string ThumbnailUrl { get; set; }

public string ToJSON()
{
return string.Format("'{0}': {{ ContentTypeName: '{0}', IconUrl: '{1}', ThumbnailUrl: '{2}' }}",
ContentTypeName.QuoteEscape(), IconUrl.QuoteEscape(), ThumbnailUrl.QuoteEscape());
}

}

// Create The master web control which compiles the ASPX arguments and registers a script
[ParseChildren(true, "IconMappings")]
[ToolboxData("<{0}:FolderContentTypeIconChanger runat=server>")]
public class FolderContentTypeIconChanger : WebControl, INamingContainer
{
// Define a key to register the MetaData JSON object with the .Net Page Rendering process
private const string FOLDERTYPESCRIPTKEY = "FolderContentTypeIconChanger";

// A list of our MetaData Controls defined above that will be subcontrols in ASPX
private List _IconMappings = new List();

public List IconMappings { get { return _IconMappings; } set { _IconMappings = value; } }

public string ListName { get; set; }

public string RootFolder { get; set; }

protected override void OnInit(EventArgs e)
{
base.OnInit(e);
// when the AllItems.aspx page loads the current folder is always in the QueryString so let's grab it
if (!string.IsNullOrEmpty(Page.Request.QueryString["RootFolder"]))
RootFolder = Page.Request.QueryString["RootFolder"];
}

protected override void OnPreRender(EventArgs e)
{
// During prerendering let's build the JSON structure that the _AllItems object consumes.
if (!Page.ClientScript.IsStartupScriptRegistered(GetType(), FOLDERTYPESCRIPTKEY))
Page.ClientScript.RegisterStartupScript(GetType(),
FOLDERTYPESCRIPTKEY, CreateScript(), true);

base.OnPreRender(e);
}

protected override void Render(HtmlTextWriter writer)
{
// Do nothing with the rendering cancel it - we don't need any html with this control the
// JavaScript has already been registered
//base.Render(writer);
}

// The worker horse - does all the work compiling the MetaData
private string CreateScript()
{
SPFolder folder;

// if there is no folder defined then we should use the root folder from the current context list
if (!string.IsNullOrEmpty(RootFolder))
folder = SPContext.Current.Site.RootWeb.GetFolder(RootFolder);
else
folder = SPContext.Current.Web.Lists[ListName].RootFolder;

// Get the SharePoint ContentType for the folder
string folderContentType = folder.Item != null ? (folder.Item.GetString(PhotoData.CONTENTTYPE) != null ?
folder.Item.GetString(PhotoData.CONTENTTYPE) : "Folder") : "Folder";

// Create a StringBuilder into which we will be appending JSON atoms
StringBuilder builder = new StringBuilder("var _FolderContentTypes = {");

// this is a custom data tier object which you're going to have to figure out on
// your own - if there is enough demand I can write another article with a
// comprehensive pattern to support folders in your document libraries
EPGData data = new EPGData();

// get all subfolders and list in json to the UI.
if (folder != null && folder.SubFolders.Count > 0)
{
// iterate all subfolders appending JSON Atoms to the script
foreach(SPFolder subFolder in folder.SubFolders)
builder.AppendLine(CreateFolderReference(subFolder.ServerRelativeUrl,
data.Folder.GetContentTypeName(subFolder), subFolder.Name));
// data.Folder.GetContentTypeName(subFolder) merely returns the name of the content type
builder.Remove(builder.Length - 3, 3); // remove the last comma
}

builder.AppendLine(string.Format("}};\n_FolderContentTypes.RootUrl = '{0}';", SPContext.Current.Site.Url.QuoteEscape()));
// QuoteEscape is an extension method which escapes all single quotes so the JavaScript is compliant

// add the icon mappings to the control
if (IconMappings.Count > 0)
{
builder.AppendLine("var _FolderContentTypeIconMaps = {");
foreach (IconMapping mapping in IconMappings)
{
builder.AppendLine(mapping.ToJSON() + ",");
}
builder.Remove(builder.Length-3,3).AppendLine("};");
}

builder.AppendLine(string.Format("\nvar _CurrentFolder = {{ Name: '{0}', ContentTypeName: '{1}' }};", folder.Name.QuoteEscape(), folderContentType.QuoteEscape()));

return builder.ToString();

}

private string CreateFolderReference(string folderUrl, string contentTypeName, string folderName)
{
return string.Format("'{0}':{{FolderName: '{2}', FolderUrl:'{0}', ContentType: '{1}'}},",
folderUrl.QuoteEscape(), contentTypeName.QuoteEscape(), folderName.QuoteEscape());
}


}

}


So there are a couple things you still have to figure out in your own implementation, but the above is a good starting point.

Now how do you add this to the page you may say - well - first, add the assembly to the GAC.

Then within the appropriate Template Folder find your AllItems.aspx page and add a directive to register the assembly as such:

<%@ Register TagPrefix="my" Namespace="EventPhotoGallery" Assembly="EventPhotoGallery, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9f4da00116c38ec5" %>

Next, somewhere in your ASPX code you can now add the JSON FolderContentTypeIconChanger control as such:

<my:FolderContentTypeIconChanger id="folderTypes" runat="server"
ListName="Event Photo Gallery Photographs">

<my:IconMapping ContentTypeName="Location Folder"
IconUrl="/_layouts/EventPhotoGallery/images/Location_Folder-16x16.png"
ThumbnailUrl="/_layouts/EventPhotoGallery/images/Location_Folder-48x48.png" />

<my:IconMapping ContentTypeName="Event Photo Folder"
IconUrl="/_layouts/EventPhotoGallery/images/Photo_folder-16x16.png"
ThumbnailUrl="/_layouts/EventPhotoGallery/images/Photo_folder-48x48.png" />

<my:IconMapping ContentTypeName="User Upload Folder"
IconUrl="/_layouts/EventPhotoGallery/images/Upload_Folder-16x16.png"
ThumbnailUrl="/_layouts/EventPhotoGallery/images/Upload_Folder-48x48.png" />

</my:FolderContentTypeIconChanger>

I'm sure you can imagine what means what etc. but ya - this should get you going in adding custom icons to your SharePoint Folders.

Let me know if you have any other questions!

Over and Out

3 comments:

Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...

Is this changing the folder icon that appears for a contenttype that inherits from parent SPFolder, when you click new in document library?

Matt Stark said...

No. This changes Sharepoint icons in the List view.

I found in order to change the "New" menu icons that it's better to create a Custom Action with a custom icon & remove the existing button.