Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ajax In Action (2006).pdf
Скачиваний:
63
Добавлен:
17.08.2013
Размер:
8.36 Mб
Скачать

Refactoring 491

location.href.split("?")[0] + "?q=" + document.Form1.user.value + "'>Save Search</a>";

document.getElementById("spanSave").innerHTML = strLink;

The code in listing 12.11 generates a dynamic link to our current search page and adds the querystring parameter q with the value of the textbox. The querystring parameter is what allows us to remember the search. This new link is then added to the span on the page so the user can select the link and send it to others or bookmark it by clicking on the link and setting it to their favorites for future use. In listing 12.12, we obtain the querystring value from the URL when the page loads and then perform the search automatically so the results are shown.

Listing 12.12 Obtaining the querystring value and performing the search

window.onload = function(){

var strQS = window.location.search; var intQS = strQS.indexOf("q="); if(intQS != -1){

document.Form1.user.value = strQS.substring(intQS+2); GrabNumber();

}

}

We add a handler for the onload event to our window object that will execute a function when the page is loaded. We check to see if our querystring value is in the URL; if it is, we obtain the value. The querystring value is placed inside the textbox, and then the GrabNumber() function is executed automatically to build the results table. Adding this code lets us bookmark the search pages and have the search results appear when we come to the page, instead of having to type in the value each time. This makes our Ajax project even more user-friendly.

12.6 Refactoring

It’s time to take our XSLT live search to the next level by—you know the drill— componentizing! We need to take this nifty script and refactor it until we have an object-oriented reusable component. So let’s start with the client-side XSLT processing. This example is different than all the others in the sense that it handles all the DOM manipulation aspects of response handling with XSLT. So let’s start there. We should be able to refactor our XSLT processing in such a way that we can use it with other components—not just the live search. Once we do that, we’ll

492CHAPTER 12

Live search using XSLT

focus on refactoring the live search in such a way that it can be quickly added to any page as an easy-to-use pluggable component.

12.6.1An XSLTHelper

We’ve gone through a lot of trouble to learn the ins and outs of XSLT processing on the client side. For example, we’ve noticed that there are completely different APIs for doing XSLT processing on the client based on whether we’re targeting an IE browser or a Mozilla-based browser. And each API has its own set of quirks and peculiarities. So, it would be a shame for us not to encapsulate that hardearned knowledge so that our colleagues who come behind us don’t have to go through the same pains to do some seemingly simple XSLT transformations. Therefore, let’s do just that by creating an XSLTHelper object to encapsulate all of our XSLT concerns.

All XSLT processing typically requires two sources of information: the XML document to transform and the XSL document to provide the transformation rules. With that in mind, let’s write a constructor for our helper that will give us a way to store that state:

function XSLTHelper( xmlURL, xslURL ) { this.xmlURL = xmlURL;

this.xslURL = xslURL;

}

The constructor is probably one of the simplest you’ve seen in this book yet. It stores the URLs for the documents we just noted: the XML data document and the XSLT transformation document. But before we get too giddy about the simplicity of it all, we need to think about an API to support graceful degradation. You’ll note that our script conditionally performs only XSLT processing if the browser supports it. So if we’re writing a helper, it would be nice for the helper to provide an API to tell the client whether or not it can perform XSLT operations. However, instantiating some object with XSLT in its name just to find out whether XSLT is supported doesn’t seem right. The solution to this conundrum is an API function that’s not scoped to the prototype object but rather to the constructor function itself. We can think of this function much like a static method in the Java world. The intent is that a client should be able to write code that looks something like this:

XSLTHelper.isXSLTSupported();

rather than having to instantiate an object like this:

Refactoring 493

var helper = new XSLTHelper( 'phoneBook.xml', 'transformation.xsl' );

var canDoThis = helper.isXSLTSupported();

So let’s accommodate our inquisitive users with a pseudo-static method, which is expressed as follows:

XSLTHelper.isXSLTSupported = function() {

return (window.XMLHttpRequest && window.XSLTProcessor ) || XSLTHelper.isIEXmlSupported();

}

XSLTHelper.isIEXmlSupported = function() { if ( ! window.ActiveXObject )

return false;

try { new ActiveXObject("Microsoft.XMLDOM"); return true; }

catch(err) { return false; }

}

There’s nothing new here. The logic is identical to the logic defined earlier; we’ve just encapsulated that knowledge about how to detect XSLT support. I’m sure someone will thank us for this. So now we can get on to the business of fleshing out the rest of the XSLTHelper API.

Let’s keep things simple. How about saying that we’ll have a single method for clients of our class to call to perform XSLT processing? Our helper will have ancillary methods to separate the responsibilities of all the internal logic, but we’ll provide a single API for our clients to use. The semantics will be as follows:

var helper = new XSLTHelper ( 'phoneBook.xml', 'transformation.xsl' );

helper.loadView( 'someContainerId' );

In this example usage, the phoneBook.xml document should be transformed into HTML by the transformation.xsl document, and the resulting HTML should be placed within the element whose ID is someContainerId. Let’s further specify that the parameter to loadView() can be either a string representing the ID of an element or the element itself. We’ll internally figure out what we’re dealing with and react accordingly. And, by the way, if the client doesn’t care to reuse the helper instance, we can express all this with a single line of code:

new XSLTHelper('phoneBook.xml', 'transformation.xsl').loadView('someContainerId');

Now that we’ve defined our API and its semantics, let’s implement it as shown in listing 12.13.

494CHAPTER 12

Live search using XSLT

Listing 12.13 The loadView method

loadView: function ( container ) {

if ( ! XSLTHelper.isXSLTSupported() ) return;

this.xmlDocument

= null;

this.xslStyleSheet =

null;

this.container

=

$(container);

b Check for XSLT support

c Reinitialize

helper state

new Ajax.Request( this.xmlURL,

d Request documents

{onComplete:

this.setXMLDocument.bind(this)} );

new Ajax.Request( this.xslURL, {method:"GET",

onComplete: this.setXSLDocument.bind(this)} );

},

The first thing the loadView() method does is makes sure it’s operating within a browser runtime that supports XSLT b. The client should have already done this, as in our earlier example, but just in case the user of our code is sloppy, we take a better-safe-than-sorry approach and check again. Second, the method sets the state variables holding the XML and XSL documents to null and sets the reference to the container to be updated c. Lastly, we send the Ajax requests to retrieve the XML and XSL documents d. When the server responds to the request for the XML document, the setXMLDocument() method is called. Likewise, when the server responds to the request for the XSL document, the setXSLDocument() method is called. These functions are shown in listing 12.14.

Listing 12.14 Setting the XML and XSL documents

setXMLDocument: function(request) { this.xmlDocument = request.responseXML; this.updateViewIfDocumentsLoaded();

},

setXSLDocument: function(request) { this.xslStyleSheet = request.responseXML; this.updateViewIfDocumentsLoaded();

},

b Perform XSLT transform
Initialize transformer

Refactoring 495

These methods set the state variables of the XSLTHelper corresponding to the XML document and XSL document, respectively. They then call the updateViewIfDocumentsLoaded() method, which checks to see if both documents have been initialized, and if this is the case, updates the view. The updateViewIfDocumentsLoaded() method is implemented as shown here:

updateViewIfDocumentsLoaded: function() {

if ( this.xmlDocument == null || this.xslStyleSheet == null ) return;

this.updateView();

},

Once both responses have come back from the server, we are ready to update the UI. We know that both responses have come back when both the this.xmlDocument and the this.xslStyleSheet state variables are non-null. The updateView() method is shown in listing 12.15.

Listing 12.15 Updating the view

updateView: function () {

if ( ! XSLTHelper.isXSLTSupported() ) return;

if ( window.XMLHttpRequest && window.XSLTProcessor ) this.updateViewMozilla();

else if ( window.ActiveXObject ) this.updateViewIE();

},

As we’ve noted already, we require different implementations for each browser type being supported, so we’ve separated out the details. Let’s look at each implementation, beginning with the Mozilla implementation, shown in listing 12.16.

Listing 12.16 Updating the view in Mozilla

updateViewMozilla: function() {

var xsltProcessor = new XSLTProcessor(); xsltProcessor.importStylesheet(this.xslStyleSheet); var fragment = xsltProcessor.

TransformToFragment( this.xmlDocument, document);

this.container.innerHTML = "";

c

Update

this.container.appendChild(fragment);

 

the UI

},