Skip to main content

Adventures in JScript: Episode Two - While You Were Looping

An Introduction:

Last time we saw how you can imbed XML in the body of an HTML document in IE's <XML>tag, known as a data island. My demonstration of this spectacular feat was having an alert pop up with the XML inside it. While visually stunning, it's not exactly useful for solving problems, and that's what we're all in this game for. So let's turn this XML into something useful. Upon hearing a phrase like that most people think: XSLT. Not me. I go straight to JScript, not passing GO, not collecting $200. Let me explain myself: I'm not opposed to XSL, I think that it's pretty cool. When it first came out, I knew I needed to learn it and worked at it hard. And that's when it came to me, I had to "work" at it. I'm one of these rare mutant humans that think in code, yet XSL has always seemed a little alien to me. So I set out a way to do everything that I could do in XSL, but do it in my favorite syntax, JScript. I've done this pretty successfully in other apps but in the Monster I took it to a new level. The Monster is this huge multi-tabbed thing, with tabs nested in tabs, menus, button and status bar, even a tree view on the left, with no XSL, but tons of XML. It also has no server side ASP, but that's for another day. I've come up with a pretty simple design pattern, so let's dive in.

The Ubiquitous While Statement:

For our first example, we'll just build a table to display the data in. So our data will be a list of names:

<row>
<field name="txtFirstName" value="Mike" />
<field name="txtLastName" value="Shaffer" />
</row>

and so on. Some people have commented that I seem to use a lot of attributes and I'll admit, I do. I was in a text node phase for a while but I've settled on attributes as a rule. Personal choice. I'll need a way to loop through each of the rows, grabbing the value attribute and slamming it into the <td>. We'll also need some sort of logic to put the first name and the last name into the right spots...but first the looping. Any looping strategy needs to be generic so that the loop doesn't need to know in advance how many times it needs to loop. In ADO, you have a null test as you loop and in XML you have the same. To set it up, we first need get a special XML object called a Node Set. Surprisingly, a Node Set is a set of nodes... We do this using the .selectNodes method of the MSXML DOM object. To get a set of nodes you first need to set up an XPath. XPath? Node Sets? You thought this was JScript, not XML, right? To use XML effectively in any language, including XSL, you gotta know these things. I'll spend more time on XPath in a later episode, so this will be a "trust me, this works" type of intro. An XPath that would set a pointer to any of my <row> nodes would be "//row", and to get to the <fields> inside them, I use "//row/field". A purist might point out that "//field" would do the same thing, and they'd be right. The beauty of XPath is that there's usually a couple of ways to do it. The complete JScript statement is:

var oNodes = theIslandXML.selectNodes("//row/field"); 

where theIslandXML is our Data Island (check the id attribute on the <XML>). .selectNodes is a method of the MSXML object that returns a Node Set assuming valid XPath has been provided. Once we have a Node Set, we need to be able to spin through it, and it's very easy:

while((oNode = oNodes.nextNode()) != null) 

This leverages a method off of the Node Set object .nextNode. .nextNode starts with the first node in the Node Set and loops until it is at the end when it gives you a nice friendly null. My good pal Ray Wilson hooked me up with this idea, because, honestly, I'm not a big while user and can count how many times I've assigned inside a statement on 1 hand...but this statement is literally everywhere inside the Monster. Truly ubiquitous. Once we're looping, then what? We now have a Node object, oNode, that contains a single <field> node from the XML. Let's do a little more work. I'm basically building a string that when complete will be stuffed into the HTML of the page. I call my string sString, clever eh? Inside my while loop I want to test if the node we're on is a First Name, if so I want to start the <tr>, if it's a Last Name, I'll finish the </tr>. To look at the attribute values, I use a method of the Node object, .getAttribute. .getAttribute returns the value of the attribute you specify as a parameter, like this: sFirstName = oNode.getAttribute("value"). So my whole inside of my loop looks like this:

if(oNode.getAttribute("name") == "txtFirstName")
{
sString += "<tr><td class='First'>" + oNode.getAttribute("value") + "</td>";
}
if(oNode.getAttribute("name") == "txtLastName")
{
sString += "<td class='Last'>" + oNode.getAttribute("value") + "</td></tr>";
}

Now because of the way the XML is laid out we can stuff sString into a div and a table appears:

TheDoc.getElementById("divHolder").innerHTML = "<table>" + sHeader + sString + "</table>"; 

You can see this in action in Example 1.

So by simply tearing through your XML with a function like this, you can create entire pages on the fly, derived from XML without XSL and without help from the server to create the HTML. The XSL camp will point out that XSL can be used on the client to do this faster. My experience is that except for very, very large XML documents, JScript performs the same as a client side transform. The reason I like using JScript to render HTML from XML is that I can develop it fast, in the same environment that the rest of my code is in (don't have to switch to XSL world) and I can easily make multiple passes over the same HTML with different XML documents. For example, the Monster has one XML island that contains the data, like we have above. Another island contains formatting information and type information like numeric, string length, things like that. A third contains required meta data (is this field required?) and yet a fourth overrides most of this by set the field to read only or read/write. Someone really clever could come up with a series of XSLT's that could do this, but staying in JScript was the easier path to develop (usually the best choice, since it typically speeds up the development) and most importantly, the performance is more than adequate. I can rip through the "binding" of the data islands to the fields on my interface in less than 1 second...I've built a timer/logger class for the client that keeps track of these things. You can nest the while loops, for instance looping on the <row> nodes then inner looping on the <field> nodes. That would be the more appropriate way to bind these nodes like this:

var oOuterNodes = TheIslandXML.selectNodes("//row");
var oOuterNode;
while((oOuterNode = oOuterNodes.nextNode()) != null)
{
sString += "<tr>";
var oInnerNodes = oOuterNode.selectNodes("field");
var oInnerNode;
while ((oInnerNode = oInnerNodes.nextNode())!=null)
{
switch (oInnerNode.getAttribute("name"))
{
case "txtFirstName":
sString += "<td class='First'>"
break;
case "txtLastName":
sString += "<td class='Last'>";
break;
}
sString += oInnerNode.getAttribute("value") + "</td>";
}
sString += "</tr>";
}

So now with 2 nested while's, JScript will allow us to assign a different CSS class to the <td>'s that contain the First Name from the Last. Since we're using a switch, we can add a default and as many cases as we need. Very flexible & and also very fast. Take a look at Example 2

This produces a nice read only interface, but what about a real data entry form with text boxes? No problem I say. Heres the plan, initially it's the same as we've already done; build the HTML from the XML. For this example I've added <input type="text"> text boxes. I'll use JScript to number each of them, to create a unique id attribute. I've modified the XML slightly too. For this example, I'm assuming that the XML serializer can add a sequential number to the end of each field name. This assumption is fine for this early example, but my experience has been somewhat different. More on this next week.. Plus I've added an original value attribute to every <field>. We're not using it in this episode, but in a few weeks, we will. Right now it easily points out the changes. So we got something that looks like this:

<row>
<field name="txtFirstName1" value="Mike" originalval="Mike" />
<field name="txtLastName1" value="Shaffer" originalval="Shaffer" />
</row>

Let's build some HTML. Taking the same strategy, we construct a string and .innerHTML it to the <table>. Here's Example 3

var oOuterNodes = TheIslandXML.selectNodes("//row");
var oOuterNode;
while ((oOuterNode = (oOuterNodes.nextNode!=null))
{
var oInnerNodes = oOuterNode.selectNodes("field");
var oInnerNode;
sString += "<tr>";
while((oInnerNode = (oInnerNodes.nextNode()!=null))
{
switch (oInnerNode.getAttribute("name").substr(0,8))
{
case "txtFirst":
sString +="<td class='First">";
break;
case "txtLastN":
sString += "<td class='Last'>";
break;
}
sString +="<input type='text' id='" + oInnerNode.getAttribute("name") + "' value='" + oInnerNode.getAttribute("value") + "' onchange='TextBoxChange();' /></td>";
}
sString += "</tr>";
}

Now we have a table (not the nicest looking form admittedly) of textboxes with values and an onchange event handler called TextBoxChange(). This function's job is to write any changes back to the data island. Here's how this function looks:

function TextBoxChange()
{
var who = window.event.srcElement;
var TheNewValue = who.value;
var TheNameThatChanged = who.id;
var xp = "//field[@name='" + TheNameThatChanged + "']";
var oChangeNode = TheIslandXML.selectSingleNode(xp);
if (oChangeNode)
{
alert("Here's the Node before the change:\n" + oChangeNode.xml);
oChangeNode.setAttribute("value",TheNewValue);
alert("Here's the Node after the change:\n" + oChangeNode.xml);
}
}

I'm binding to the onchange of the text box...some people like that, some don't. For these purposes it's satisfactory. You'll see an alert that throws the oNode.xml out to you when you change, showing before and after. Simple as that, you've bound these text boxes to your XML Data Island! Even though this technique is script based, it is very high performance. In the Monster I've successfully bound this to the onkeypress and onkeydown events...changing the value on every key stroke! I'm one of the people I mentioned earlier that doesn't like the onchange and onblur, I prefer not to rely on control focus events to trigger a bind operation. But that's just me....


This style reminds me a lot of using the ADO recordset in ASP to build HTML on the server. For years we all created HTML by looping through the recordset on the server, and performing logic to differentiate the different tags. This strategy is not much different, the primary difference is that it is being performed on the client rather than the server.

An Aside:

You may have noticed that I refer to the window.document object with a global variable reference, TheDoc. I read somewhere that every time you reference window.document JScript churns through the whole window collection, then the document collection, every time. Using a temporary variable to reference this allows JScript to look this up once and use from then on, speeding up the overall page by at least a millisecond or two....


You might also notice that I bind my onclick events to buttons using the syntax:
TheDoc.getElementById("btnShow").attachEvent("onclick",ShowXML); rather than the more typical:
<button id="btnShow" onclick="ShowXML();" >Show</button>.
It's really a preference of style, but I have found cases where this style has come in handy. If you are in the habit of dynamically binding and rebinding the events to objects programmatically, something done a lot in very complex interfaces, the JScript bind is more powerful. You can use the .detachEvent to remove dynamically added event handlers, but you can't get rid of the in-line ones. I also think it makes for cleaner HTML. We've come a long way in cleaning up the crud attributes that can litter our HTML tags, and this just keeps that trend, separating markup from function and layout. Lastly, it's very similar to the way .Net delegates work, and it makes for an easier transition, mentally....

The Finish Line:

Next time, we'll look at the same page but with more complex XML...which will force us to really dive into XPath and JScript.

If anyone has any comments or questions, email me at ms_@_mybluecoat.com (remove the underlines first...)

Popular posts from this blog

A teacher's credentials...

Kristie told me this funny story..... The School has been blessed for years with a teacher who has dutifully taught yearbook class to the Senior High students. She abruptly resigned a couple of weeks ago, for personal reasons and nothing to do with the School or the job per se. In a mad scramble, the principal scrounged up a "suitable" replacement. This replacement was described as "a mom". Fast forward to yesterday. Kristie was doing new employee orientation and this new hire was present. Kristie did emphasize this person seems to be a terrific individual and an all around nice lady. When Kristie introduced herself she stated: "So you're the new yearbook teacher....nice to meet you". The new hire replied, "Thanks, do you know anything about this yearbook stuff? Because I don't..." Kelly signed up for yearbook this year...she already has Digital Photography, a questionable class and now Yearbook. Wednesday Elective Day is going t...