Showing posts with label JavaScript. Show all posts
Showing posts with label JavaScript. Show all posts

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

14/08/2009

Clear | Remove Query Strings From Post Backs (ASP.Net, SharePoint)

As part of an unorthodox request I was asked to make a TabStrip component SEO friendly. My first thought was to completely rewrite the control to make use of GET requests rather than continuing to POST the data back to the server.

Immediately I ran into a problem with this approach as the developers of this site have implemented the majority of these TabStrip controls inside the ASP.Net UpdatePanel (don't get me started with these) which does not recognize GET requests as Async events.

The architect of this site (actually a SharePoint site) failed to recognize that the SharePoint indexing service would not be able to index information on any tab other than the default one (as subsequent requests for tab data are done using Asynchronous Events). When project management asked him to modify the tabs for accessibility he told them it was impossible without a complete redesign.

As a solution to this problem I modified the TabStrip control such that it renders an anchor tag rather than a link button and added some fancy JavaScript to cancel the Anchor tag click event and trigger the appropriate __doPostBack option. I now have a crawl able anchor tag, which works like a link button, and triggers the appropriate update.

Moreover, when the TabStrip is initialized (OnInit) it looks for a specific query string variable indicating the ID of the selected tab. This is when the real problem starts:
  1. Indexing service crawls the anchor and builds a GET string reference to the selected tab id.
  2. Page is loaded into browser.
  3. User selects different tab.
  4. TabStrip is initialized finding the previous (last selected tab) tab id.
  5. TabStrip raises MenuItem Click event ignoring the actual new selected tab id.
The problem then becomes: How do I Clear The Query String From Subsequent Post Back Operations?

My quest turned to the Google.

First I found this approach which actually removes the query string VIA triggering a post back and is therefore too wasteful to be my solution.

I then found the following approach which unfortunately fails to load as it uses the ugly document.all JavaScript method which isn't available until after page load (therefore causes a client side exception - even when registered properly as a StartupScript).

I put together a slightly different JavaScript routine that looks like this:

if(window.onload != null) {
var wol = window.onload;
window.onload = function(e) {
wol(e);
document.forms[0].action = "Index.aspx";
}
} else {
window.onload = function() {
document.forms[0].action = "Index.aspx";
}
}


This JavaScript Method then rolls nicely into an ASP.Net Server Control like so:
///
/// Attempt to remove the tab id from the query string
///

private void AddRemoveTabIdQueryString()
{
const string key = "REMOVETABIDQUERYSTRING";
if (!Page.ClientScript.IsStartupScriptRegistered(GetType(), key))
{
Page.ClientScript.RegisterStartupScript(this.GetType(), key,
string.Format("if(window.onload != null) {{var wol = window.onload;window.onload = function(e) {{wol(e);{0};}}}} else {{window.onload = function() {{document.forms[0].action = \"{0}\";;}}}}", GetRequestPageName()), true);
}

// Get the page Name of the request
private string GetRequestPageName()
{
string rawUrl = Page.Request.RawUrl;
int pos = rawUrl.LastIndexOf("/");
int qpos = rawUrl.LastIndexOf("?");
return qpos > -1 ? rawUrl.Substring(pos + 1, qpos - pos - 1) :
rawUrl.Substring(pos + 1, rawUrl.Length - pos - 1);
}

I hope this is helpful to others.

Over and Out

06/05/2009

.Net Custom Panel Control with Transparent Background (no png's, css hacks, or problems)

I'm making a really quick post on how you can wrap the jQuery fadeBack control into a .Net Web Custom Control. If you haven't read the article on the fadeBack control, you will have to go here and grab the source.

Save the scripts in a script folder inside your Web Site / Web Application.

To use this control you need to add a reference to the Web Server Control project you have built the code in.

To use this control in your WebForm please Register the Web Server Control dll and namespace on the page like:
<%@ Register Assembly="<<NAME OF WEB SERVER CONTROL PROJECT DLL>>" Namespace="<< THE NAME OF THE NAMESPACE>>" TagPrefix="ctl" %>

Then you can start using the control like so:

<ctl:TransBackPanel ID="backTest" runat="server"
Width="400px" Opacity="20" BackColor="Aquamarine"
BorderColor="Red" BorderWidth="10px" BorderStyle="Solid">
<ctl:TransBackPanel ID="TransBackPanel1" runat="server"
Width="300px" Opacity="20" BackColor="Red"
BorderColor="Green" BorderWidth="10px" BorderStyle="Solid">
<div>Hello World!</div>
</ctl:TransBackPanel>
</ctl:TransBackPanel>

Below is the source code for the .Net Web Custom Control, add this to a Web Server Control project and build:

[ToolboxData("<{0}:TransBackPanel runat=server></{0}:TransBackPanel>")]
public class TransBackPanel : Panel
{

// the jQuery script reference
public const string JQUERY_KEY = "jquery-1.3.2.min.js";
public const string JQUERY_URL = "script/jquery-1.3.2.min.js";

// the transparent background plugin reference
public const string TRANSBACKPANEL_KEY = "jQuery.fadeBack.js";
public const string TRANSBACKPANEL_URL = "script/jQuery.fadeBack.js";

/// <summary>
/// Declare a local variable with the default opacity
/// </summary>
private int _Opacity = 20;

/// <summary>
/// Get / Set opacity to an int - 100 to 0 with 100 being a completely transparent background.
/// </summary>
public int Opacity { get { return _Opacity; } set { _Opacity = value; } }

/// <summary>
/// Override the create child controls method and add the scripts from our constants
/// </summary>
protected override void CreateChildControls()
{

// Register jQuery like so
if (!Page.ClientScript.IsClientScriptIncludeRegistered(JQUERY_KEY))
Page.ClientScript.RegisterClientScriptInclude(JQUERY_KEY, JQUERY_URL);

// Register the fade back external script
if (!Page.ClientScript.IsClientScriptIncludeRegistered(TRANSBACKPANEL_KEY))
Page.ClientScript.RegisterClientScriptInclude(TRANSBACKPANEL_KEY, TRANSBACKPANEL_URL);

base.CreateChildControls();
}

/// <summary>
/// Render the panel control
/// </summary>
/// <param name="writer"></param>
protected override void Render(HtmlTextWriter writer)
{

// Register the startup script to fade the background to another color
if (!Page.ClientScript.IsStartupScriptRegistered(this.ID))
Page.ClientScript.RegisterStartupScript(typeof(string), this.ID, GetOnloadScript(), true);

base.Render(writer);

}

/// <summary>
/// Produce a jQuery on load thingy to geter done.
/// </summary>
/// <returns></returns>
private string GetOnloadScript()
{
return "$(document).ready(function() {\n" +
"$('#" + this.ClientID + "').fadeBack({opacity:" + Convert.ToDouble(Opacity) / 100 + "});\n" +
"});";
}

}

JavaScript: jQuery Plugin to fade Background but not Content: no css hacks, no *.png files, no problems ...

UPDATE: the toNum method must be changed to this:

function toNum(strNum) {
if (strNum && strNum != "") {
var i = parseFloat(strNum);
if (i.toString() == "NaN")
return 0;
else
return i;
}
return 0;
};


I scoured the web in search of a jQuery plugin that allows me to fade the background but not the contents of a div. I found this article: Cross Browser Transparent Background with jQuery, no css hacks, no *.png files by Mihaistancu and noted that there were a lot of problems with the plugin. It didn't work for me -> but I'm sure it's great code once I figure out the particulars.

I decided to write my own plugin that can take some of these issues into account. The below jQuery plugin has been tested in IE7, Firefox, Opera and Safari and it works.

It takes padding and borders into account so your padding will be enforced and so will your background borders (background can have borders!!). Infact, anything that is not a child element will be applied to the background fade.

How it works:
1) Get the dimensions of TheDiv we want to fade (top, left, height, width - if these are not set in css it will default to the default style for that html element type).
2) Get the inner html of TheDiv and add it to a NewDiv (with id: TheDiv.id + "_content"), and append that NewDiv to TheDiv's parent.
3) Fade TheDiv out ...

Give it a shot and let me know what you think.

You can call the plugin like so:

$(document).ready(function() {
$("#outer").fadeBack();
});

You can download a zip file example /plugin from my Windows Live Space here.

You can see the raw plugin source code below:

(function($) {


$.fn.fadeBack = function(options) {

var _s = $.extend($.fn.fadeBack.defaultOptions, options);

return this.each(function() {

var t = $(this);
var oh = t.height();
var ow = t.width();
var ih = t.html();
var p = t.parent();
var tl = t.position();
var id = t[0].id + "_content";

t.css({ height: oh, width: ow }).html("").fadeTo("fast", _s.opacity);

var padding = dimSum(getPadding(t), getBorderSizes(t));

var ncss = { top: tl.top, left: tl.left,
position: "absolute", height: oh, width: ow,
paddingTop: padding.top, paddingBottom: padding.bottom,
paddingLeft: padding.left, paddingRight: padding.right };

$("
" + ih + "
").css(ncss).appendTo(p);

});

function dimSum(dim1, dim2) {
return { top: dim1.top + dim2.top,
bottom: dim1.bottom + dim2.bottom,
left: dim1.left + dim2.top,
right: dim1.right + dim2.right
}
};

function getBorderSizes(sel) {
return {
top: toNum(sel.css("borderTopWidth")),
bottom: toNum(sel.css("borderBottomWidth")),
left: toNum(sel.css("borderLeftWidth")),
right: toNum(sel.css("borderRightWidth"))
};
};

function getPadding(sel) {
return { top: toNum(sel.css("paddingTop")),
bottom: toNum(sel.css("paddingTop")),
left: toNum(sel.css("paddingLeft")),
right: toNum(sel.css("paddingRight"))
};
};

function toNum(strNum) {
if (strNum && strNum != "") {
var i = parseFloat(strNum);
if (i.toString() == "NaN")
return 0;
else
return i;
}
return 0;
};

}

})(jQuery);

$.fn.fadeBack.defaultOptions = { opacity: 0.4 }

Over and Out

01/05/2009

JavaScript: Chaining Cancelable Events - If you're not first, you're last

Another quick post on cancelable events in JavaScript. An example of a cancelable event is the keydown method. If I return false from a keydown method JavaScript will not allow the action of that keydown event to proceed. For example, I could return false on a document keydown event to cancel the ctrl+c behaviour etc. etc.

The following is a nice method I've built and tested only in IE 7 which allows users to chain JavaScript events together.

Obviously this could be compressed significantly.

addToObject: the HtmlElement we want to attach the cancelable event to.

eventName: the name of the event we want to trap.

methodDelegate: a JavaScript function delegate / closure for the JavaScript method to attach to the event.

order: First or Last. You can build on this however you want.

Enjoy,

function AddCancelableEvent(addToObject, eventName, methodDelegate, order) {
if (addToObject[eventName] != null) {
var oe = addToObject[eventName];
if (order == "first")
addToObject[eventName] = function(e) {
var r = methodDelegate(e);
if (r == false) return false;
return oe(e);
};
else // "if you're not first you're last" - Ricky Bobby
addToObject[eventName] = function(e) {
var r = oe(e);
if (r == false) return false;
return methodDelegate(e);
};

} else {
addToObject[eventName] = methodDelegate;
}
};


Over And Out

30/04/2009

JavaScript: Overloading Window onload

Quick post on a very common methodology I've been using to chain events to the window.onload event in pure JavaScript:

if (window.onload == null) {
window.onload = loadAutoSubmitScript;
} else {
var ol = window.onload;
window.onload = function() { ol(); loadProc(); }
}

function loadProc() { alert("The window is loaded"); }

This should work for pretty much every page you use.

Over and Out

07/01/2009

JavaScript: Cancel Event Bubbling

Another common tidbit of JavaScript code:

Cancel event bubbling:

function stopEvent(evt){
evt = evt || window.event;
if (evt.stopPropagation){
evt.stopPropagation();
evt.preventDefault();
}else if(typeof evt.cancelBubble != "undefined"){
evt.cancelBubble = true;
evt.returnValue = false;
}
return false;
}

JavaScript: Create Open Below Pop Ups (Ex// auto complete text box)

This is a simple method to open popups below text inputs (or any Html Element at that) using JavaScript. Please let me know if things are missing ... The intent of this script is to open a small pop up below a text box for use in a custom auto complete. As this is an ASP.Net control, and ASP.Net creates element ID's based on containers we need something to base our element id's on so we can do stuff with them. In this example I use a text box as the naming pivot upon which all other client ID's are based on.

// the previous document click event handler we've stored when we change the event.
// This implies when the pop up is open you'll need to cancel all click events therein so the popup doesn't close
var orignDocClick = null;
// the active pop up id. In another method I haven't provided here we determine if there is an active pop up and close it before we open a second.
var activePopId = null;

// the id of this element is provided VIA ASP.Net control but in this example is hard coded
// we want to show a Pop Under directly below this text box.
var inputTypeText = document.getElementById("ourTextBox");

// create the pop under by calling show pop.
var myNewPopUnder = ShowPop(inputTypeText.id + "_PopUp", inputTypeText);

myNewPopUnder.innerHTML = "Hello World";
// OR
myNewPopUnder.appendChild(document.createElement("br"));
// OR ....


function ShowPop(strPopUpId, objTextBox) {
// get the existing div control and remove it (it is merely a placeholder with ID's aligned as per ASP.Net Crap)
var popEl = document.getElementById(strPopUpId);
var parent = popEl.parentNode;
parent.removeChild(popEl);

// create a new pop up control and append it to the parent of the old auto complete pop up.
var newPop = document.createElement("div");
newPop.id = strPopUpId;
newPop.className = "AutoCompletePop";
parent.appendChild(newPop);

// determine the left and top positions for the pop under
var curleft = 0;
var curtop = parseFloat(objTextBox.offsetHeight);
var top = objTextBox;
do {
curleft += top.offsetLeft;
curtop += top.offsetTop;
} while (top = top.offsetParent);
newPop.style.top = curtop + "px";
newPop.style.left = curleft + "px";
newPop.style.display = "block";

// trap some document events to ensure we can close auto complete
orignDocClick = document.body.onclick;
document.body.onclick = DocHidePop;
activePopId = strPopUpId;

return newPop;
};

function DocHidePop() {
if (activePopId != null) {
HidePop(document.getElementById(activePopId));
}
};

function HidePop(objPopContainer) {
objPopContainer.style.display = "none";
document.body.onclick = orignDocClick;
activePopId = null;
};

JavaScript: Event Target

A simple way to get the element that starts an event. I'm using this to ascertain which text box is calling the KeyPress event in pure JavaScript:

function KeyPress(event) {
var srcElement = getElement(event);
alert(srcElement.id);
}

function getElement(evt) {
if (window.event)
return window.event.srcElement;
else
return evt.currentTarget;
};

Another variation of this script could be written as such (note, this get's the object that triggers the event):

function getTargetElement(evt){
if (window.event)
return window.event.srcElement;
else
return evt.target;
}

Over and Out

JavaScript: Sorting Complex Arrays

I wasn't aware that JavaScript text sorting was case sensitive (well on ie7 I've verified it is). So, Here is a simple example of sorting an Object Array taking into account case sensitivity.

for (var i in searchList) {
var indexOfTerm = WordStartsWith(searchList[i].Text, searchPhrase);
if (indexOfTerm > -1) {
results[results.length] = { Item: searchList[i],
IndexOf: indexOfTerm,
toString: function() { return this.Item.Text.toUpperCase(); }
};
}
}

results.sort();

By overloading the toString method we can convert our sort key to upper case and boom sorting works properly.

P.S.,
WordStartsWith is a method that returns the index of a search term in a text string.
searchList is a JSON structure with a bunch of data for use by a custom auto complete.

Each item in the results array is an object thus I've overridden the toString method to return an upper case copy of the text item for use in sorting (The JavaScript array calls toString to get sortable data).

Over and Out

28/01/2008

Long Break, Run over by Cube Van, and JavaScript

Sorry for the delay in my posts. It's been too long and I apologize.

Amongst other things, I managed to get run over by a Cube Van on my way into the office (didn't break a bone!!), changed consulting firms, and am once again enjoying life.

Now that I'm back at it I wanted to post a link to the Art and Science of Javascript it is a great book that actually talks to real world JavaScript examples. Isn't it about time something like this came out.

Please Give It a Read, and if you find an eBook version, please leave a comment in this blog posting.

Over And Out

11/10/2007

What is the best Offline JavaScript Library?

Dudes and Dudettes,

I'm writing to ask which JavaScript library is best suited to bring a web application Offline.

Here are the limitations of my question:

1) I don't want a library that objectifies my JavaScript objects (EX// I don't want to use prototype).

2) I want to use a library that is web standards compliant (EX// I don't want to use Flash, Apollo).

3) I want to use a library that is abstracted from server architecture (EX// I'd prefer to develop a 100% JavaScript interface that merely calls XML services on the server for data).

To my knowledge there are a couple options on the market and I guess I'm asking for your input:

1) Google Gears
2) Dojo Offline Toolkit
3) ???

Let me know your opinions!

Over and Out