Creating an XSLT View

In the XSLT-based AJAX architecture, you might want to create one or more views per data schema. Each view is implemented through a template and used to render data based on a data schema. The data schema you use is defined by the WCF service’s data contract. In the following example, I’ll create a navigational control based on a list of topics. The Catalog service defined at the endpoint http://localhost:8080/Web/Services/CatalogService.svc/Default/Topics produces an XML document similar to Example 9-2. It’s important to have a source XML document when you develop the XSLT so that you can test the output as you work. Example 9-3 demonstrates the output document from the sample application’s Catalog service.

Example 9-2. To begin developing XSLT files, save an XML sample data file.

<ArrayOfLink xmlns="http://knowledgebase"
      xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <Link>
    <Title>AJAX</Title>
    <Catalog>Default</Catalog>
  </Link>
  <Link>
    <Title>Default</Title>
    <Catalog>Default</Catalog>
  </Link>
</ArrayOfLink>

To start creating an XSLT view, begin with a simple XSLT file using the Visual Studio XSLT template as previously described. In the root xsl:stylesheet element, you need to define any XML namespaces that your document uses. For the sample application, the XML namespace http://knowledgeBase is used. In the XSLT document, assign a prefix to the namespace in the root node. This prefix is used to alias the XML namespace. The XML namespace definition xmlns:kb="http://knowledgebase" is used in the sample code, so kb can be used in XPath expressions to alias the namespace http://knowledgeBase.

You also need to define the output type of the XSLT file. To declare an HTML fragment, use the following tag inside the root xsl element. The use of this tag prevents an XML declaration from being included in the output, and it instructs the XSLT processor to render HTML.

<xsl:output omit-xml-declaration="yes" method="html" encoding="utf-16" />

One advantage of using XSLT is that you can use Visual Studio to debug your XSL logic against a source file. In your Visual Studio project, save the sample XML file from the input. Next, to debug the XSLT, open the XSLT file and choose Debug XSLT from Visual Studio’s XML menu. You might be prompted to choose a source file for the XML input, which is saved in the project as a property of the XSLT file. You can set break points in the XSLT file to examine the XML nodes at the selected path. Figure 9-2 demonstrates the Visual Studio XSLT debugging process.

Visual Studio provides debugging support for XSLT code.

Figure 9-2. Visual Studio provides debugging support for XSLT code.

After generating and testing the basic XSLT structure, you’re ready to start defining template rules. A template rule is a processing instruction for a given XPath path, and it usually defines a block of markup to render and any child template rules to process. In our example, we’re expecting a root element with the XPath expression /kb:ArrayOfLink. This XPath rule matches the root ArrayOfLink element defined in the http://knowledgeBase namespace. The following XSL template will match the root node of our expected XML document and include the test "Topics" before continuing to process any child nodes. The processing command <xsl:apply-templates /> instructs the XSLT engine to process any other matches at the currently selected document element.

<xsl:template match='/kb:ArrayOfLink'>
    <div class='navBarHeader'>Topics</div>
    <xsl:apply-templates />
</xsl:template>

As a best practice, include any styles in a CSS definition, defined either in the page or in a global CSS file (although not all of my code samples will reflect this, for simplicity’s sake).

Finally, for each link, we’ll include an HTML anchor element inside of a DIV element. You should avoid writing JavaScript in the XSLT because it’s too easy to create invalid JavaScript from user input. Instead, you should use post-processing to attach event handlers to the anchor elements by using Sys.UI.DomEvent.addHandler, as first introduced in Chapter 5. Example 9-3 displays the final XSLT file we’ll use for the topics list in the navigation element.

Example 9-3. The XSLT file can be very simple (Web/XML/Topics.xslt).

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:kb="http://knowledgebase"
        exclude-result-prefixes="kb">
    <xsl:output omit-xml-declaration="yes" method="html" encoding="utf-16" />

    <xsl:template match='/kb:ArrayOfLink'>
        <div class='navBarHeader'>Topics</div>
        <xsl:apply-templates />
    </xsl:template>

    <xsl:template match='kb:Link'>
        <div class='navDiv'>
            <a><xsl:value-of select='kb:Title' disable-output-escaping='yes'/></a>
        </div>
    </xsl:template>

</xsl:stylesheet>

Example 9-3 also shows a new attribute on the stylesheet element: exclude-result-prefixes. This attribute prevents the namespace from being rendered in the output HTML. Without this attribute, you end up with the HTML containing xmlns:kb="http://knowledgebase". You can specify a space-delimited list of prefixes to exclude in the exclude-result-prefixes attribute.

If the text you are transforming is to include HTML markup (rather than plain text), be sure to include the processing instruction disable-output-escaping="yes". Without this attribute, the XSLT processor might output escaped HTML. For example, the following XSLT rule for the title XML element allows the processor to output HTML rather than escaped HTML:

<xsl:value-of select='kb:title' disable-output-escaping="yes" />

After validating the XSLT, you are ready to use it in the AJAX application. I prefer saving the XSLT files in an XML directory for the application, although you can save them anywhere in the Web application. These XSLT files can be loaded as needed by the AJAX client application. Loading files on this basis doesn’t add considerable overhead because XSLT loads very quickly on the client, as fast as loading an additional CSS file or image.

XML on the Client

To create a new XML document using the Microsoft AJAX Library, you can use the class Sys.Net.XMLDOM. This class is included in the AJAX library and is used internally to create XML documents from Web service calls made through classes defined in the Sys.Net namespace.

Tip

Tip

Prior to the Microsoft AJAX Library 3.5, ASP.NET AJAX Extensions 1.0 implemented the XML object in the class XMLDOM (without the Sys.Net namespace). In the 3.5 library, this class is part of the Sys.Net namespace, a breaking change if you used the class in legacy applications. To write JavaScript that runs on the ASP.NET AJAX Extensions 1.0, include the following line in your code:

if (Sys$Net$XMLDOM) XMLDOM = Sys$Net$XMLDOM;

By examining the source code of Sys.Net.XMLDOM you can get a better understanding of the XML processing engine. All non-Microsoft browsers include the native browser object window.DOMParser, which is used to implement an XML object. Microsoft browsers (Internet Explorer 5.5, 6.0, and 7.0) use the MSXML library to implement the XML parser. The MSXML 3.0 library is used consistently for all Windows operating systems since Windows 2000 SP4, including Windows XP, Windows Server 2003, Windows Vista, and Windows Server 2008. Example 9-4 contains the Sys.Net.XMLDOM function from the Microsoft AJAX Library. By examining the code, you can see more clearly how the framework provides support for the MSXML library in Internet Explorer or just uses window.DOMParser in other modern browsers.

Example 9-4. Sys.Net.XMLDOM provides support for MSXML in Internet Explorer and for the browser’s native XML technology for non-Microsoft browsers (excerpt from MicrosoftAjax.debug.js.).

// Excerpt from the Microsoft AJAX Library
Sys.Net.XMLDOM = function Sys$Net$XMLDOM(markup) {
    if (!window.DOMParser) {
        var progIDs = ['Msxml2.DOMDocument.3.0', 'Msxml2.DOMDocument'];
        for (var i = 0, l = progIDs.length; i < l; i++) {
            try {
                var xmlDOM = new ActiveXObject(progIDs[i]);
                xmlDOM.async = false;
                xmlDOM.loadXML(markup);
                xmlDOM.setProperty('SelectionLanguage', 'XPath'),
                return xmlDOM;
            } catch (ex) {}
        }
    }  else {
        try {
            var domParser = new window.DOMParser();
            return domParser.parseFromString(markup, 'text/xml'),
        } catch (ex) {}
    }
    return null;
}

As I’ve mentioned before, while Internet Explorer uses the MSXML implementation, other browsers, including Mozilla Firefox, Apple Safari, Google Chrome, and Opera, use native browser objects for processing XML. When writing XML support into your application, it’s important to check between the ActiveX model and the native browser model, which we’ll demonstrate in the following example.

A simple method for transforming XML with XSLT is shown in Example 9-5. In the sample code, this method is included in the utility library SOAjax.js. You’ll notice that the XSLT transform is a simple one-line command using MSXML in Internet Explorer, which is the only browser that doesn’t implement the XSLTProcessor method. Other browsers implement the same interface for XSLT, letting you use common code for all other cases. With browsers other than Internet Explorer, you might end up with encoded output in certain cases, for example RSS feeds with encoded HTML. In this case, you need to decode the escaped HTML characters, as implemented in the SOAjax.HtmlDecode method, also shown in Example 9-5.

Example 9-5. A simple method for transforming XML with XSLT (from SOAjax.js).

// XML TOOLKIT
SOAjax.XmlTransform = function(xml, xsl, control, decode) {
    var decode; if (decode == null){decode = true;}
    if (decode == null) decode = true;
    if (!window.XSLTProcessor) // ie, using MSXML:
        control.innerHTML = xml.transformNode(xsl);
    else {  // MOZZILA
        Sys.Debug.trace('XSLT using XSLTProcessor'),
        Sys.Debug.trace(typeof (XSLTProcessor));
        var processor = new XSLTProcessor();
        processor.importStylesheet(xsl);
        var content = processor.transformToFragment(xml, document);
        if (decode) {
            var div = document.createElement('div'),
            div.appendChild(content);
            control.innerHTML = SOAjax.HtmlDecode(div.innerHTML);
        } else {
            control.appendChild(content);
        }
    }
}
//   client side version of the useful Server.HtmlDecode method
//   takes an encoded string and returns the decoded string.
SOAjax.HtmlDecode = function HtmlDecode(enc) {
    return enc.replace(/&quot;/gi, String.fromCharCode(0x0022))
        .replace(/&amp;/gi, String.fromCharCode(0x0026))
        .replace(/&lt;/gi, String.fromCharCode(0x003c))
        .replace(/&gt;/gi, String.fromCharCode(0x003e));
}

XML processing can be a fundamental building block for your AJAX library. In fact, you can implement nearly all the code for an application’s user interface by using client-side XSLT and a little bit of JavaScript. As mentioned earlier, however, it is important to keep your stylesheets compact, simple, and maintainable. If several views use the same data structure, use a common XSLT file. For example, you can use the same XSLT file for lists of people whether they’re online users, site contributors, or friends. The user interface is best designed by data views coded against a schema, not the relationship of the data. This strategy helps you reuse common XSLT assets in your application.

Building an XML Control

To implement XSLT in a control, you can create a control class that loads data and XSLT on demand. This class can be used as a building block throughout multiple applications, either as a base class or to provide full functionality. An XML control implementation is a very powerful component in your AJAX toolkit and can be used to build entire applications. In the next section, I’ll walk through the creation of an XML control and then show how to integrate it into the sample application. To build a control for XML loading and rendering, you want to build a control class with the interface defined in Table 9-1.

Table 9-1. The XML Control Interface

Methods

Description

loadXML(url)

Loads the XML data from the specified URL.

loadXSLT(url)

Loads the XSLT data from the specified URL.

reload

Causes the data to be reloaded from the server.

reloadInterval

When set, causes the control to reload on an interval.

Properties

Description

xml

The XML source data.

xslt

The XSLT used for rendering.

Events

Description

error

Raised when an error occurs in the data load or XSLT transform.

prerender

Raised before rendering. Used to pre-process the rendered HTML by clearing any DOM event handlers from the prior render.

render

Raised after the XSLT render. Used to add any DOM event handlers to the rendered HTML.

xmlLoaded

Raised when the XML is loaded from the data source.

xsltLoaded

Raised when the XSLT is loaded from the XSLT URL.

The significant methods of the XML control will be for data loading and transformation. We can also implement a timer function so that the control can refresh itself at arbitrary intervals and present live data to the user. To load the data, we’ll implement fairly standard logic to load XML from an arbitrary endpoint that will be set from client code. Example 9-6 demonstrates the methods used to load the XML.

Example 9-6. To implement a client-side XML control, create methods to load XML data at will (from Web/Script/Controls/XmlControl.js).

_loadXml: function(url) {
    if (this._timeoutID) window.clearTimeout(this._timeoutID);
    this.xmlUrl = url;
    if (url == null || url == '') return;
    var request = new Sys.Net.WebRequest();
    var context = new Object();
    request.add_completed(Function.createDelegate(this, this._loadXmlComplete));
    request.set_userContext(context);
    request.set_url(url);
    request.invoke();
},

_loadXmlComplete: function(response) {
    if (response == null) { response = new Sys.Net.WebRequestExecutor(); }
    var context = response.get_webRequest().get_userContext();
    var status = response.get_statusCode();
    var url = response.get_webRequest().get_url();
    var xml;
    if (status == 200) {
        try {
            var lastMod = response.getResponseHeader('Last-Modified'),
            if (lastMod && lastMod != '' && this._lastMod == lastMod) {
                this.queueReload();
                return;
            }else this._lastMod = lastMod;
            xml = response.get_xml();
            if (xml == null)
                xml = response.get_responseData();
            if (!xml) throw Error.create(
                'We could not load the XML data at the URL ' + url);
            var xmlLoadedHandler = this.get_events().getHandler('xmlLoaded'),
            if (xmlLoadedHandler) xmlLoadedHandler(this, Sys.EventArgs.Empty);
            this.set_xml(xml);
        } catch (e) {
            Sys.Debug.fail('Could not process callback method.'),
        }
    }
    else if (status == 304) { // not modified, not handled by the browser.
        this.queueReload();
    }
    else { // Process the status. Could be 401, 404 (not found), 410 (gone)
        var statusText = response.get_statusText();
        Sys.Debug.trace(String.format('ERROR: {0} replied "{1}" ({2}).',
            url, statusText, status));
        var errorHandler = this.get_events().getHandler('error'),
        if (!errorHandler) {   // Default error handler
            switch (status) {
                case 410: // HttpStatusCode.Gone:
                    alert('Content has been removed.'),
                    break;
                case 404: // HttpStatusCode.NotFound:
                    alert('Could not find resource.'),
                    break;
                default:
                    alert(String.format('ERROR: {0} replied "{1}" ({2}).',
                        url, statusText, status));
            }
        } else {
            errorHandler(response, Sys.EventArgs.Empty);
        }
    }
},

As with the XML logic, we need to implement a method for loading XSLT. The method for loading XSLT is simpler because we won’t implement error handling for the Web request. These errors will be caught during development rather than in production code. Example 9-7 demonstrates the logic for loading XSLT in the XML control.

Example 9-7. Client-side AJAX code must be able to load XSLT on the client on demand (from Web/Script/ Controls/XmlControl.js).

loadXslt: function(url) {
    this.xslUrl = url;
    if (url == null || url == '') return;
    var request = new Sys.Net.WebRequest();
    Sys.Debug.trace("XSLT Load: " + url);
    request.add_completed(Function.createDelegate(this, this._loadXsltComplete));
    request.set_url(url);
    request.invoke();
},

_loadXsltComplete: function(response) {
    var context = response.get_webRequest().get_userContext();
    var status = response.get_statusCode();
    var xml;
    if (status == 200) {
        try {
            xml = response.get_xml();
            if (xml == null)
                xml = response.get_responseData();
            var xsltLoadedHandler = this.get_events().getHandler('xsltLoaded'),
            if (xsltLoadedHandler) xsltLoadedHandler(this, Sys.EventArgs.Empty);
            if (xml) this.set_xslt(xml);
        } catch (e) {
            var errorHandler = this.get_events().getHandler('error'),
            if (errorHandler) errorHandler(response, Sys.EventArgs.Empty)
            else
                Sys.Debug.trace('Could not process callback method.'),
        }
    }
},

For both the xml and xslt properties, when the XML content is set we can call the render method. The render method can be private, which is inferred by prefixing the method name with an underscore. The following logic is in both the xml and xslt properties.

if (this._xml && this._xslt) this._render();

The render method simply wraps the SOAjax.XmlTransform utility method (previously shown in Example 9-3) with prerender and postrender handlers. These handlers call the handlers for the prerender event and the render event. The following code shows the render method, which is called as soon as both the XML and XSLT are loaded:

_render: function() {
    Sys.Debug.trace('XmlControl Render'),
    var control = this.get_element();
    if (control == null || this._xml == null || this._xslt == null)
        return;

    var prerenderHandler = this.get_events().getHandler('prerender'),
    if (prerenderHandler) prerenderHandler(this, Sys.EventArgs.Empty);

    SOAjax.XmlTransform(this._xml, this._xslt, control);
    control.style.display = '';
    this._rendered = true;

    var renderHandler = this.get_events().getHandler('render'),
    if (renderHandler) renderHandler(this, Sys.EventArgs.Empty);
}

The full XmlControl class is shown in Example 9-8. With this control, you can implement very rich applications using XML source data and client-side rendering on demand. In the following code samples, we’ll utilize XmlControl in the case study application while adding more functionality to the rendered HTML through DOM events.

Example 9-8. The XmlControl class is a standard control used for XML loading and rendering (Web/Script/ Controls/XmlControl.js).

/// <reference name="MicrosoftAjax.js"/>
Type.registerNamespace('SOAjax.Controls'),

SOAjax.Controls.XmlControl = function(element) {
    /// <summary>
    /// A component that loads and renders XML using XSLT on the client.
    /// </summary>
    SOAjax.Controls.XmlControl.initializeBase(this, [element]);
}

SOAjax.Controls.XmlControl.prototype = {
    _xml: null,
    _xslt: null,
    _xsltUrl: null,
    _xmlUrl: null,
    _reloadInterval: null,
    _interval: null,
    _timeoutID: null,
    _lastMod: null,

    get_xml: function() {
        /// <value>Gets or sets the XML data for the control.</value>
        return this._xml;
    },
    set_xml: function(value) {
        if (this._xml !== value) {
            this._xml = value;
            this.raisePropertyChanged('xml'),
        }
        if (this._xml && this._xslt) this._render();
    },

    get_xslt: function() {
        /// <value>Gets or sets the XSLT for the control.</value>
        return this._xslt;
    },
    set_xslt: function(value) {
        if (this._xslt !== value) {
            this._xslt = value;
            this.raisePropertyChanged('xslt'),
        }
        if (this._xml && this._xslt) this._render();
    },

    get_xsltUrl: function() {
        /// <value>The URL to load the XSLT from.</value>
        return this._xsltUrl;
    },
    set_xsltUrl: function(value) {
        if (this._xsltUrl !== value) {
            this._xsltUrl = value;
            this.raisePropertyChanged('xsltUrl'),
            if (this._xsltUrl && !this.get_isUpdating())
                this._loadXslt(this._xsltUrl);
        }
    },

    get_xmlUrl: function() {
        /// <value>The URL to load the data from.</value>
        return this._xmlUrl;
    },
    set_xmlUrl: function(value) {
        if (this._xmlUrl !== value) {
            this._lastMod = null;
            this._xmlUrl = value;
            this.raisePropertyChanged('xmlUrl'),
            if (this._xmlUrl && !this.get_isUpdating())
                this._loadXml(this._xmlUrl);
        }
    },
    get_reloadInterval: function() {
        /// <value>The interval at which we'll reload the control.</value>
        return this._reloadInterval;
    },
    set_reloadInterval: function(value) {
        if (this._reloadInterval !== value) {
            if (value != 0 && value < 99) {
                throw Error.argumentOutOfRange('reloadInterval',
                  value, 'The reload interval must be 0 or over 100 milliseconds.'),
            }
            this._reloadInterval = value;
            this.raisePropertyChanged('reloadInterval'),
        }
    },

    reload: function() {
        if (this._timeoutID) window.clearTimeout(this._timeoutID);
        this._reload();
    },

    _reload: function() {
        var xmlUrl = this.get_xmlUrl();
        if (xmlUrl != null) { this._loadXml(xmlUrl); }
    },

    // Events --------------------------------------------------------
    add_xmlLoaded: function(handler) {
        /// <value>Bind and unbind to the xmlLoaded event.</value>
        this.get_events().addHandler('xmlLoaded', handler);
    },
    remove_xmlLoaded: function(handler) {
        this.get_events().removeHandler('xmlLoaded', handler);
    },

    add_xsltLoaded: function(handler) {
        /// <value>Bind and unbind to the xsltLoaded event.</value>
        this.get_events().addHandler('xsltLoaded', handler);
    },
    remove_xsltLoaded: function(handler) {
        this.get_events().removeHandler('xsltLoaded', handler);
    },

    add_render: function(handler) {
        /// <value>Bind and unbind to the render event.</value>
        this.get_events().addHandler('render', handler);
    },
    remove_render: function(handler) {
        this.get_events().removeHandler('render', handler);
    },

    add_prerender: function(handler) {
        /// <value>Bind and unbind to the prerender event.</value>
        this.get_events().addHandler('prerender', handler);
    },
    remove_prerender: function(handler) {
        this.get_events().removeHandler('prerender', handler);
    },

    add_error: function(handler) {
        /// <value>Bind and unbind to the error event.</value>
        this.get_events().addHandler('error', handler);
    },
    remove_error: function(handler) {
        this.get_events().removeHandler('error', handler);
    },

    initialize: function() {
        SOAjax.Controls.XmlControl.callBaseMethod(this, 'initialize'),
        if (this._xsltUrl && !this.get_isUpdating())
            this._loadXslt(this._xsltUrl);
        if (this._xmlUrl && !this.get_isUpdating())
            this._loadXml(this._xmlUrl);
    },

    _loadXml: function(url) {
        if (this._timeoutID) window.clearTimeout(this._timeoutID);
        this.xmlUrl = url;
        if (url == null || url == '') return;
        var request = new Sys.Net.WebRequest();
        var context = new Object();
        request.add_completed(Function.createDelegate(this, this._loadXmlComplete));
        request.set_userContext(context);
        request.set_url(url);
        request.invoke();
    },

    _loadXmlComplete: function(response) {
        if (response == null) { response = new Sys.Net.WebRequestExecutor(); }
        var context = response.get_webRequest().get_userContext();
        var status = response.get_statusCode();
        var url = response.get_webRequest().get_url();
        var xml;
        if (status == 200) {
            try {

                var lastMod = response.getResponseHeader('Last-Modified'),
                if (lastMod && lastMod != '' && this._lastMod == lastMod) {
                    this.queueReload();
                    return;
                }
                else this._lastMod = lastMod;

                xml = response.get_xml();
                if (xml == null)
                    xml = response.get_responseData();
                if (!xml) throw Error.create(
                    'We could not load the XML data at the URL ' + url);
                var xmlLoadedHandler = this.get_events().getHandler('xmlLoaded'),
                if (xmlLoadedHandler) xmlLoadedHandler(this, Sys.EventArgs.Empty);
                this.set_xml(xml);
            } catch (e) {
                Sys.Debug.fail('Could not process callback method.'),
            }
        }
        else if (status == 304) { // not modified, not handled by the browser.
            this.queueReload();
        }
        else { // Process the status. Could be 401, 404 (not found), 410 (gone)
            var statusText = response.get_statusText();
            Sys.Debug.trace(String.format('ERROR: {0} replied "{1}" ({2}).',
                url, statusText, status));
            var errorHandler = this.get_events().getHandler('error'),
            if (!errorHandler) {   // Default error handler
                switch (status) {
                    case 410: // HttpStatusCode.Gone:
                        alert('Content has been removed.'),
                        break;
                    case 404: // HttpStatusCode.NotFound:
                        alert('Could not find resource.'),
                        break;
                    default:
                        alert(String.format('ERROR: {0} replied "{1}" ({2}).',
                            url, statusText, status));
                }
            } else {
                errorHandler(response, Sys.EventArgs.Empty);
            }
        }
    },

    _loadXslt: function(url) {
        this.xslUrl = url;
        if (url == null || url == '') return;
        var request = new Sys.Net.WebRequest();
        Sys.Debug.trace("XSLT Load: " + url);
        request.add_completed(Function.createDelegate(this,
            this._loadXsltComplete));
        request.set_url(url);
        request.invoke();
    },
    _loadXsltComplete: function(response) {
        var context = response.get_webRequest().get_userContext();
        var status = response.get_statusCode();
        var xml;
        if (status == 200) {
            try {
                xml = response.get_xml();
                if (xml == null)
                    xml = response.get_responseData();
                var xsltLoadedHandler = this.get_events().getHandler('xsltLoaded'),
                if (xsltLoadedHandler) xsltLoadedHandler(this, Sys.EventArgs.Empty);
                if (xml) this.set_xslt(xml);
            } catch (e) {
                var errorHandler = this.get_events().getHandler('error'),
                if (errorHandler) errorHandler(response, Sys.EventArgs.Empty)
                else
                    Sys.Debug.trace('Could not process callback method.'),
            }
        }
    },

    _render: function() {
        var control = this.get_element();
        if (control == null || this._xml == null || this._xslt == null)
            return;
        var prerenderHandler = this.get_events().getHandler('prerender'),
        if (prerenderHandler) { prerenderHandler(this, Sys.EventArgs.Empty); }
        SOAjax.XmlTransform(this._xml, this._xslt, control);
        this._rendered = true;
        var renderHandler = this.get_events().getHandler('render'),
        if (renderHandler) renderHandler(this, Sys.EventArgs.Empty);
        this.queueReload();
    },

    queueReload: function() {
        if (this._timeoutID) window.clearTimeout(this._timeoutID);
        // Only queue a reload if we have a valid reload interval (over 100 ms):
        if (this._reloadInterval && this._reloadInterval > 100) {
            this._timeoutID = window.setTimeout(
               String.format("SOAjax.Controls.XmlControl.Refresh('{0}')",
                   this.get_element().id),
                   this._reloadInterval);
        }
    },
    dispose: function() {
        ///<summary>Release resources before control is disposed.</summary>
        var element = this.get_element();
        if (element) { try { $clearHandlers(this.get_element()); } catch (e) { } }
        SOAjax.Controls.XmlControl.callBaseMethod(this, 'dispose'),
    }
}
SOAjax.Controls.XmlControl.registerClass(
    'SOAjax.Controls.XmlControl', Sys.UI.Control);

SOAjax.Controls.XmlControl.Refresh = function(elementID) {
    var xmlControl = $find(elementID);
    if (xmlControl && xmlControl.reload)
        xmlControl.reload();
}
Sys.Application.notifyScriptLoaded();

To create an instance of XmlControl on a page, you can use the $create method of Sys.Component, passing in the xmlUrl and xsltUrl properties. (We discussed the $create method in depth in Chapter 8.) In the following examples, we’ll use the Topics.xslt file (defined earlier in Example 9-3), which transforms the topics from the Catalog service into a repeating list of DIV elements with an anchor tag. Each topic is rendered as the following HTML:

<div><a>AJAX</a></div>

The generated link for each topic will fire a navigation event for the wiki control. The approach we’ll take is to add an AJAX event handler for the click event rather than generate JavaScript code in the XSLT file.

Tip

Tip

When rendering JavaScript in the XSLT, there’s a high likelihood of error. One common point of error is introduced with the apostrophe. One of the bugs that occurs most often in software is the result of invalid output when a user’s name contains an apostrophe, which is common in Irish names such as O’Brien. Regardless, whenever users can input text, there’s a high likelihood of error. For example, if a topic was based on Dave O’Brien and the JavaScript method was rendered by XSLT, the following HTML, with an invalid JavaScript method, would be output, and this would cause an error when added to the page:

<div><a onclick="javascript:alert('Dave O'Brien')">AJAX</a></div>

Instead, it’s best to render pure HTML with no JavaScript in AJAX XSLT implementations and instead attach an event handler to process the click event.

Example 9-9 demonstrates a test page for the XmlControl class that adds a handler to the prerender and render events of XmlControl to add functionality to the rendered HTML. In a real-world use case we would fire a navigation event. The typical use for the dynamic XmlComponent class is a data set that is navigable or that might be updated frequently, instead of data that is rendered once. Because you might have previously attached event handlers, if you are re-rendering the control, you need to clear them to prevent memory leaks. Any time you add handlers, you need to remove them when the content is removed from the page or when the page is closed or reloaded. For example, in the XML control’s pre-render handler, you need to remove any handlers you might have added in a prior rendering before the content is cleared and rendered again.

In the example in Example 9-9, the handler itself simply processes the innerHTML of the target element as in the following code, which simply alerts the content of the link element that was clicked. In the following section, we’ll convert this to a navigational link:

function topicHandler(domEvent, eventArgs){
    alert(domEvent.target.innerHTML);
}

As an alternative to the innerHTML of the element, you can also access attributes of the HTML element. This approach is useful if you are using database-generated unique identifiers as method parameters. The following source code demonstrates a simple handler that is added to the click event. In the next sample, we’ll integrate the XmlControl class into the case study wiki application to see a real-world use with object orientation.

Example 9-9. XmlControl is a staple in the AJAX Web application and is instantiated with an XML data source URL, an XSLT URL, and a DOM element (Web/XmlControl.aspx).

<%@ Page Language="C#" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Service Oriented AJAX: XmlControl test page</title>
    <script type="text/javascript">

        function preRenderHandler(sender, eventArgs){
            var element = sender.get_element();
            var links = element.getElementsByTagName('A'),
            for (var i = 0; i < links.length; i++) {
                $clearHandlers(links[i]);
            }
        }
        function renderHandler(sender, eventArgs){
            var element = sender.get_element();
            var links = element.getElementsByTagName('A'),
            for (var i = 0; i < links.length; i++) {
                $addHandler(links[i], 'click', topicHandler);
            }
        }

        function topicHandler(domEvent, eventArgs){
            // add intellisense to domEvent
            if(domEvent == null){
                domEvent = new Sys.UI.DomEvent(null);
                throw Error.argumentNull();
            }
            alert(domEvent.target.innerHTML);
        }

        function pageLoad() {
            initDebug();
            var props = {
                xmlUrl: '/Web/Services/CatalogService.svc/Default/Topics',
                xsltUrl : '/Web/XML/Topics.xslt'
            };
            var events = { prerender: preRenderHandler, render: renderHandler };
            var xmlElement = $get('testControl'),
            var xmlControl =
               $create(SOAjax.Controls.XmlControl, props, events, null, xmlElement);
        }

    </script>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server">
            <Scripts>
                <asp:ScriptReference Path="~/Script/Controls/SOAjax.js" />
                <asp:ScriptReference Path="~/Script/Controls/XmlControl.js" />
            </Scripts>
        </asp:ScriptManager>
        <div id="testControl" />
    </form>
</body>
</html>
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset