- •preface
- •acknowledgments
- •about this book
- •Who should read this book?
- •Roadmap
- •Code conventions
- •Code downloads
- •Author Online
- •About the title
- •About the cover illustration
- •Rethinking the web application
- •A new design for the Web
- •1.1 Why Ajax rich clients?
- •1.1.1 Comparing the user experiences
- •1.1.2 Network latency
- •1.1.3 Asynchronous interactions
- •1.1.4 Sovereign and transient usage patterns
- •1.1.5 Unlearning the Web
- •1.2 The four defining principles of Ajax
- •1.2.1 The browser hosts an application, not content
- •1.2.2 The server delivers data, not content
- •1.2.3 User interaction with the application can be fluid and continuous
- •1.2.4 This is real coding and requires discipline
- •1.3 Ajax rich clients in the real world
- •1.3.1 Surveying the field
- •1.3.2 Google Maps
- •1.4 Alternatives to Ajax
- •1.4.2 Java Web Start and related technologies
- •1.5 Summary
- •1.6 Resources
- •First steps with Ajax
- •2.1 The key elements of Ajax
- •2.2 Orchestrating the user experience with JavaScript
- •2.3 Defining look and feel using CSS
- •2.3.1 CSS selectors
- •2.3.2 CSS style properties
- •2.3.3 A simple CSS example
- •2.4 Organizing the view using the DOM
- •2.4.1 Working with the DOM using JavaScript
- •2.4.2 Finding a DOM node
- •2.4.3 Creating a DOM node
- •2.4.4 Adding styles to your document
- •2.4.5 A shortcut: Using the innerHTML property
- •2.5 Loading data asynchronously using XML technologies
- •2.5.1 IFrames
- •2.5.2 XmlDocument and XMLHttpRequest objects
- •2.5.3 Sending a request to the server
- •2.5.4 Using callback functions to monitor the request
- •2.5.5 The full lifecycle
- •2.6 What sets Ajax apart
- •2.7 Summary
- •2.8 Resources
- •Introducing order to Ajax
- •3.1 Order out of chaos
- •3.1.1 Patterns: creating a common vocabulary
- •3.1.2 Refactoring and Ajax
- •3.1.3 Keeping a sense of proportion
- •3.1.4 Refactoring in action
- •3.2 Some small refactoring case studies
- •3.2.2 Managing event handlers: Observer pattern
- •3.2.3 Reusing user action handlers: Command pattern
- •3.2.4 Keeping only one reference to a resource: Singleton pattern
- •3.3 Model-View-Controller
- •3.4 Web server MVC
- •3.4.1 The Ajax web server tier without patterns
- •3.4.2 Refactoring the domain model
- •3.4.3 Separating content from presentation
- •3.5 Third-party libraries and frameworks
- •3.5.2 Widgets and widget suites
- •3.5.3 Application frameworks
- •3.6 Summary
- •3.7 Resources
- •Core techniques
- •The page as an application
- •4.1 A different kind of MVC
- •4.1.1 Repeating the pattern at different scales
- •4.1.2 Applying MVC in the browser
- •4.2 The View in an Ajax application
- •4.2.1 Keeping the logic out of the View
- •4.2.2 Keeping the View out of the logic
- •4.3 The Controller in an Ajax application
- •4.3.1 Classic JavaScript event handlers
- •4.3.2 The W3C event model
- •4.3.3 Implementing a flexible event model in JavaScript
- •4.4 Models in an Ajax application
- •4.4.1 Using JavaScript to model the business domain
- •4.4.2 Interacting with the server
- •4.5 Generating the View from the Model
- •4.5.1 Reflecting on a JavaScript object
- •4.5.2 Dealing with arrays and objects
- •4.5.3 Adding a Controller
- •4.6 Summary
- •4.7 Resources
- •The role of the server
- •5.1 Working with the server side
- •5.2 Coding the server side
- •5.2.1 Popular implementation languages
- •5.3 The big picture: common server-side designs
- •5.3.1 Naive web server coding without a framework
- •5.3.2 Working with Model2 workflow frameworks
- •5.4 The details: exchanging data
- •5.4.2 Introducing the planet browser example
- •5.5 Writing to the server
- •5.5.1 Using HTML forms
- •5.5.2 Using the XMLHttpRequest object
- •5.5.3 Managing user updates effectively
- •5.6 Summary
- •5.7 Resources
- •Professional Ajax
- •The user experience
- •6.1 Getting it right: building a quality application
- •6.1.1 Responsiveness
- •6.1.2 Robustness
- •6.1.3 Consistency
- •6.1.4 Simplicity
- •6.1.5 Making it work
- •6.2 Keeping the user informed
- •6.2.1 Handling responses to our own requests
- •6.2.2 Handling updates from other users
- •6.3 Designing a notification system for Ajax
- •6.3.1 Modeling notifications
- •6.3.2 Defining user interface requirements
- •6.4 Implementing a notification framework
- •6.4.1 Rendering status bar icons
- •6.4.2 Rendering detailed notifications
- •6.4.3 Putting the pieces together
- •6.5 Using the framework with network requests
- •6.6 Indicating freshness of data
- •6.6.1 Defining a simple highlighting style
- •6.6.2 Highlighting with the Scriptaculous Effects library
- •6.7 Summary
- •6.8 Resources
- •Security and Ajax
- •7.1 JavaScript and browser security
- •7.1.1 Introducing the “server of origin” policy
- •7.1.2 Considerations for Ajax
- •7.1.3 Problems with subdomains
- •7.2 Communicating with remote services
- •7.2.1 Proxying remote services
- •7.2.2 Working with web services
- •7.3 Protecting confidential data
- •7.3.1 The man in the middle
- •7.3.2 Using secure HTTP
- •7.3.3 Encrypting data over plain HTTP using JavaScript
- •7.4 Policing access to Ajax data streams
- •7.4.1 Designing a secure web tier
- •7.4.2 Restricting access to web data
- •7.5 Summary
- •7.6 Resources
- •Performance
- •8.1 What is performance?
- •8.2 JavaScript execution speed
- •8.2.1 Timing your application the hard way
- •8.2.2 Using the Venkman profiler
- •8.2.3 Optimizing execution speed for Ajax
- •8.3 JavaScript memory footprint
- •8.3.1 Avoiding memory leaks
- •8.3.2 Special considerations for Ajax
- •8.4 Designing for performance
- •8.4.1 Measuring memory footprint
- •8.4.2 A simple example
- •8.5 Summary
- •8.6 Resources
- •Ajax by example
- •Dynamic double combo
- •9.1 A double-combo script
- •9.2 The client-side architecture
- •9.2.1 Designing the form
- •9.2.2 Designing the client/server interactions
- •9.3 Implementing the server: VB .NET
- •9.3.1 Defining the XML response format
- •9.4 Presenting the results
- •9.4.1 Navigating the XML document
- •9.4.2 Applying Cascading Style Sheets
- •9.5 Advanced issues
- •9.5.2 Moving from a double combo to a triple combo
- •9.6 Refactoring
- •9.6.1 New and improved net.ContentLoader
- •9.7 Summary
- •Type-ahead suggest
- •10.1 Examining type-ahead applications
- •10.1.2 Google Suggest
- •10.2.1 The server and the database
- •10.3 The client-side framework
- •10.3.1 The HTML
- •10.3.2 The JavaScript
- •10.3.3 Accessing the server
- •10.5 Refactoring
- •10.5.1 Day 1: developing the TextSuggest component game plan
- •10.5.3 Day 3: Ajax enabled
- •10.5.4 Day 4: handling events
- •10.5.6 Refactor debriefing
- •10.6 Summary
- •11.1 The evolving portal
- •11.1.1 The classic portal
- •11.1.2 The rich user interface portal
- •11.2 The Ajax portal architecture using Java
- •11.3 The Ajax login
- •11.3.1 The user table
- •11.4 Implementing DHTML windows
- •11.4.1 The portal windows database
- •11.4.3 Adding the JS external library
- •11.5 Adding Ajax autosave functionality
- •11.5.1 Adapting the library
- •11.5.2 Autosaving the information to the database
- •11.6 Refactoring
- •11.6.1 Defining the constructor
- •11.6.2 Adapting the AjaxWindows.js library
- •11.6.3 Specifying the portal commands
- •11.6.4 Performing the Ajax processing
- •11.6.5 Refactoring debrief
- •11.7 Summary
- •Live search using XSLT
- •12.1 Understanding the search techniques
- •12.1.1 Looking at the classic search
- •12.1.3 Examining a live search with Ajax and XSLT
- •12.1.4 Sending the results back to the client
- •12.2 The client-side code
- •12.2.1 Setting up the client
- •12.2.2 Initiating the process
- •12.3 The server-side code: PHP
- •12.3.1 Building the XML document
- •12.3.2 Building the XSLT document
- •12.4 Combining the XSLT and XML documents
- •12.4.1 Working with Microsoft Internet Explorer
- •12.4.2 Working with Mozilla
- •12.5 Completing the search
- •12.5.1 Applying a Cascading Style Sheet
- •12.5.2 Improving the search
- •12.5.3 Deciding to use XSLT
- •12.5.4 Overcoming the Ajax bookmark pitfall
- •12.6 Refactoring
- •12.6.1 An XSLTHelper
- •12.6.2 A live search component
- •12.6.3 Refactoring debriefing
- •12.7 Summary
- •Building stand-alone applications with Ajax
- •13.1 Reading information from the outside world
- •13.1.1 Discovering XML feeds
- •13.1.2 Examining the RSS structure
- •13.2 Creating the rich user interface
- •13.2.1 The process
- •13.2.3 Compliant CSS formatting
- •13.3 Loading the RSS feeds
- •13.3.1 Global scope
- •13.3.2 Ajax preloading functionality
- •13.4 Adding a rich transition effect
- •13.4.2 Implementing the fading transition
- •13.4.3 Integrating JavaScript timers
- •13.5 Additional functionality
- •13.5.1 Inserting additional feeds
- •13.5.2 Integrating the skipping and pausing functionality
- •13.6 Avoiding the project’s restrictions
- •13.6.1 Overcoming Mozilla’s security restriction
- •13.6.2 Changing the application scope
- •13.7 Refactoring
- •13.7.1 RSS reader Model
- •13.7.2 RSS reader view
- •13.7.3 RSS reader Controller
- •13.7.4 Refactoring debrief
- •13.8 Summary
- •The Ajax craftsperson’s toolkit
- •A.1 Working smarter with the right toolset
- •A.1.1 Acquiring tools that fit
- •A.1.2 Building your own tools
- •A.1.3 Maintaining your toolkit
- •A.2 Editors and IDEs
- •A.2.1 What to look for in a code editor
- •A.2.2 Current offerings
- •A.3 Debuggers
- •A.3.1 Why we use a debugger
- •A.3.2 JavaScript debuggers
- •A.3.3 HTTP debuggers
- •A.3.4 Building your own cross-browser output console
- •A.4 DOM inspectors
- •A.4.1 Using the Mozilla DOM Inspector
- •A.4.2 DOM inspectors for Internet Explorer
- •A.4.3 The Safari DOM Inspector for Mac OS X
- •A.5 Installing Firefox extensions
- •A.6 Resources
- •JavaScript for object-oriented programmers
- •B.1 JavaScript is not Java
- •B.2 Objects in JavaScript
- •B.2.1 Building ad hoc objects
- •B.2.2 Constructor functions, classes, and prototypes
- •B.2.3 Extending built-in classes
- •B.2.4 Inheritance of prototypes
- •B.2.5 Reflecting on JavaScript objects
- •B.2.6 Interfaces and duck typing
- •B.3 Methods and functions
- •B.3.1 Functions as first-class citizens
- •B.3.2 Attaching functions to objects
- •B.3.3 Borrowing functions from other objects
- •B.3.4 Ajax event handling and function contexts
- •B.3.5 Closures in JavaScript
- •B.4 Conclusions
- •B.5 Resources
- •Ajax frameworks and libraries
- •Accesskey Underlining Library
- •ActiveWidgets
- •Ajax JavaServer Faces Framework
- •Ajax JSP Tag Library
- •Ajax.NET
- •AjaxAC
- •AjaxAspects
- •AjaxCaller
- •AjaxFaces
- •BackBase
- •Behaviour
- •Bindows
- •BlueShoes
- •CakePHP
- •CL-Ajax
- •ComfortASP.NET
- •Coolest DHTML Calendar
- •Dojo
- •DWR (Direct Web Remoting)
- •Echo 2
- •FCKEditor
- •Flash JavaScript Integration Kit
- •Google AjaxSLT
- •Guise
- •HTMLHttpRequest
- •Interactive Website Framework
- •Jackbe
- •JPSpan
- •jsolait
- •JSON
- •JSRS (JavaScript Remote Scripting)
- •LibXMLHttpRequest
- •Mochikit
- •netWindows
- •Oddpost
- •OpenRico
- •Pragmatic Objects
- •Prototype
- •Qooxdoo
- •RSLite
- •Ruby on Rails
- •Sack
- •SAJAX
- •Sarissa
- •Scriptaculous
- •SWATO…
- •Tibet
- •TinyMCE
- •TrimPath Templates
- •Walter Zorn’s DHTML Libraries
- •WebORB for .NET
- •WebORB for Java
- •XAJAX
- •x-Desktop
- •XHConn
- •index
- •Symbols
- •Numerics
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();
},
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 |
},