Converting forms to AJAX
How to avoid common CSS mistakes
Using transformNode in IE7
What you should do instead is to use a wrapper library like Sarissa that gives you XSLTProcessor object in both Firefox and Internet Explorer environments. The XSLTProcessor API is described in Mozilla documentation (which also includes a simple example).
Client-side development environment
Scenario: XSLT deployment in a browser/server scenario with XSLT transformations performed on the browser (JavaScript or with <?xsl-stylesheet ?>)
Potential tools:
- Saxon is great for off-line testing (primarily while developing small bits of code or trying new programming techniques). It cannot fetch the information from the server, so you have to provide test data in local XML files
- Firefox is the best tool if you want to test client-server integration and use <?xsl-stylesheet ?> approach as it can recover from XSLT errors (which IE can't). However, it doesn't detect all errors, so the stylesheets working with Firefox might break in IE (but not due to an IE bug)
- IE is a nightmare when using <?xsl-stylesheet ?> directive. Whenever something breaks (error in stylesheet or even server-side error resulting in HTML response instead of XML response), its cache gets messed up and you have to restart the browser to continue the tests. I therefore use IE only in the final tests to verify that my XSLT has no weird errors not detected by Firefox.
- The best testing mechanism is probable the manual (JavaScript) client-side transformation. You can do it in IE or Firefox; in this case I would prefer IE, as it has better XSLT error reporting (MSXML is not that bad :).
Example: Use XSLT transformation in browser
Several readers were asking for an code sample that performs XSLT transformation in the browser, so here it is :) To start with, you should use a wrapper library to isolate yourself from browser version/platform inconsistencies. I'm using Sarissa to deal with XML, XSLT and HttpRequest and X library to deal with DHTML-related functions.
The most common XSLT-related operation that you do in your browser is this:
- Download XML document from the web server using XMLHttpRequest;
- Transform the XML into application-specific HTML markup with an XSLT transformation;
- Insert the resulting HTML markup into the web page.
Sarissa library has a function (Sarissa.updateContentFromURI) that does almost exactly what you need to get there, so you only need to perform the following steps:
- Load the XSLT stylesheet as XML document (sync load is easier to do).
- Import the loaded XML document into an XSL processor (special object that can perform the XSLT transformations).
- Call the updateContentFromURI function.
The resulting code is very simple:
// load XSLT stylesheet, done only once.
var xsl = loadURI("moveRoutes_lists.xsl");
if (!xsl) throw("Cannot load XSL stylesheet");
var xform = new XSLTProcessor();
xform.importStylesheet(xsl);
// the following call transforms a remote XML document
// into HTML fragment and inserts it into the target
// element
Sarissa.updateContentFromURI(remoteURI, xGetElementById(targetElement),xform);
Notes:
- sarissa.js as well as sarissa_dhtml.js libraries have to be included in the source document.
- xGetElementById function is part of the X library.
- loadURI function is documented here.
JavaScript progressive enhancement in practice
Server-side XSLT transformations
I would often use XSLT on the server to separate data (XML) from its presentation. Its usage also results in cleaner server-side code, as the markup generation is separated from the programming logic. It's also extremely useful if you want to deploy XML/XSLT-based solution that has to support low-end clients (for example, search engine spiders).
Error handling Sarissa wrapper
In a previous post I've included a simple Sarissa wrapper that could be used to load an XML document (for example, XSLT stylesheet). If you want to perform more error checking and generate sensible errors, you could use these two functions:
- loadFromServer function requests object load. If you provide a callback parameter, the load is asynchronous, otherwise it waits for the load to complete and return the wrapper object.
- checkLoadStatus checks the various error conditions and throws appropriate errors.
var AWS = new Object();
AWS.loadFromServer = function (url,cb) {
var lso = new Object() ;
function loadHandler() {
if (lso.request.readyState == 4) cb(lso); }
lso.savedURL = url ;
lso.request = new XMLHttpRequest() ;
lso.request.open("GET", lso.savedURL, cb ? true,false);
if (cb) lso.request.onreadystatechange = loadHandler;
lso.request.send(null);
return lso;
}
AWS.checkLoadStatus = function (lso) {
var url = lso.savedURL ;
var xr = lso.request ;
var msg;
if (!url) throw("AWS.checkLoadStatus expects loadFromServer object") ;
if (xr.status != 200) throw (url + " failed: "+xr.statusText);
try {
var de = xr.responseXML.documentElement;
if (!de) msg = url + ": response is not a valid XML document";
} catch(err) {
msg = url + ": response is not a valid XML document (" + err + ")";
}
if (msg) throw(msg);
return xr.responseXML;
}
Offline XSLT processor
I'm primarily using SAXON to test my XSLT code (or develop new programming techniques) before deploying it in a distributed environment. I was also using it to develop the WordML transformations that are now executed within the context of Save As ... function in Word.
Simple high-level wrapper for Sarissa
function loadURI(uri,async) {
var d = Sarissa.getDomDocument();
d.async = false;
d.load(uri);
if (d.parseError) throw("Error loading "+uri+": "+Sarissa.getParseErrorText(d));
return d;
}
Make pop-up windows visible to search engines
Recommended wrapper libraries
Note: I decided not to use a framework library. If you want to go down that route, these two would be way too low-level.
Automate the pagination of web pages
Using WordProcessingML to Generate Clean HTML from Word
MSXML is still only DOM-1 compliant
function getDomById(e,id) {
if (e.getElementById) { return e.getElementById(id); }
if (e.documentElement) e = e.documentElement;
var cn = e.childNodes;
for (var i = 0 ; i != cn.length; i++) {
var n = cn[i];
if (n.nodeType == 1) {
if (n.getAttribute("id") == id) return n ;
}
}
for (var i = 0 ; i != cn.length; i++) {
var n = cn[i];
if (n.nodeType == 1) {
var result = getDomById(n,id); if (result) return result;
}
}
return false;
}Note: The procedure is doing breadth-first search as I was looking for IDs that were pretty high in the hierarchy of trees with many branches.