Detect included libraries in ASP

My ASP applications rely a lot on included libraries. As I usually end up with a complex hierarchy of libraries, it's not always possible to include the required modules in the dependent library. This makes debugging a bit more interesting, as you cannot be sure all the prerequisites have been included in the main .asp page unless you test all code paths.

To work around this problem, I wrote a small routine that checks for existence of prerequisite functions and throws an exception documenting what went wrong.
'
' CheckIncludedLibrary - checks if the incRoutine is available,
' otherwise throws an exception documenting that srcLibrary
' needs incLibrary
'
Sub CheckIncludedLibrary(incRoutine,srcLibrary,incLibrary)
Dim routineRef

On Error Resume Next
Set routineRef = GetRef(incRoutine)
If Err.Number <> 0 Then On Error Goto 0 : _
Err.Raise vbObjectError+1,srcLibrary, _
"You have to include " & incLibrary
End Sub

You would use this routine in a way similar to the example below:
'
' This library requires inclusion of /forms/xmlLibrary.asp,
' /forms/lib/editLibrary.asp and /asp/incPostingPreview.asp
'

CheckIncludedLibrary "NewXMLTextElement", _
"listRoutesLibrary","/forms/xmlLibrary"
CheckIncludedLibrary "EnrichElementText", _
"listRoutesLibrary","/forms/lib/editLibrary"
CheckIncludedLibrary "PostToPreview", _
"listRoutesLibrary","/asp/incPostingPreview"

“You've asked for it” series

Analyzing Google query strings that brought visitors to my blog (StatCounter is an excellent free tool to do this job), I usually find interesting (oft repeating) queries that are not yet answered in my blog. Obviously there are not too many good answers on other web sites, otherwise Google users would probably not click on a hit on the second or third page (where my blog usually appears for more generic queries).

So, to help my fellow programmers, I've started a series of “You've asked for it” posts answering the questions that brought many of you to my site in the first place (and, don't forget, you can always send me an interesting question with the Send a message link on my bio page.

Use XSLT to generate RSS item description

RSS specifications do not allow HTML markup in title or description elements. If you want to include HTML markup in RSS elements, it has to be quoted, for example <b>bold</b>. Doing this in any server-side scripting language is easy, for example, ASP provides Server.HTMLEncode function. If you use XSLT to transform internal XML documents into RSS feeds, the task gets trickier, as XSLT provides no equivalent function.

The following XSLT templates solve the problem: call the outputQuotedTree template in the context of input node containing HTML markup and it will generate quoted contents of its child nodes.

<xsl:template name="outputQuotedTree">
<xsl:for-each select="node() ¦ text()">
<xsl:call-template name="outputTextNode" />
</xsl:for-each>
</xsl:template>


<xsl:template name="outputTextNode">
<xsl:choose>
<xsl:when test="name() = ''">
<xsl:value-of select="." />
</xsl:when>
<xsl:otherwise>
<!-- Emit the opening tag -->
<xsl:text>&lt;</xsl:text><xsl:value-of select="name()" />
<!-- Emit the attributes of the opening tag -->
<xsl:for-each select="@*">
<xsl:text> </xsl:text>
<xsl:value-of select="name()"/><xsl:text>='</xsl:text>
<xsl:value-of select="."/>
<xsl:text>'</xsl:text>
</xsl:for-each>
<xsl:choose>
<xsl:when test="node() ¦ text()">
<!-- If there are children, close the start tag and
process the children -->
<xsl:text>&gt;</xsl:text>
<xsl:for-each select="node() ¦ text()">
<xsl:call-template name="outputTextNode" />
</xsl:for-each>
<!-- Emit the closing tag -->
<xsl:text>&lt;/</xsl:text>
<xsl:value-of select="name()" />
<xsl:text>&gt;</xsl:text>
</xsl:when>
<xsl:otherwise>
<!-- No children, emit the self-closing tag -->
<xsl:text>/&gt;</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>


</xsl:stylesheet>

Centering CSS-based layouts, Detecting text resize and more

The latest collection of links by Meryl Evans contains real gems:

MSXML firstChild property in VBScript

To complement a bit vague Microsoft MSDN document: firstChild property returns nothing (not null) if the XML node has no children. To test whether an XML node has children, use:
If Not (node.firstChild Is Nothing) Then ...
or, alternatively
If node.childNodes.length > 0 Then ...

Windows that resize to fit their content

This article contains a great description of JavaScript code that lets you resize a pop-up window to perfectly fit its content. Highly recommended!

Another way of writing less-than

In one of my projects, I had to check the version number of included JavaScript library to detect out-of-date browser cache. However, that's hard if you generate web pages with XSLT transformations. Finally, this trick worked for me - instead of writing
if (window.libraryVersion > minimumVersion) ...
... I wrote ...
if (Math.max(window.libraryVersion,minimumVersion) ==  minimumVersion) ...

Embedding JavaScript in XSL documents

Summary: Do not use complex inline JavaScript in XSLT-generated web pages
If you're using XSLT to produce full-blown (X)HTML pages from XML documents (more about that in one of my upcoming articles for www.InformIT.com), you'll sooner or later need a bit of inline JavaScript code in the final web page. As you know, you cannot use bare less-than-sign (<) in an XML document and you cannot use &lt; quoting in JavaScript embedded in <script> tags in traditional HTML page.

Apart from migrating all JavaScript code in a separate .js file (which is the best solution anyway) or using tricks to avoid < and & in the JavaScript code, the only way to make things work (at least in some modern XHTML-aware browsers) is to structure the web page to be XHTML-compliant with proper <!DOCTYPE> declaration and <html xmlns="http://www.w3.org/1999/xhtml"> root element.  When the browsers recognize a page as XHTML-compliant page, they might be willing to transform &lt; escape within the <script> tag into less-than sign (works for Opera, but not for Firefox).

Reading the XSLT-related standards, one would get an impression that using the <xsl:output method="html" /> command should force the transformation engine to write pure text instead of XML escapes within the <script> tag. However, it's almost impossible to force all the XSLT transformation engines being used today to do it properly (for example, it looks like MSXML engine on the IIS server simply ignores it).

Setting text property of MSXML Node object destroys its children

The Microsoft's documentation of the IXMLDOMNode.text property skips an important detail: if the node has non-text children and you set its text property, it loses all children. The node.text = string statement deletes all children and creates a single IXMLDOMText child.

Manipulating innerHtml disables event handlers set with JavaScript

If you manipulate innerHtml property of an HTML element with children and you've previously set any event handlers on those children with JavaScript (not with onxxxx attributes), they're gone (as the changing of innerHTML effectively erases all the children and recreates them). If you want to retain children event handlers, you should add new children with DOM calls (node.appendChild).

For example, if you set up onclick handler like this:
<div id="outerDiv">
<p id="para1">Click here</p>
</div>
<script>
var x = document.getElementById("para1");
x.onclick = function () { alert("Clicked"); }
</script>

... the onclick handler will stop working if you change innerHtml of outerDiv, for example to add another paragraph:
<script>
var outerDiv = document.getElementById("outerDiv") ;
outerDiv.innerHtml += "<p id='para2'>Another paragraph</p>" ;
</script>

Browser-independent Object Fading

The author of this article (courtesy of InformIT.com) did a great job documenting ways to fade an object in a wide range of incompatible browser implementations. Highly recommended.

Detecting cached CSS style sheets

Have you ever deployed an improved version of your web pages, just to find out that the visitors have to press Ctrl-R (Reload) to get them right as their browsers have cached the Cascaded Style Sheet (.css) files used in your web site. Renaming the style sheet files every time you change them definitely solves the problem, but requires changing all the related web pages as well.
It's infinitely better if you can detect that the style sheets are cached by the browser (and thus obsolete) and respond with a forced reload. In the simplest case (more about version control in another post), you can detect that the style sheets referenced by your web page are missing a class (or id) selector that you need. Include the browser-independent stylesheet library in your web page and add the following code somewhere in the body of the page:
<script>if (xHasStyleSheets() && ! xHasStyleSelector('.myClass')) location.reload(true);</script>
If you're looking for an ID selector,  replace '.myClass' with '#myID'.

Detecting out-of-date JavaScript libraries

A major challenge of writing rapid-changing AJAX applications is the asynchronous nature of browser page fetching. It's almost impossible to ensure that the browser will fetch the new version of all the components of your application (for example, JavaScript library) when it changes on the server.

You might solve this problem by renaming the JavaScript libraries (.js files) every time you make a significant change that could break the client-side application. While being bullet-proof, this approach requires republishing of all pages referencing the renamed library (which could be your entire web site).

It's way better to include the cache-checking code in your application:
  • Within your library, set a global variable (or a property of window object) to library's version number. The version number could be an increasing number, the date the change was made or (ideally) a version number from your source code control system (SCCS for Unix fans or Visual SourceSafe for Windows)
  • In every web page that relies on particular functionality of that library, check the version number and force a reload if there's a mismatch.
For example, in your JavaScript file, include a statement setting the version number ...
window.version_MyLibrary = 20060810; // library last changed on August 10th, 2006
... and at the beginning of the web pages requiring new library functionality, check the version number just after the library has been included with the <script> tag:
<script>if (window.version_MyLibrary < 20060810) location.reload(true);</script>
So, if your web page detects that the library included during the load process has an incompatible version number (due to cached version being used by the browser), it forces a hard reload, downloading all components from the web server.

Browser-independent style sheet Javascript code

In a recent project, I had to check whether the stylesheets attached to a document contain a particular class name. The X library (www.cross-browser.com) does not contain anything handling the stylesheets, so here's the necessary code (works in Internet Explorer and Firefox, Opera is broken).
 
Usage example: 
xHasStyleSelector('.menuButton') will return true if any of the stylesheets attached to the document (including the inline stylesheets) contain definition of .menuButton.

// browser-independent stylesheet handling
// Copyright 2006 Ivan Pepelnjak (
www.zaplana.net)
// Distributed under the terms of the GNU LGPL
 
//
// xGetStyleSheetFromLink - extracts style sheet object from the HTML LINK object (IE vs. DOM CSS level 2)
//
function xGetStyleSheetFromLink(cl) { return cl.styleSheet ? cl.styleSheet : cl.sheet; }
 
//
// xGetCSSRules - extracts CSS rules from the style sheet object (IE vs. DOM CSS level 2)
//
function xGetCSSRules(ss) { return ss.rules ? ss.rules : ss.cssRules; }
 
//
// xHasStyleSheets - checks browser support for stylesheet related objects (IE or DOM compliant)
//
function xHasStyleSheets() {
  return document.styleSheets ? true : false ;
}
   
//
// xTraverseStyleSheet (stylesheet, callback)
//   traverses all rules in the stylesheet, calling callback function on each rule.
//   recursively handles stylesheets imported with @import CSS directive
//   stops when the callback function returns true (it has found what it's been looking for)
//
// returns:
//   undefined - problems with CSS-related objects
//   true      - callback function returned true at least once
//   false     - callback function always returned false
//
function xTraverseStyleSheet(ss,cb) {
  if (!ss) return false;
  var rls = xGetCSSRules(ss) ; if (!rls) return undefined ;
  var result;
  
  for (var j = 0; j < rls.length; j++) {
    var cr = rls[j];
    if (cr.selectorText) { result = cb(cr); if (result) return true; }
    if (cr.type && cr.type == 3 && cr.styleSheet) xTraverseStyleSheet(cr.styleSheet,cb);
  }
  if (ss.imports) {
    for (var j = 0 ; j < ss.imports.length; j++) {
      if (xTraverseStyleSheet(ss.imports[j],cb)) return true;
    }
  }
  return false;
}
   
//
// xTraverseDocumentStyleSheets(callback)
//   traverses all stylesheets attached to a document (linked as well as internal)
//   
function xTraverseDocumentStyleSheets(cb) {
  var ssList = document.styleSheets; if (!ssList) return undefined;
     
  for (i = 0; i < ssList.length; i++) {
    var ss = ssList[i] ; if (! ss) continue;
    if (xTraverseStyleSheet(ss,cb)) return true;
  }
  return false;
}
 
//
// xHasStyleSelector(styleSelectorString)
//   checks whether any of the stylesheets attached to the document contain the definition of the specified
//   style selector (simple string matching at the moment)
//
// returns:
//   undefined  - style sheet scripting not supported by the browser
//   true/false - found/not found
//
function xHasStyleSelector(ss) {
  if (! xHasStyleSheets()) return undefined ;
 
  function testSelector(cr) {
    return cr.selectorText.indexOf(ss) >= 0;
  }
  return xTraverseDocumentStyleSheets(testSelector);
}

Improve your search engine rankings with AJAX

My article describing how to use AJAX to split navigational elements from the main page content, resulting in improve search engine ranking and reduced download time has been published on InformIT.com.