Adam howitt Duo Consulting 7/7/20061223PN How to Design a large AJAX Application Introduction I'll cover the process I have developed in the course of implementing two AJAX applications as a developer for Duo Consulting in Chicago. This approach has made it easier for me to work with the design team, produce estimates for this type of project and communicate what is involved each step of the way to the project managers for scheduling purposes Background experimenting with AJAX requests to handle tasks that really shouldnt trigger sun I re-designed WalkJog Run to utilize the Google maps API over a year ago and beg complete page request. Shortly after that one of our clients, Chicago Park District wanted to redesign their seasonal registration application to maximize the number of people who could register for their seasonal programs and make it easier for people to use the site. After a detailed discovery phase we proposed an AJAX browser interface to make it simpler to drilldown through their online offerings and ultimately to locate a program and register. Inspired by the iPod menu concept, we developed and built ar applicationwhich served 3000 orders in the first 3 minutes of summer registration Since then I worked on a second application which sits on top of the custom CMs we implement for clients to allow them to generate Microsoft Word proposals to send to clients from the data that drives the website without cutting and pasting. This application was built on the prototype AJAX library but uses the same design principles Why use AJAX? AJAX isn't just a new buzzword or a cream cleanser from Procter& Gamble. It's a great leap forward(and a small step back) for usability. Like flash, when it is used correctly and appropriately it can dramatically enhance the user experience by making web applications respond faster and make interactions less jarring than having a page reload AJAX also offers a performance benefit for your web server since you begin serving smaller chunks of pages instead of complete pages every time. For Cold Fusion this also means only the business logic pertaining to the requested chunk needs to be recalculated instead of the logic for a whole page so your application server will get some relief. Ihttp://www.duoconsulting.com/ http://www.walkiogrun.net shttp://www.google.com/apis/maps/ http://duoconsulting.com/experience/chicago-park-district-registration/index.cfm http://programs.chicagoparkdistrictcom/programbrowserl http://www.duocms.com httpgeneratorduodesign.com:sElecttheproposalgeneratoronceyouhaveloggedinwiththeusername demo(@duoconsulting com and password demo 1/8
Adam Howitt Duo Consulting 7/7/2006 12:23 PM How to Design a large AJAX Application Introduction I’ll cover the process I have developed in the course of implementing two AJAX applications as a developer for Duo Consulting 1 in Chicago. This approach has made it easier for me to work with the design team, produce estimates for this type of project and communicate what is involved each step of the way to the project managers for scheduling purposes. Background I re-designed WalkJogRun 2 to utilize the Google maps API 3 over a year ago and begun experimenting with AJAX requests to handle tasks that really shouldn’t trigger a complete page request. Shortly after that one of our clients, Chicago Park District4 , wanted to redesign their seasonal registration application to maximize the number of people who could register for their seasonal programs and make it easier for people to use the site. After a detailed discovery phase we proposed an AJAX “browser interface” to make it simpler to drilldown through their online offerings and ultimately to locate a program and register. Inspired by the iPod menu concept, we developed and built an application5 which served 3000 orders in the first 3 minutes of summer registration. Since then I worked on a second application which sits on top of the custom CMS6 we implement for clients to allow them to generate Microsoft Word proposals7 to send to clients from the data that drives the website without cutting and pasting. This application was built on the prototype AJAX library but uses the same design principles. Why use AJAX? AJAX isn’t just a new buzzword or a cream cleanser from Procter & Gamble. It’s a great leap forward (and a small step back) for usability. Like flash, when it is used correctly and appropriately it can dramatically enhance the user experience by making web applications respond faster and make interactions less jarring than having a page reload. AJAX also offers a performance benefit for your web server since you begin serving smaller chunks of pages instead of complete pages every time. For ColdFusion this also means only the business logic pertaining to the requested chunk needs to be recalculated instead of the logic for a whole page so your application server will get some relief. 1 http://www.duoconsulting.com/ 2 http://www.walkjogrun.net 3 http://www.google.com/apis/maps/ 4 http://duoconsulting.com/experience/chicago-park-district-registration/index.cfm 5 http://programs.chicagoparkdistrict.com/programBrowser/ 6 http://www.duocms.com 7 http://generator.duodesign.com : Select the proposal generator once you have logged in with the username demo@duoconsulting.com and password demo 1/8
Adam howitt o Consulter 7/720061223PM sign Work from a mockup you can doodle on. Draw on the mockup to identify discreet sections of the applications and label them. Do panels interact or are they isolated? If there is a relationship between an interaction in one panel and the display of information draw it with arrows and callouts. This helps to communicate the logical breakdown of the application with other developers. An example marked up mockup for the Chicago Park District program browser application might look like this chicago park district home events grams in the news parks facilites Program Registration Summer 2006 Session June 19-August 27 Online registration is now open! Walk in registration opens 04/22/2006 9: 00 AM Find Programs Program Groups pI Clubs and Game Program Search Results Wishlist 891011:127 Why create D Only show programs available for online registration(highlighted Aquatic Exercise- Ages: 60 8 Ove estriction: None Sign in to your account (Il 0619 747,6001 ender: Co-Rec Create one now 19/2006·8/27/2006 skill Level: open to All Available slots: I w Shopping Cart anticipant Firstname Lastname (Add to Cart D)(cancel Your cart is empty ase Fee!丰0.00 In the example I have numbered the interactions on the mockup for the sake of space but these correspond to the following interactions 1. selectltem(panel 3. selectltem(panel) 4. selectltem(panel) /8
Adam Howitt Duo Consulting 7/7/2006 12:23 PM Design Work from a mockup you can doodle on. Draw on the mockup to identify discreet sections of the applications and label them. Do panels interact or are they isolated? If there is a relationship between an interaction in one panel and the display of information, draw it with arrows and callouts. This helps to communicate the logical breakdown of the application with other developers. An example marked up mockup for the Chicago Park District program browser application might look like this: 5 1 11 12 9 8 6 7 2 4 3 10 In the example I have numbered the interactions on the mockup for the sake of space but these correspond to the following interactions: 1. selectItem(panel) 2. selectItem(panel) 3. selectItem(panel) 4. selectItem(panel) 2/8
Duo Consulting 7/7/20061223PN 5. getHelp(helptopic) 6. view ProgramDetails(activity code) 7. save Towishlist(activity code) 8. check Availability(activity code) 9. addTocart(activity code, participant) 10. cancelRegistration(activity code) 11. load WishlistScreen(mode) 12. load WishlistScreen(mode) Obviously, there are huge gaps in this model. Ideally you would take each colored section of the diagram and model each of the states(screens )and continue labeling Interactions Identify each interaction(a function) and work out the parameters required to allow the function to work as an isolated item. Functions should be able to perform their task without going beyond global variables and the argument scope in most cases. Create a text file and group the function specifications based on the discreet areas of the application e.g. cart, wishlist, account, help. Add JavaScript comments to describe the interaction and include any pseudo code to describe the steps for a function where there are important validation tests, lookups or steps to be performed Are there any panels of the site which have related Java Script functions that are repeated? Whether an undefined number of repeats or a defined number it is preferred to build a JavaScript object. This reduces code duplication, provides variables local to each instance and generally makes things easier. Prefix functions that act on a specific instance of a panel with the name of the panel e.g. Cart: function deleteltemO Look at the interactions which fetch remote data(make an AJAX call). Think about each in turn and make a decision whether each request must be unique(e.g. get user cart)or whether the information doesnt change at all (e.g. get help panel). Update the comments for each function making a remote call to explain that it is remote and indicate if it is unique or not Next, consider the types of remote call you are making. Is the complete result sent back from the remote page as rendered hTmL (rendered result) which can be inserted directly into a destination panel or will the result contain JavaScript variables, XML or a custom structured result(functional result) to be processed in a specific way? An example of rendered result is that a help file chunk can be simply rendered in a given Div element so it is sent back as HTML; a functional result example would be a remote address lookup function where you send an address and receive back a complex object to be inspected which then updates various fields on a form. Add a comment to each AJAX call to say whether the result is rendered or functional Now is probably a good time to walk through your design with another programmer and the designer to make sure you understood the designer's intentions and make sure that your approach isn't missing some obvious efficiencies. An example design document for 3/8
Adam Howitt Duo Consulting 7/7/2006 12:23 PM 5. getHelp(helptopic) 6. viewProgramDetails(activity_code) 7. saveToWishlist(activity_code) 8. checkAvailability(activity_code) 9. addToCart(activity_code,participant) 10. cancelRegistration(activity_code) 11. loadWishlistScreen(mode) 12. loadWishlistScreen(mode) Obviously, there are huge gaps in this model. Ideally you would take each colored section of the diagram and model each of the states (screens) and continue labeling interactions. Identify each interaction (a function) and work out the parameters required to allow the function to work as an isolated item. Functions should be able to perform their task without going beyond global variables and the argument scope in most cases. Create a text file and group the function specifications based on the discreet areas of the application e.g. cart, wishlist, account, help. Add JavaScript comments to describe the interaction and include any pseudo code to describe the steps for a function where there are important validation tests, lookups or steps to be performed. Are there any panels of the site which have related JavaScript functions that are repeated? Whether an undefined number of repeats or a defined number it is preferred to build a JavaScript object. This reduces code duplication, provides variables local to each instance and generally makes things easier. Prefix functions that act on a specific instance of a panel with the name of the panel e.g. Cart:function deleteItem() Look at the interactions which fetch remote data (make an AJAX call). Think about each in turn and make a decision whether each request must be unique (e.g. get user cart) or whether the information doesn’t change at all (e.g. get help panel). Update the comments for each function making a remote call to explain that it is remote and indicate if it is unique or not. Next, consider the types of remote call you are making. Is the complete result sent back from the remote page as rendered HTML (rendered result) which can be inserted directly into a destination panel or will the result contain JavaScript variables, XML or a custom structured result (functional result) to be processed in a specific way? An example of a rendered result is that a help file chunk can be simply rendered in a given DIV element so it is sent back as HTML; a functional result example would be a remote address lookup function where you send an address and receive back a complex object to be inspected which then updates various fields on a form. Add a comment to each AJAX call to say whether the result is rendered or functional. Now is probably a good time to walk through your design with another programmer and the designer to make sure you understood the designer’s intentions and make sure that your approach isn’t missing some obvious efficiencies. An example design document for 3/8
Adam howitt Duo Consulting 77/20061223PM the subset of functionality described in the earlier Chicago Park District mockup might look something like this program Browser. txt /Search Panels SearchPanel: selectItem(panel) / if panel is between 1 and 3 retrieve the contents of the next panel (getResults) else retrieve the program listing * SearchPanel: getResults(target panel, filter) /* based on the given filter make a non-unique rendered x call to grab the contents of the panel and place it in the target panel * //Help getHelp(help topic)t / make non-unique rendered AJAX call to grab the help text for help topic and place it in the elp div 7/Program Search Result riew ProgramDetails(activity code)( / expand the hidden divs for the panel labeled with the activity code and check availability * saveToWishlist(activity code / make a unique functional AJAX call to return a result ndicate is the item already in the cart is the cart full 4/8
Adam Howitt Duo Consulting 7/7/2006 12:23 PM the subset of functionality described in the earlier Chicago Park District mockup might look something like this: programBrowser.txt //Search Panels SearchPanel:selectItem(panel) { /* if panel is between 1 and 3 retrieve the contents of the next panel (getResults), else retrieve the program listing */ } SearchPanel:getResults(target_panel,filter) { /* based on the given filter make a non-unique rendered AJAX call to grab the contents of the panel and place it in the target_panel */ } //Help getHelp(help_topic) { /* make non-unique rendered AJAX call to grab the help text for help_topic and place it in the help div */ } //Program Search Result viewProgramDetails(activity_code) { /* expand the hidden divs for the panel labeled with the activity_code and check availability */ } saveToWishlist(activity_code) { /* make a unique functional AJAX call to return a result to indicate - is the user logged in - is the item already in the cart - is the cart full 4/8
Adam howitt o Consulter 7/720061223PM Pass the result to handlesaveToWishlist( */ checkAvailability(activity code)( / make a unique functional AJAX call to return a result to indicate if there is available inventory and pass the result to andlecheckAvailability()*/ handle CheckAvailability(result)t /* if none available set inventory to zero and change the class and text of the inventory box to indicate that the item is sold out, else update the inventory box to the returned value * adaToCart(activity code, participant) cancelRegistration(activity code) t / hide the more info div for the activity code * loadwishlistscreen(mode) / make a non-unique rendered AJAx call to retrieve the content matching the the wishlist div accordingly handlesaveTowishlist(result / based on the result either refresh the wishlist panel to reflect date the wishlist p 5/8
Adam Howitt Duo Consulting 7/7/2006 12:23 PM Pass the result to handleSaveToWishlist() */ } checkAvailability(activity_code) { /* make a unique functional AJAX call to return a result to indicate if there is available inventory and pass the result to handleCheckAvailability() */ } handleCheckAvailability(result) { /* if none available set inventory to zero and change the class and text of the inventory box to indicate that the item is sold out, else update the inventory box to the returned value */ } addToCart(activity_code,participant) { /* skipped for brevity... */ } cancelRegistration(activity_code) { /* hide the more info div for the activity_code */ } //Wishlist loadWishlistScreen(mode) { /* make a non-unique rendered AJAX call to retrieve the content matching the mode passed in and update the wishlist div accordingly */ } handleSaveToWishlist(result) { /* based on the result either -refresh the wishlist panel to reflect the new item -update the wishlist panel with an error message */ 5/8
Adam howitt Duo Consulting 7/720061223PM Again, this is by no means complete but gives you an idea of the pre-work to do at this Coding the application The first thing you will want to do is to take each of the sections of your JavaScript design document and make each section its own js file to be included in the head of you document. Smaller chunks make it more obvious where to look when you are trying to debug the application You will need to work through each of those files next and flesh out the JavaScript. If possible, start with the panels least connected and work your way to the most connected panels Assuming all went according to plan during the review how exactly do you make the different types of AJAX calls mentioned above? To recap Non-unique rendered The result from this call is an HTML fragment and is inserted AJAX cal directly into the contents of a given target div. Non-unique means that the result will be cached at the client minimally and at the server too if you have coded your remote handling page to serve cached results Unique rendered AJAX The result from this call is an HTML fragment and is inserted directly into the contents of a given target div. The goal is to serve the latest information or information which is volatile so each request is not cached by the browser or the server Non-unique functional Results from this call is either a simple result or a complex AJAX call object in either XML or a custom string to be parsed by the client. Non-unique means that the result will be cached at the client minimally and at the server too if you have coded your remote handling page to serve cached results Unique functional Results from this call is either a simple result or a complex AJAX call object in either XML or a custom string to be parsed by the client. The goal is to serve the latest information or information which is volatile so each request is not cached by the browser or the server To begin, you must include the prototype. js libraries if you havent already done so This currently loads the current version of the library(at this point 1.6.0) 6/8
Adam Howitt Duo Consulting 7/7/2006 12:23 PM } Again, this is by no means complete but gives you an idea of the pre-work to do at this stage. Coding the application The first thing you will want to do is to take each of the sections of your JavaScript design document and make each section its own .js file to be included in the head of your document. Smaller chunks make it more obvious where to look when you are trying to debug the application: You will need to work through each of those files next and flesh out the JavaScript. If possible, start with the panels least connected and work your way to the most connected panels. Assuming all went according to plan during the review how exactly do you make the different types of AJAX calls mentioned above? To recap: Non-unique rendered AJAX call The result from this call is an HTML fragment and is inserted directly into the contents of a given target div. Non-unique means that the result will be cached at the client minimally and at the server too if you have coded your remote handling page to serve cached results. Unique rendered AJAX call The result from this call is an HTML fragment and is inserted directly into the contents of a given target div. The goal is to serve the latest information or information which is volatile so each request is not cached by the browser or the server. Non-unique functional AJAX call Results from this call is either a simple result or a complex object in either XML or a custom string to be parsed by the client. Non-unique means that the result will be cached at the client minimally and at the server too if you have coded your remote handling page to serve cached results. Unique functional AJAX call Results from this call is either a simple result or a complex object in either XML or a custom string to be parsed by the client. The goal is to serve the latest information or information which is volatile so each request is not cached by the browser or the server. To begin, you must include the prototype.js libraries if you haven’t already done so: This currently loads the current version of the library (at this point 1.6.0). 6/8
Adam howitt Duo Consulting 7/720061223PM Rendered results Rendered results require the user of the prototype function AJAX Updater which looks ike this var reportError function (t)t alert('error t status tt statusText) function getHelp(helptext)( var url =index. cfm? fuseaction=cpd gethelp 'i var params ='&helptype='thelptext var ajax new Ajax Updater my DivId [method: get' parameters: params onFailure: reporterror, Breaking this down into more manageable chunks we see three parameters passed to the function. The first is the container to be updated on successful completion of the request; the second is the URL of the remote page to retrieve the rendered HTML and lastly, the options parameter which is further broken down into three elements. Method specifies that the parameters should be submitted using get as opposed to post Parameters is a query string beginning with an ampersand e.g. &id=l"and lastly on Failure names a function to execute should there be a transmission error with the AJAX call It is as simple as that to serve up a rendered chunk of HTML content! The scriptaculous wikiathttp://wiki.script.aculo.us/scriptaculous/show/ajax.Updaterhasamoreindepth outline of the parameters if you need further information Functional results Functional results require the user of the prototype function AJAX Request which looks like th var handler func function //handle result var reportError function(t) alert('Error t status+ t statustext)i function checkAvailability(activity code) var url ='index. cfm? fuseaction=cpd. checkAvailability'i act⊥vit new Ajax Request( Parameters: params handler func, 7/8
Adam Howitt Duo Consulting 7/7/2006 12:23 PM Rendered results Rendered results require the user of the prototype function AJAX.Updater which looks like this: var reportError = function(t) { alert('Error ' + t.status + ' -- ' + t.statusText); } function getHelp(helptext) { var url = 'index.cfm?fuseaction=cpd.gethelp'; var params = '&helptype='+helptext; var ajax = new Ajax.Updater ( ’myDivId’, url, {method: 'get', parameters: params, onFailure: reportError, } ); } Breaking this down into more manageable chunks we see three parameters passed to the function. The first is the container to be updated on successful completion of the request; the second is the URL of the remote page to retrieve the rendered HTML and lastly, the options parameter which is further broken down into three elements. Method specifies that the parameters should be submitted using get as opposed to post. Parameters is a query string beginning with an ampersand e.g. “&id=1” and lastly onFailure names a function to execute should there be a transmission error with the AJAX call. It is as simple as that to serve up a rendered chunk of HTML content! The scriptaculous wiki at http://wiki.script.aculo.us/scriptaculous/show/Ajax.Updater has a more in depth outline of the parameters if you need further information. Functional results Functional results require the user of the prototype function AJAX.Request which looks like this: var handlerFunc = function(t) { //handle result } var reportError = function(t) { alert('Error ' + t.status + ' -- ' + t.statusText); } function checkAvailability(activity_code) { var url = 'index.cfm?fuseaction=cpd.checkAvailability'; var params = '&activity_code='+helptext; new Ajax.Request( url, {parameters: params, onSuccess:handlerFunc, 7/8
Adam howitt Duo Consulting 7/7/20061223PN onFailure: errfunc))i The major difference between this and the ajaX Updater function is that there is no container passed to be updated and there is an additional option specified"on Success which tells the function to send the results to handler Func(. Again, the reference wiki at scriptaculous has a fuller description There is no support I could find for prototype to be able to make a request unique so I rolled my own. Both of the above scripts can be modified to add a line getUniqueToken(url This line references a function which, when passed a URL will append a string representing the current date and time down to the millisecond such that a user's browser is unlikely to be able to submit the same request in that exact millisecond function getUniqueToken(url)t var dt new Date(i var dtstring ='+dt. getFullYear()+ dt. getMonth()+ dt getDate()+dt. getHours ()t dt. getMinutes()+ dt. getMilliseconds() return dataurl This should be enough to get you started with the an AJAX application utilizing Prototype to build a functional application. For an example of a hand rolled AJAX applicationvisithttp://programs.chicagoparkdistrict.com/programbrowser/andforone builtusingprototypevisithttp:generatorduodesign.comandselecttheproposal generator once you have logged in with the username demo @duoconsulting com and http://wiki.scriptaculo.us/scriptaculous/show/aiax.Request 8/8
Adam Howitt Duo Consulting 7/7/2006 12:23 PM onFailure:errFunc}); } The major difference between this and the AJAX.Updater function is that there is no container passed to be updated and there is an additional option specified “onSuccess” which tells the function to send the results to handlerFunc(). Again, the reference wiki at scriptaculous8 has a fuller description. Uniqueness There is no support I could find for prototype to be able to make a request unique so I rolled my own. Both of the above scripts can be modified to add a line: url = getUniqueToken(url); This line references a function which, when passed a URL will append a string representing the current date and time down to the millisecond such that a user’s browser is unlikely to be able to submit the same request in that exact millisecond: function getUniqueToken(url) { var dt = new Date(); var dtString = ''+dt.getFullYear()+ dt.getMonth()+ _ dt.getDate()+dt.getHours()+ dt.getMinutes()+ _ dt.getMilliseconds(); dataUrl = url + '&dtm='+dtString; return dataUrl; } This should be enough to get you started with the an AJAX application utilizing Prototype to build a functional application. For an example of a hand rolled AJAX application visit http://programs.chicagoparkdistrict.com/programBrowser/ and for one built using prototype visit http://generator.duodesign.com and select the proposal generator once you have logged in with the username demo@duoconsulting.com and password demo. 8 http://wiki.script.aculo.us/scriptaculous/show/Ajax.Request 8/8