26/02/2010

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

No comments: