- •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
448CHAPTER 11
The enhanced Ajax web portal
11.5Adding Ajax autosave functionality
Using Ajax allows us to implement an autosave feature that can be fired by any event without the user knowing that it is happening. Normally, the user would have to click a button to force a postback to the server. In this case, we will be firing the autosave with the onmouseup event, which ends the process of dragging and resizing events. If we were to fire a normal form submission on the onmouseup event, the user would lose all of the functionality of the page, disrupting her workflow. With Ajax, the flow is seamless.
11.5.1Adapting the library
As we mentioned earlier, the code from JavaScript DHTML libraries is normally cross-browser compliant, which frees us from spending time getting cross-browser code to work correctly. If you look at the code in the external JavaScript file, AjaxWindow.js, you’ll see a lot of functionality (which we will not discuss here because of its length). There are functions that monitor the mouse movements, and one function that builds the windows. There are functions that set the position of the windows, and another function that sets the size. Out of all of these functions, we need to adapt only one to have our window save back to the database with Ajax.
Adapting the DHTML library for Ajax
The DHTML library functions for dragging and resizing windows use many event handlers and DOM methods to overcome the inconsistencies between browsers. The dragging and resizing of the windows is completed when the mouse button is released (“up”). Therefore, we should look for a function that is called with the onmouseup event handler in the AjaxWindow.js file. It contains the following code, which is executed when the mouse button is released:
document.onmouseup = function(){ bDrag = false;
bResize = false; intLastX = -1;
document.body.style.cursor = "default"; elemWin="";
bHasMoved = false;
}
In this code, a lot of booleans are being set to false to indicate that their actions have been canceled. The cursor is being set back to the default. The line that we need to change is the one where the elemWin reference is being canceled. At this point, we want to take the reference and pass it to another function to initialize our XMLHttpRequest object, in order to transfer the information to the server.
Adding Ajax autosave functionality |
449 |
|
|
Although sometimes when we adapt libraries, it might take a lot of trial and error to adapt them to our needs, in this case, the functionality is pretty straightforward. Just add the following line, shown in bold, to your document’s onmouseup event handler:
document.onmouseup = function(){ bDrag = false;
bResize = false; intLastX = -1;
document.body.style.cursor = "default";
if(elemWin && bHasMoved)SaveWindowProperties(elemWin);
bHasMoved = false;
}
The bold line in the previous code snippet checks to make sure that the object has been moved or resized and that the element still exists. If the user did not perform either of these actions, then there would be no reason to send the request to the server. If one of these actions was performed, we pass the element reference to the function SaveWindowProperties(), which initiates the request to the server.
Obtaining the active element properties
After the user has moved or resized an element, we must update the server with the new parameters. The DHTML window library uses CSS to position the elements and to set their width and height. This means that all we have to do is obtain the database ID, the coordinates, and the size of the window. We can obtain the coordinates and size by looking at the CSS parameters assigned to the window that had focus. We then can take these new parameters and send them to the server to be saved in the database with Ajax (listing 11.10).
Listing 11.10 SaveWindowProperties() function
function SaveWindowProperties(){ |
|
|
|
winProps = "ref=" + |
|
|
|
elemWin.id; |
b Obtain window ID |
|
|
winProps += "&x=" + |
|
c Find |
|
|
|||
parseInt(elemWin.style.left); |
|
||
|
window |
||
winProps += "&y=" + |
|
position |
|
parseInt(elemWin.style.top); |
|
|
|
winProps += "&w=" + |
|
d Grab |
|
|
|||
parseInt(elemWin.style.width); |
|
||
|
window size |
||
winProps += "&h=" + |
|
|
|
parseInt(elemWin.style.height); |
|
e Call Settings function |
|
Settings("saveSettings",winProps); |
|
||
elemWin = ""; |
f Remove element reference |
}
450CHAPTER 11
The enhanced Ajax web portal
As you can see in listing 11.11, we obtain the ID of the window bby referencing the window object. The ID that we obtained was assigned to the window when the library built it. When it assigns an ID, it appends win in front of the number from the database id column; we can see that by looking at the JavaScript code that is building the windows.
The x and y positions of the window are obtained c by referencing the left and top properties in the stylesheet. We also use the stylesheet properties to obtain the size dof the window by referencing its width and height properties.
After obtaining the information, we can call another function, Settings() e, which we will be creating shortly, to send our request to the server. Once we call the function, we should remove the element object from our global variable elemWin f. To do this, we assign an empty string to the variable elemWin. Now with the SaveWindowProperties() function complete, we can initiate our silent Ajax request to the server with the JavaScript function Settings().
11.5.2Autosaving the information to the database
Ajax lets us send information to the server without the user even knowing it is happening. We can see this in action with two projects in this book. We can easily submit requests to the server as a result of both monitoring keystrokes, as we do in the type-ahead suggest (chapter 10), and monitoring mouse movements, as we do in this chapter. This invisible submission is great for developers since we can update the user’s settings without him having to lift a finger. In most cases, reducing steps increases the user’s satisfaction. For this application, the action of the user releasing the mouse button is all we need to initiate the XMLHttpRequest object. Now it’s time to initiate the process to send the request to the server.
The client: sending the silent request
The XMLHttpRequest process in this case will not require anything sophisticated. The user’s interaction with the form sends all of the form properties to our function. We first need to initialize the XMLHttpRequest object:
function Settings(xAction,xParams){ var url = xAction + ".servlet"; var strParams = xParams;
var loader1 = new net.ContentLoader(url, BuildSettings, ErrorBuildSettings, "POST",
strParams);
}
Adding Ajax autosave functionality |
451 |
|
|
For the function Settings(), we are passing the action string that contains all of our window’s properties. We attach the parameters that we’re going to post back to the server. If we get a successful round-trip to the server, the loader will call the function BuildSettings(). If we get an error during the round-trip, we will call the function ErrorBuildSettings():
function BuildSettings(){
strText = this.req.responseText; document.getElementById("divSettings").innerHTML = strText;
}
function ErrorBuildSettings(){
alert('There was an error trying to connect to the server.'); document.getElementById("divSettings").style.display = "none";
}
The function BuildSettings() shown here is quite basic; all we are doing is finishing up our XMLHttpRequest received from the server. We can set a message on the portal status bar to show that we have updated the information on the server. We can add an error message to the status bar if we encounter a problem updating the information on the server. We also generate an alert, which tells the user of the error, but will also disrupt their workflow. We presented produc- tion-ready notification mechanisms in chapter 6, and leave it as an exercise for the reader to integrate those systems into the portal. Now let's see what happens on the server.
The server: gathering information from the client
All we have left to do is to extract the values from our form submission. The values were sent by our XMLHttpRequest object, which was triggered by the onmouseup event handlers. We need to create our SQL query with this information and update the record in the database to save the new information. We define an UpdateServlet for this purpose, which is shown in listing 11.11.
Listing 11.11 UpdateServlet.java (mapped to 'saveSettings.servlet')
public class UpdateServlet extends HttpServlet { protected void doPost(
HttpServletRequest request, HttpServletResponse response
)throws ServletException, IOException{
String windowId= |
|
b Get unique ID from request |
request.getParameter("ref"); |
||
HttpSession session=request.getSession(); |
||
PortalWindow window=(PortalWindow) |
||
(session.getAttribute |
c Get Window object from session |
|
("window_"+windowId)); |
window.setXPos(getIntParam(request,"x"));
452 |
CHAPTER 11 |
|
|
|
|
The enhanced Ajax web portal |
|
|
|
|
window.setYPos(getIntParam(request,"y")); |
|||
|
window.setWidth(getIntParam(request,"w")); |
|||
|
window.setHeight(getIntParam(request,"h")); |
|||
|
DBUtil.savePortalWindow(window); |
d Save changes |
||
|
Writer writer=response.getWriter(); |
|
Return simple text reply |
|
|
writer.write("Save Complete"); |
|
|
|
|
|
|
||
|
writer.flush(); |
|
|
}
private int getIntParam(HttpServletRequest request, String param) { String str=request.getParameter(param);
int result=Integer.parseInt(str); return result;
}
}
Given the window ID as a request parameter b, we can extract the PortalWindow from session c and update its geometry based on further request parameters. We then call another method on our DBUtil object to save the portal window settings in the database d. Again, the implementation that we’ve provided here in listing 11.12 has been written to be simple and easy to translate to other languages.
Listing 11.12 savePortalWindows() method
public static void savePortalWindow(PortalWindow window){ Connection conn=getConnection();
int x=window.getXPos(); int y=window.getYPos(); int w=window.getWidth(); int h=window.getHeight(); int id=window.getId();
String sql="UPDATE portal_windows SET xPos="+x +",yPos="+y
+",width="+w
+",height="+h
+" WHERE id="+id; try{
Statement stmt=conn.createStatement(); stmt.execute(sql);
stmt.close();
}catch (SQLException sqlex){
}
}
The code in listing 11.12 is very straightforward. We read the relevant details from the PortalWindow object and construct a SQL update statement accordingly. Rather than returning any JavaScript this time, we issue a simple text acknowledgment.