Cool .NET Tips and Tricks No. 3
By Dr. Dexter Dotnetsky
Printer - Friendly Version
Peter Bromberg

Well, kidzies, them eggheadcafe.com folks done relented and let me back again, to share some cool stuff with y'all! Now the first think I want to share is a cool trick you can use with the Internet Explorer userdata persistence default (that means, "built-in") behavior. Now different browsers "behave" in different ways, and the last time I checked the stats, the overall usage for Internet Explorer out there in the bandwidth world is running over 96.6 percent! Damn! Yessir, dis means y'all can seriously consider writin' code fer IE only, you can tell them "other" guys ta "take a hike", and basically yer finally gonna be on safe ground with all this "Browser compatibility B-S!! (That is, unless the user happens to work fer SUN, Oricle, or one a doze weirdo companies that tell all their people if they ever catchum usin' anything but Nutscrape they will get their "nuts scraped").

OK, now I'm going to get serious:

UserData is a kind of CSS style behavior. There are four or five of them "built in" to IE, but really, this one is the most interesting. Now a guy on this site wrote a couple of articles about this stuff some time ago, and I don't want to beat a dead horse, but I think he may have missed part of the point; let me give you a very big tip:

If you are writing apps that don't use HTTP (in other words, they are designed to come up from the local filesytem in Internet Explorer) then if you plan on using HTTP cookies YOU ARE GONNA BE PLUM OUTTA LUCK!. Cookies don't work off of "FILE:///", period! Oy, vay!

So now that we've recovered from a bad case of Debuggus Interuptus , what we gonna do, call CookieBusters? Nope! We gonna use "UserData", which is way cool better than cookies ANYWAY! It doesn't care whether its HTTP, FILE, GET, PUT or BLOG! It "just works"! Damn! Slicker'n snot on a DOORKNOB!

Now lots of people use this as a single element behavior, and they end up with a whole buncha files gumming up the user's machine in the C:\Documents and Settings\<loggedOnUserName>\Application Data\Microsoft\Internet Explorer\UserData folder, and they make a real mess of the whole thing. Typical novice programmer behavior! However, if you do a little study and remember to remove your foot from your mouth before you go to show off everything you know, you will find that the userData element that you assign this behavior to exposes an xmlDocument property. This document has the root tag "rootstub" already built it, and every time you assign a new attribute to it, it just gets added as a name / value attribute pair. You can't add elements to it, only attributes. Microsoft sez you can stick up to 64K worth of your USELESS JUNK in there PER PAGE, and up to 640 K worth of your useless junk per domain! Man! I smell a cookie factory on steroids here! Take a bath, at least, after you leave!

Now anybody that's had the chance to meet or talk with Dr. Dotnetsky in between hops on railroad cars drinking Absolut Martinis will know that he prefers to illustrate rather than pontificate. So the best way to see how this stuff all works is: 1) look at some sample code, just below and, 2) have a working set of test pages (just below that!). I swear I didn't inhale!

UserData Persistence Behavior Redux [How it works - all on one single HTML page!]:

<HTML>
<HEAD>
<STYLE TYPE="text/css"> .userData {behavior:url(#
default#userdata);}</STYLE>
<script>
window.onload = loadUserData;
window.onunload = saveUserData; 
function showUserData()
{
alert(spnUserData.xmlDocument.xml);
}
function loadUserData()
{ 
 spnUserData.load("userData");
}
function saveUserData()
{
 spnUserData.save("userData");
}
function addUserDataItem(sName, sValue, iExpiryMinutes)
{
    try{
    var oTimeNow = new Date();  
    oTimeNow.setMinutes(oTimeNow.getMinutes() + iExpiryMinutes);
    var sExpirationDate = oTimeNow.toUTCString();
    spnUserData.expires = sExpirationDate; 
    spnUserData.setAttribute( sName,sValue); 
    spnUserData.save("userData");
    }
    catch(e)
    {
    return e;
    }
}

function getUserDataItem(sName)
{
if(spnUserData.getAttribute(sName)==null || spnUserData.getAttribute(sName)=="") 
spnUserData.load("userData"); try{ return spnUserData.getAttribute(sName); } catch(e) { return e; } } function removeUserDataItem(sName) { spnUserData.removeAttribute(sName); } </script> </HEAD> <BODY> <SPAN CLASS="userData" ID="spnUserData" style="display:none;" ></SPAN> <h3>UserData Tester Page One</h3> <input type="text" id="DataName" >Item Name<BR> <input type="text" id="DataValue" >Item Value<BR> <input type="text" id="exp" value="1000000">Expiration(Minutes)<BR> <input type="button" value="add item"
onclick="addUserDataItem(DataName.value, DataValue.value, exp.value);"> <input type="button" value="remove item" onclick="removeUserDataItem(DataName.value);"> <input type="button" value="Show Data" onclick="showUserData();"> <a href="userdatatester2.htm">Page Two</a> </BODY> </HTML>

UserData Persistence Behavior Redux [Try it out, man! You got this far, didn't ya?]:

Page One (it has a link to Page Two, which is essentially identical). View Source to see ALL THE CODE. (OK Posters and Emailers, let me make it a little clearer: RIGHT CLICK Anywhere on the Page and choose the "View Source" context menu option [they complain why didn't we provide the source code for a 100% client side script page - DUH!]).

So now you have a way to store relatively large amounts of data on the client, it doesn't matter whether you brought up the HTML page by double clicking it on the user's file system or as a shortcut because that's where your WISE, Installshield or other installer program deposited your junk, or whether they clicked a link that made an HTTP call. It's gonna work EXACTLY THE SAME! Yay!

Debugging Functions

OK, now lets' switch gears a little and talk about debugging JAVASCRIPT (JSCRIPT, SCRIPT, Javascript1.2, you name it).

I'm not going to explain a lot about this because I took some time to put together a page with a JS include file that really illustrates all the concepts. You have the window.onerror built-in method illustrated (try changing the debugMode variable from true to false, etc to see the action). This is extremely useful for zeroing in on the exact line where an error has occurred, and as you can see it's easy to turn on and off. Don't forget you can also go into IE in the Tools/Internet Options/Advanced and clear the "Disable Script Debugging" checkbox too.

You have the "showSource" method I've put together which iterates the DOM and displays it in a window, which can be invaluable for dynamic HTML assembly (I use it to display the results of XSLT transforms that are dynamically assigned to the <BODY> element or even where you have multiple IFRAMEs and you assign the ENTIRE HTML document to the source of one of the IFRAMEs as a result of a transform). Next, the showSource method automatically exposes a clipboard copy method so you can paste your stuff into EDIT PLUS or your favorite text editor from the clipboard. And finally, the "debugger" Javascript keyword shows how to bring up the JIT Debugger at a specific point in your code. Now I don't know which debugger you use, but Dr. Dotnetsky likes Visual Studio.NET 2003 because it allows you to step through your code with F11, examine the values of locals and watch variables, print (? XXX) values and expressions to the Command Window, and everything else - even in a crummy client-side Javascript page! The only part that doesn't seem to work well is the Intellisense when you mouse hover over a variable and get to see its value.

There aren't too many other tricks for debugging client - side script than these. If you have one, please DON"T HESITATE to post your comments or suggestions on our eggheadcafe.com forums!

<HTML>
<head>
<script language="JScript" src="Debug.js"></script>
<script>
var debugMode=true;
var debugMe=true;
window.onerror=showError;
function showError(Moe,Larry,Curly)
{ 
if (debugMode) alert(Moe +"\n in: " + Larry + "\n at line: " + Curly);
return true;
}
var test="YO!";
function showMe()
{

show.innerHTML=test ;
}
function doDis()
{
if(debugMe)debugger;
show.innerHTML=test ;
}
</script>
</head>
<BODY onload="showMe();">
<table Cellspacing =2 cellpadding="2">
<TR><TD>HELLO</TD><TD>HOWDY</TD></TR>
</TABLE>
<input type="button" value="do it with error" onclick="showSource(ko);">
<input type="button" value="do it right" onclick="showSource();">
<input type="button" value="Debugger" onclick="doDis();">
<div id='show' />
</BODY>
</HTML> 

//save as debug.js//
function showSource(){
var doc_stuff="";
for(i=0; i<document.all.length; i++) 
    {    // NOTE: Next line prevents recursion...
        if (document.all[i].tagName.toUpperCase() !="BODY" && 
                     document.all[i].tagName.toUpperCase() !="HEAD")
            {
            doc_stuff+= document.all(i).outerHTML;
            }
    }
openWindowAndCopy(doc_stuff); 
}
function openWindowAndCopy(strText) {
winBS='toolbar=no,location=no,directories=no,menubar=no,';
winBS+='scrollbars=yes,width=650,height=500';
winBS+=',left=10,top=25';
holyCow=window.open("","",winBS); 
holyCow.document.open();
holyCow.document.write("<scr"+"ipt>function copy(){stuff.focus();stuff.select();
window.clipboardData.setData('Text', stuff.value);}</scr"+"ipt>"); holyCow.document.write("<input type=button value='Copy To Clipboard' onclick='copy();'>
<input type=button value='Close' onclick='javascript:self.close();'><BR>"); holyCow.document.writeln ("<textarea id='stuff' rows='60' cols='75'>"); holyCow.document.write (strText); holyCow.document.writeln ("</textarea>"); holyCow.document.close(); holyCow.focus() ; }"

Dr. Dexter Dotnetsky is the alter-ego of the Eggheadcafe.com forums, where he often pitches in to help answer particularly difficult questions and make snide comments. Dr. Dotnetsky holds no certifications, and does not have a resume. Always the consummate gentleman, Dr. Dotnetsky can be reached at youbetcha@mindless.com.  Dr. Dotnetsky's motto: "If we were all meant to get along, there would be no people who wait for all the groceries to be rung up before starting to look for their damn checkbook."