04/02/2011

Hack Fix For typeface.js JavaScript Error " 'undefined' is null or not an object"

In IE the following JavaScript error is thrown in circumstances where there is no match for an individual character code in your font glyph map (you know that file you generated for your font):

Error " 'undefined' is null or not an object"
Line: 601 (uncompressed source code).

At time of posting I am using typeface-0.14.js.

The visual result of this error is the sudden disappearance of all headings after the heading which contains the erroneous character on your page. If you inspect your markup, you will notice your missing head tags ALL contain style="visible: hidden" yet have no VML content. This is a result of typeface hiding markup elements before ensuring it can successfully emit the resulting vml syntax & an error.

The Hack fix, which merely seeks to allow typeface to continue processing all "non-corrupt" words on the page (rather than an ugly fail & missing headings) is to modify typeface.js with the following:

1) In the _renderWord method, wrap all calls to: "this.renderGlyph" in a try catch statement.
2) If an Exception occurs return null.

You will find renderGlyph is invoked VIA the renderWord & recursively VIA the renderGlyph method(s). Fortunately for us, the method that calls renderWord (getRenderedText) performs a null check on the renderWord result. If the return from the renderWord method is null, the word will be "ignored" by the portion of the method responsible for creating the VML, and therefore will NOT cause a catastrophic failure in typeface enabling rendering of subsequent words & headings.

So that's the solution. It works. To be honest, I don't like this solution for a bunch of reasons:

1) I don't like try ... catch oriented programming. If there is an error, I want to be told of it so I can treat its root cause rather than at its symptoms. However, circumstances do arise which make it difficult to ensure issues are treated for root cause (EX// CMS Systems which do not impose such limitations on user generated content).

2) The primary reason: This is a 3rd party library for my app. Inevitably, at some point in the SDLC, someone smart is going to take over this project and say: "I can't believe they are using typeface-0.14.js, don't they know it's at version 3 right now". Other than the fact the file is named typeface-0.14-CUSTOM.js & loaded with customization comments, there is no indication to developers that this file has been customized (devs tend to look at code & runtime behavior vs ReadMe files & comments). And you know the rest of the story that starts with: I,E,6; Ends with: "hack fix"; and resounds in a high pitched user saying: "Everything was working fine until your last deployment".

3) A "JavaScript plugin" with such a pervasive behaviors, to a certain degree could be better programmed to isolate and tolerate faults. NO, this is not a contradiction to #1 above, but rather, represents an authors conscious decision to say: "Ok, with respect to what I'm trying to do, I CAN tolerate this fault, but I CANNOT tolerate that fault". In the case of this fix, I have made a conscious decision to say: "It's better if I just fail this one word than risk failing every subsequently unprocessed heading and word on the document".

4) 1#: I am in no way speaking down or pointing a finger at the typespace developers. I think everyone in the community owes them a stand up applause. Seriously. Amazing work & great contribution guys! I have contributed this fix. But in developing a site, and having to decide on how typography would be handled, and being in an " obviously IT" time crunch, I really don't have time for errors like this. More specifically, having written & proofed an opposable dynamic font solution to typeface, when I saw these errors, I immediately sought to rip typeface out of my application. This developer decision directly contradicts mass adoption of these technologies. Let's be honest, with the typeface source code, and a web browser, it's pretty easy to figure out this work around, but for the 90% of users that don't have the skill, it's a three point shot into the garbage bin. In not patching this gap, the door swings open (insert firm kick from behind) for developers to consider alternatives & for this to never truly mature. For example my train of thought: P - "undefined error in typeface (3rd party js lib??)" -- TOT - "another hack library not doing null checks" -- R - "ctrl+f5 on browser" -- NEXT - P "undefined error in typeface" -- TOT - "this is a third party library, is it even reasonable to try to crack the hood?" -- R - "build new matrix of reasons I'm using typeface and not my guaranteed image based solution" -- NEXT -- P "is typeface the right tool for this?" -- TOT "evaluate guaranteed solution vs. unexplored option" -- R "how much time required to fix" -- NEXT -- P "what is the quickest means to an end: risk debugging typeface VS. introducing jQuery convention" -- TOT "I can justify spending 10mins debugging typeface, after that, I'm pulling out and inserting my fontSprite". OUTCOME: typeface is a really well designed script that is easy to diagnose and repair problems in therefore 10 mins spent investing and repairing problem in typeface source was worth the effort. I conclude this is true ONLY because the resolution was simple. And this is my reservation: we use these scripts to gain a level footing on different platforms. When platform comparability issues are prevalent in X-Platform platforms, it's difficult to remain confident about that platform. Aren't the five lines of code required to implement this fix / ensure failure is logged (nice logging BTW) worth the end user inconvenience even if it is merely makeup? The vs. personality of the algorithm/argument is: "Nice headings dude, but, but, but, a word isn't printed because a) it contains illegal characters, b) etc. reasons. Sorry dude, I tried my best." vs. "Rookie Undefined Error dude. LOL - you seriously want me printing mo headings after that BS!". IE sucks, but the point of using typeface is to eliminate this issue. I don't think putting this fix on hold or on me is the way it should be. I get the point.

At EOD, typeface was used and the project was a success. However, it is unrealistic to expect shops to jump into 3rd party JavaScript libraries & build custom fixes. I fully understand the philosophy behind not fixing this problem however, I fail to see how not fixing this problem works in the best interest of typeface or the JS community.

Moreover, and to set the record straight for concerned readers: this problem is really uncommon. In any controlled environment it would be completely avoidable. This is a manifestation of how our lazy CMS (SharePoint 2010) emits character data with hexadecimal encoding (you know those invisible characters with "" values and character codes in the 80k range).

Typeface is there for you on the 99.9% side, my point merely talks to one specific circumstance in what is otherwise a really amazing product.

Over and Out.

Matt




01/06/2010

Need Help Moving Boat June 11th - Cleveland to Welland

I need help moving my C&C 40 Centerboard from Cleveland to Welland.

Please leave your contact information in a comment (will be moderated so nobody will see except me).

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