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

Change Folder Icons In MOSS / SharePoint Using JavaScript

I hope you guys find this code useful in changing default folder icons using JavaScript.

Before that - I want to point out that many people are of the opinion that Folders should be avoided at all costs in SharePoint. While I appreciate the perspective there, I believe it merely shows there is a gap in the technology platform.

I believe that once that gap is filled (EX// through CustomActions, EventReciever Methods) they actually become a pretty valuable resource in allowing old school business users to transition from windows based folder system - to a web based folder system - which opens them up to the more modern ways of locating resources VIA search / taxanomy. Moreover, how many SharePoint business developers out there haven't had a client ask for Folders?

OK - to the JavaScript. I want to start off by pointing out that this entire methodolgy will most likely be ineffective if you've customized the List Views (if the HTML structure is different the JavaScript below will not be able to find the correct HTML elements to change).

Before you start - you're going to need to grab a copy of this cross browser getElementsByClassName method and add it to your script (It's tha bomb if your architecture prohibits the use of jQuery :().

First, I create a couple JSON objects to hold Meta Data about my ContentTypes / Folders (I actually use a .Net Custom Control to auto generate this dynamically when the View loads - check here for that code):

// This data structure contains information about the folders at this particular level
// in the folder hierarchy within the current AllItems view
var _FolderContentTypes = {

// Key the list By Folder URL as we'll use it as a Lookup later
'/Lists/EventPhotoGalleryPhotographs/User Uploads':

{
// All metadata related to above folder
FolderName: 'User Uploads',
FolderUrl:'/Lists/EventPhotoGalleryPhotographs/User Uploads',
ContentType: 'User Upload Folder'

},

// next folder
'/Lists/EventPhotoGalleryPhotographs/Forms':

{
// more metadata
FolderName: 'Forms',
FolderUrl:'/Lists/EventPhotoGalleryPhotographs/Forms',
ContentType: 'Folder'

}

};

// next we'll setup the root url to use as a reference
_FolderContentTypes.RootUrl = 'http://mtw-sharepoint';

// The second data structure we create maps ContentTypes to Icon Urls as such
var _FolderContentTypeIconMaps = {

// The SharePoint ContentType 'Location Folder'
'Location Folder':
{
ContentTypeName: 'Location Folder',
// icon to use in details view
IconUrl: '/_layouts/EventPhotoGallery/images/Location_Folder-16x16.png',
// icon to use in thumbnails view
ThumbnailUrl: '/_layouts/EventPhotoGallery/images/Location_Folder-48x48.png'
},

// Another SharePoint Folder ContentType
'Event Photo Folder':
{

// more meta data
ContentTypeName: 'Event Photo Folder',
IconUrl: '/_layouts/EventPhotoGallery/images/Photo_folder-16x16.png',
ThumbnailUrl: '/_layouts/EventPhotoGallery/images/Photo_folder-48x48.png'

},

// Another SharePoint ContentType etc...
'User Upload Folder': {

ContentTypeName: 'User Upload Folder',
IconUrl: '/_layouts/EventPhotoGallery/images/Upload_Folder-16x16.png',
ThumbnailUrl: '/_layouts/EventPhotoGallery/images/Upload_Folder-48x48.png'

}

};


Next I'll create a worker object I can use in my _AllItems view that actually does the dirty work. This can be saved in a JavaScript file / cached in the client browser:

var _AllItems = {

// create the initialization method which will execute when the window has loaded
Initialize: function() {

// This section sets the small icons when in details view
var folders = getElementsByClassName("ms-vb-icon");

// if there are no folders then don't try changing icons
if (folders.length > 0) { // this method works when we are in details view

// define a constant to recognize if we are in the root folder
var rf = "RootFolder=";

// loop through each HTMLElement that was returned with the ms-vb-icon class selector
for (var intCnt = 0; intCnt < folders.length; intCnt++) {

// perform some string parsing in order to figure out the URL of the folder as

// this is what we use as our Key in determining which Icon to apply to the folder var startPos = folders[intCnt].firstChild.search.indexOf(rf) + rf.length;
var endPos = folders[intCnt].firstChild.search.indexOf("&", folders[intCnt].firstChild.search.indexOf(rf) + rf.length + 1);

// get the folder URL and replace all SharePoint Url Hexidecimal's to get a pure Url reference var listUrl = folders[intCnt].firstChild.search.substr(startPos, endPos - startPos);
listUrl = listUrl.replace(/%2f/g, "/").replace(/%20/g, " ");

// double check to make sure this particular folders' URL exists in our _FolderContentTypes
// meta data object
if (_FolderContentTypes[listUrl] != null) {
// double check that our _FolderContentTypeIconMaps data structure contains the ContentType
// for which we want to apply a custom icon to
if (_FolderContentTypeIconMaps[_FolderContentTypes[listUrl].ContentType] != null) {

// now actually change the icon url to the new URL we have specified in our iconmap folders[intCnt].firstChild.firstChild.src = _FolderContentTypes.RootUrl + _FolderContentTypeIconMaps[_FolderContentTypes[listUrl].ContentType].IconUrl; }

}

}

}

// The following section repeats the above methodology for the _AllItems
// thumbnail view using larger custom icons folders = getElementsByClassName("thumbnail"); if (folders.length > 0) {

// this method works when we are in thumbnail view
for (var intCnt = 0; intCnt < folders.length; intCnt++) {

// you can see just how sketchy this method is - I surround it in a try catch blatantly
// because I'm too lazy to code the massive statement required to make sure each
// element exists programatically try { var imgObj = folders[intCnt].firstChild.firstChild
.firstChild.firstChild.firstChild.firstChild
.firstChild.firstChild.firstChild.firstChild; } catch (ex) { }

// The difference here is we'll see every thumbnail icon on the page so we have // to double check the icon is actually the default SharePoint folder icon
if (imgObj != null && imgObj.src.indexOf("fldrnew.gif") > -1) {
var folder =
this.GetFolderByName(folders[intCnt].firstChild.firstChild.children[1].firstChild.innerText);

// Again, ensure the icon map contains an icon for this folders content type
if (_FolderContentTypeIconMaps[folder.ContentType] != null) {

// Actually change the thumbnail icon source
imgObj.src = _FolderContentTypes.RootUrl + _FolderContentTypeIconMaps[folder.ContentType].ThumbnailUrl;

// The icons I used were a specific constant size so I adjusted the style props as required
imgObj.style.width = "auto";
imgObj.style.height = "48px";
imgObj.title = folder.ContentType;

}

}

imgObj = null; // Just kill the obj ref

}

}

},

// This method gets the real folder name
GetFolderByName: function(folderName) {
if (folderName != null) {
folderName = folderName.replace(/^\s\s*/, '').replace(/\s\s*$/, '')
for (var f in _FolderContentTypes) {
if (_FolderContentTypes[f].FolderName == folderName)
return _FolderContentTypes[f];
}
}
},
// This is the startup method that should be called when the window loads
StartUp: function() {
var ol = window.onload;
if (ol != null) {
window.onload = function(e) {
ol(e);
_AllItems.Initialize();
}
} else {
window.onload = function() { _AllItemsInit.ialize(); }
}
}
};

// Wire up object initialization
_AllItems.StartUp();

And that's about it. When the window has completed loading _AllItems.Initialize() is called which changes the src property on all SharePoint icons. With the metadata provided the above example changes one folders' icons - the "User Upload" ContentType folder.

Obviously the above could be greatly simplified using jQuery - you could probably do it in about 15 lines (I'd encourage someone to do that and flip a trackback below).

Blah Blah Blah - The .Net Control which produces the JSON metadata

20/02/2010

JavaScript / C# Hack to For Latency Simulation In ASP.Net Web Applications

When developing AJAX functionality sometimes a latency simulation can be quite revealing. The following four lines of code serve as an interception point injecting a user specified network delay in serving network resources for UI testing purposes.

If you use your imagination a bit, and leverage the power of jQuery selectors, one can quickly build a network latency injector for help in developing those tough to test RIA interfaces. I hope you enjoy:

1) Create a new WebForm Called ImageLatencySimulator.aspx in the root of your web application.

2) Within the .aspx file delete all the html markup so only the @Page level declaration is visible (optional).

3) In the Page Load event of the .aspx.cs(.vb) file add the following code (C#, message me for VB):

int delay = Convert.ToInt32( Request.QueryString["delay"] );
string redirectTo = Request.QueryString["redirect"];
System.Threading.Thread.Sleep(delay);
Response.Redirect(Page.ResolveUrl(redirectTo));
// quite simple really isn't it? ya ya ya parse the int properly and perform appropriate null checks ... play your little violin for me before commenting ...

4) Add an image to the images directory within the root of your web application named .gif;

5) Add another WebForm or Html File to your project in the same level in the hierarchy as ImageLatencySimulator.aspx called LatencyTest.aspx|html.

5) Within the body tag in the LatencyTest.html file add an image tag with the src attribute pointing to ImageLatencySimulator.aspx with the appropriate get params as such:

.gif" alt="latency example redirect" />

6) Run the project.

You should notice all requests for your image resource are now being redirected through the ImageLatencySimulator.aspx and therefore are subject to your user defined delay parameter of 5000 miliseconds. Your waterfall diagram is now visibly apparent as the page loads.

OK - so why is this cool at all you may ask? We'll, imagine I add a #if declaration to my .Net assembly which registers a piece of JavaScript on my page when running in debug mode that does the following (using jQuery):

$(document).ready(function() {
$("img").each(function() {
$(this).attr("src") = "ImageLatencySimulator.aspx?delay=500&redirectTo=" + $(this).attr("src");
});
});
// coded the above in WYSIWYG editor so message me if it doesn't work.

If you can read jQuery code then it should be obvious - the previous code injects a network delay against every image on your entire web page simulating network latency in any and every web browser!! Now go add an eTag (PageOutput Cache in ASP.Net) to your ImageLatencySimulatory.aspx file and test out your cache layer. Just add a random parameter at the end of the GET string to invalidate the cache and you have an extremely simple network latency simulator for use in development.

I'd encourage you to get creative with the jQuery part of this algorithm, add the appropriate type checks to the C# component - see what else you can simulate latency for - use templates for urls - I personally find this technique invaluably simple - simple to implement, simple to expalin, simple to advocate for - and in the spirit of KISS, that makes me warm and fuzzy.

Over and Out

18/02/2010

AOP: Aspect Oriented Programming

AOP goes like this: If it's not part of your core logic it's an aspect and should in some way be separated from your core algorithm.

In a more literal sense, Aspects are application infrastructure components, as a foundation is to a house, Aspects are to a software application. Now how does this differ from OOP?

The intention of Aspect Oriented Programming is two fold:
  1. Separation of Concern: the separation and centralization of application logic in a non redundant fashion.

  2. Cross Cutting: entanglement of application logic within a program resulting in scattering tangling or both.
"For instance, if writing an application for handling medical records, the bookkeeping and indexing of such records is a core concern, while logging a history of changes to the record database or user database, or an authentication system, would be cross-cutting concerns since they touch more parts of the program." - Wikipedia

Some of you may argue AOP is no more than another level of abstraction in OOP, and you'd be correct in saying that. I personally find the theory interesting, but the practice a bit defeatist. As a .Net developer, I'm sure I'd prefer using the built in .Net Roles & Members over AOP, but, heck, if I can make my code more readable I'm up for giving it a shot.

Here are some interesting articles which talk to AOP, and a simple AspectF implementation (which isn't real AOP) by the creator of pageflakes.com:

Aspect Oriented Programming Wikipedia

AspectF Fluent Way to Add Aspects for Cleaner Maintainable Code

AspectF Simple way to cache objects and collections for greater performance and scalability

What do you think about AOP?

Over and Out