Section 12.5. Building the JavaScript Application


12.5. Building the JavaScript Application

Now that the back end of the application is complete, we need to look at building the front end. This code needs to build the user interface, manage the application flow, and communicate with the back end. To help manage this process, we;ll be breaking the application into a number of different components. Each component is made up of an HTML file and a JavaScript file, and each component performs one action, such as logging a user into the site or adding a ticket. The various components will be tied together by some simple JavaScript code that controls the site.

The JavaScript controller is a simple design; it is based around the idea of showing and hiding DIV elements to enable a current section of the site. All the JavaScript and HTML code will be loading into the browser on the initial page load and then will use a function to pick which code to deal with. Each part of the application with which you can interact is called a section, and it could be made up of multiple components, if needed; note, however, that in this case, each section will contain just one.

The basic site setup is a PHP page that loads the application; this page includes ticket.js, which contains JavaScript shared throughout the entire site. It also includes all the HTML code for the various components and their associated JavaScript files. The components are used to build the various sections of the application, and only one is visible at a time. There is also a site sidebar that always shows the login component. This setup was used because some features, such as viewing a ticket, can be used without logging in, but other features, such as adding a new ticket, require the user to be logged in. The basic layout of the user interface can be seen in Figure 12-4.

Figure 12-4. Basic ticket-manager interface


The look of the ticket application is controlled by the Ticket.css CSS file. There is no need to cover the CSS in detail because it doesn't do anything AJAX-specific. The one item to note is that it does specify default display types for a number of CSS classes; this allows us to have all of the various sections of the site hidden by default, so you don't see them until they have been selected. Now that you've seen what we're building, let's look at it in detail, starting with the page that pulls it all together (see Listing 12-7).

Listing 12-7. index.php

1  <?php 2  require_once 'HTML/AJAX/Helper.php'; 3  require_once 'Ticket.class.php'; 4 5  $app = new Ticket(); 6  $ajax = new HTML_AJAX_Helper(); 7  $ajax->stubs = array('Ticket'); 8  $ajax->jsLibraries = array('All'); 9 10  // Load time values 11  $isLoggedIn = $ajax->jsonEncode($app->isLoggedIn()); 12  $profile = $ajax->jsonEncode($app->profile()); 13  ?> 14  <html> 15  <head> 16  <title>Ticket Manager</title> 17  <?php echo $ajax->setupAJAX(); ?> 18  <script type="text/javascript" 19  src="/books/2/341/1/html/2/scriptaculous/prototype.js"></script> 20  <script type="text/javascript" 21  src="/books/2/341/1/html/2/scriptaculous/scriptaculous.js"></script> 22 23  <script type="text/javascript"> 24  var app = { 25    isLoggedIn: <?php echo $isLoggedIn; ?>, 26    profile: <?php echo $profile; ?>, 27    setup: [], 28    logout: [], 29    templates: {}, 30    since: false 31  } 32  HTML_AJAX.onError = function(e) { 33    alert(HTML_AJAX_Util.quickPrint(e)); 34  } 35  </script> 36 37  <script type="text/javascript" 38  src="/books/2/341/1/html/2/Ticket.js"></script> 39  <script type="text/javascript" 40  src="/books/2/341/1/html/2/components/Login.js"></script> 41  <script type="text/javascript" 42  src="/books/2/341/1/html/2/components/EditAccount.js"></script> 43  <script type="text/javascript" 44  src="/books/2/341/1/html/2/components/MyTickets.js"></script> 45  <script type="text/javascript" 46  src="/books/2/341/1/html/2/components/AddTicket.js"></script> 47  <script type="text/javascript" 48  src="/books/2/341/1/html/2/components/TicketEditor.js"></script> 49  <script type="text/javascript" 50  src="/books/2/341/1/html/2/components/Register.js"></script> 51  <script type="text/javascript" 52  src="/books/2/341/1/html/2/components/Assign.js"></script> 53 54 55  <link rel="stylesheet" href="Ticket.css" 56    type="text/css"> 57  </head> 58  <body onload="setup()"> 59 60  <div > 61  <h1>Ticket Manager</h1> 62  <div > 63  <span><a href="javascript:selectSection('front')" 64    >Home</a></span> 65 66  <span ><a 67    href="javascript:selectSection('myTickets')" 68   >My Tickets</a></span> 69 70 <span ><a 71   href="javascript:selectSection('addTicket')" 72   >Add Ticket</a></span> 73 74 <span ><a 75   href="javascript:selectSection('assign')" 76   >Assign Tickets</a></span> 77 <span ><a 78   href="javascript:selectSection('editAccount')" 79   >Update Account</a></span> 80 </div> 81 </div> 82 83 <div style="position: relative"> 84 85 <div > 86 <div  > 87 Welcome to the Ticket Manager. Log in to add 88 new tickets and view your own. 89 90 <form onsubmit="return viewTicketForm(this)"> 91 <p ></p> 92 <label>ID</label> <input name="id" size="4"> 93 <input type="submit" value="View Ticket"> 94 </form> 95 </div> 96 <?php include 'components/Register.php'; ?> 97 <?php include 'components/EditAccount.php'; ?> 98 <?php include 'components/MyTickets.php'; ?> 99 <?php include 'components/AddTicket.php'; ?> 100 <?php include 'components/Ticket.php'; ?> 101 <?php include 'components/Assign.php'; ?> 102 </div> 103 104 <div > 105 <?php include 'components/Login.php'; ?> 106 </div> 107 108 </div> 109 110 </body> 111 </html>

The first 57 lines of index.php do the JavaScript setup for the entire application. This includes setting up our interaction with the HTML_AJAX-powered back end and including the JavaScript for the various components. Each component has its own JavaScript file, which is great from a development point of view but could become a scalability problem over time due to the number of HTTP requests required to load them all. You could easily replace all the individual JavaScript includes with a request to a PHP script that combined the files automatically, if that ever became a problem.

Lines 2 and 3 require the PHP classes that will be used to build this page. One of these is the HTML_AJAX_Helper class (line 2), which we will be using to output some JSON-encoded strings and to quickly build the JavaScript include line for the HTML_AJAX libraries. The Ticket class (line 3) will be used to get the user's current login status. Outputting this information in index.php keeps us from having to do an AJAX call at page-load time to figure out if the user is already logged in. Line 5 creates an instance of the Ticket class, which will be used later, and lines 58 create an HTML_AJAX_Helper instance and then configure it. Setting its stub property to Ticket sets it to load the generated JavaScript stub class for the PHP Ticket class we registered in Server.php. Setting its jsLibraries to All gives us the entire set of HTML_AJAX JavaScript libraries; you could limit this to just the components you're using to reduce the amount of JavaScript code that is required, but most sites don't need that level of optimization. Lines 11 and 12 encode login status and the currently logged in user's profile as JSON strings so that we can use them later.

Line 14 starts the HTML output. The first task is to include the JavaScript libraries we're going to use. Line 17 uses the helper class to output the HTML_AJAX JavaScript includes and is followed by the includes for scriptaculous on lines 1821. We will be using HTML_AJAX for communications and scriptaculous for visual effects and drag-and-drop support. The next step (lines 2335) is to define the JavaScript app object; this object is used to hold data that will be reused throughout our JavaScript application. We could use a number of separate variables, but combining them into one object helps us keep track of variables that are safe to use throughout the application; included in this variable are the user's login status (line 25) and the user's profile data (line 26). We also set up a development error handler for HTML_AJAX requests (lines 3436); you could leave this for production as well, but normally, you'll want to show users a less technical error message.

Lines 3752 require the JavaScript files that are used throughout the site. The first is Ticket.js (see Listing 12-8), which contains the site's shared code, followed by the JavaScript for the various components. The order in which the components are required shouldn't matter because they don't have intercomponent dependencies. All the shared code is included in Ticket.js, with interactions of items such as login status handled through the application object or through CSS classes. The setup is completed by including the Ticket.css file, which gives the application its basic look and feel.

The rest of the file builds a basic user interface; this includes a navigation menu, a simple front page, and a sidebar. On line 58, we add an onload handler to the page, which calls a setup method that exists in the shared Ticket.js file. Next, the page defines the header DIV (lines 6081). This DIV contains the application's name (line 61) and its navigation links (lines 6280). The navigation links call the setSection function to change which section is selected, and they use a CSS class to mark which ones should be shown when the user is logged in. Any element on the page with a CSS class of loggedIn will be shown only when the user is logged in. The hiding and showing of these elements is handled in the login component.

Lines 85102 define the main body of the application. Most of this is just including the HTML for the given modules, but the default view is created right on this page because it is so simple. It contains a form (lines 9094) that calls the viewTicketForm function when submitted. This function is defined in the ticket component. The page finishes by defining a sidebar DIV (lines 104106) that includes the login component. Having the login on the sidebar allows the user to log in or out at any time.

Listing 12-8. Ticket.js

1 // set up some function aliases 2 var byClass = HTML_AJAX_Util.getElementsByClassName; 3 var byId = function(id) { 4   return document.getElementById(id); 5 } 6 var d = function(item) { 7   alert(HTML_AJAX_Util.quickPrint(item)); 8 } 9 10 function setup() { 11   for(var i = 0; i < app.setup.length; i++) { 12     app.setup[i](); 13   } 14   selectSection('front'); 15 } 16 17 function selectSection(name) { 18 19   if (!app.isLoggedIn && 20     (name != 'front' && 21     name != 'ticket' && 22     name != 'register') 23   ) { 24     name = 'front'; 25   } 26 27   var s = byClass('section',byId('body')); 28   for(var i = 0; i < s.length; i++) { 29     if (s[i].id == name) { 30       s[i].style.display = 'block'; 31       if (s[i].onDisplay) { 32         s[i].onDisplay(); 33       } 34     } 35     else { 36       s[i].style.display = 'none'; 37     } 38   } 39 } 40 41 function setMessage(element,message) { 42   element.innerHTML = message; 43   element.className = 'message'; 44 45   element.style.display = 'block'; 46 47   window.setTimeout(function() { 48     new Effect.Fade(element); },3000); 49 } 50 51 52 // Utility functions 53 function positionOver(element) { 54   var target = element.parentNode; 55   target.style.position = 'relative'; 56   element.style.position = 'absolute'; 57   element.style.top = 0; 58   element.style.left = 0; 59   element.style.width = '100%'; 60   element.style.height = target.clientHeight+'px'; 61 } 62 63 function buildTable(data,table) { 64   var tbody = byId(table).tBodies[0]; 65   for(var i = tbody.rows.length-1; i >= 0; i--) { 66     tbody.deleteRow(i); 67   } 68 69   for(var i = 0; i < data.length; i++) { 70     var row = app.templates[table].cloneNode(true); 71     updateRow(row,data[i]); 72 73     byId(table).tBodies[0].appendChild(row); 74   } 75 } 76 77 function updateRow(row,rowData) { 78   row.ticket_id = rowData.ticket_id; 79   var tds = row.getElementsByTagName('td'); 80   for(var r = 0; r < tds.length; r++) { 81     tds[r].innerHTML = tds[r].innerHTML.replace( 82     /{\$([a-zA-Z0-9_]+)}/g, 83     function(s,k) { 84       return rowData[k]; 85     } 86     ); 87   } 88 }

Ticket.js contains the JavaScript code that is shared throughout the application. At the start of the file, we define several utility functions that cut down on the amount of typing we have to do while writing the application. The first one, which appears on line 2, is byClass, which is an alias to an HTML_AJAX function that returns an array of elements that have the given CSS class. Lines 35 define the byId function, which is a wrapper for document.getElementById, and lines 68 define a quick debug function, which is useful during the development process.

Lines 1015 define the setup function. This function is called by the document's load event and gives each module in the application a way to run some code at load time without first having to register an event handler. This is done by looping over the functions in the app.setup array (lines 1113) and running them. Because the modules are included before the onload event runs, they can add functions to this array as they are included. The setup finishes by selecting the front section of the application.

The next item defined in Ticket.js is the selectSection function. This function acts as the main controller of the application, selecting which section will be shown. It takes a single parameter, which is the ID of the section to be shown. First, the function checks whether the user is logged in (lines 1925). Only the front, ticket, and register sections can be used without logging in. By doing this check, we prevent errors from taking the user to a section that won't work. Next the function selects all the sections within the body; each section is marked with section class, so we use the byClass function to do this. Then we loop over the sections; if the ID matches the section we passed in (line 29), we show it (line 30) and run its onDisplay method if it exists. Otherwise, we hide the section (line 27). Running the onDisplay method gives the section a way to run code as the section is displayed. This gives it the opportunity to update data or clear out old data.

Lines 4149 define the setMessage function. This is a simple function that allows us to give the user a success or failure message that will be shown for three seconds and then fade off the screen. The first parameter to the function is the DOM element to fade; the second parameter is the message to use. Depending on your design, you can always use a single message target so that the first parameter isn't needed, but in this case, the flexibility is needed, so the same code can be used for login messages. The fading out of the message is performed by the use of a scriptaculous effect (line 48), which is applied inside a setTimeout call; the delay on running the timeout function is in milliseconds, so the value of 3000 equals 3 seconds.

When combined with an alert color, such as yellow or red, notification messages that appear and then fade away after a short period can be very successful in AJAX applications. The disappearing message keeps the alerts timely, and it is needed so that the user can tell which action the message applied to, because a page reload won't be clearing it. The biggest downside of this approach is that the user can lose useful information if it disappears before he or she has read the message. One approach to get around this problem is to add a message history to the application. Instead of being removed completely, the message is added to the message history after three seconds. An example message is shown in Figure 12-5.

Figure 12-5. Message that will fade away after three seconds


The positionOver function is used to position a passed-in element over its parent element. This is used when you want to cover an element, such as a form, with an opaque message while it is being AJAX processed. This is a useful loading technique because it will prevent the form from being clicked while you are processing the last request it sent. To use the function, follow these steps:

1.

Create a new element (usually a DIV).

2.

Style it.

3.

Add any messaging it should have.

4.

Append it to the element you want to hide.

Once the element has been added, you run positionOver(element), and the element is positioned to hide all the elements to which it was added. You can show the parent by removing the element from the DOM or setting its display property to none. Scriptaculous effects, such as fade, are a good way to remove this element. The positioning elements of this function can be achieved using CSS on some browsers, but if you want it to work everywhere, size the element with CSS.

Another possible approach to preventing a form from being submitted while its results are loading is to disable its form submission button. This is often accompanied with changing the label on the button to a loading message. Both approaches provide good feedback to the users, but positioning a DIV over the form gives the developer the ability to display a larger loading message. Figure 12-6 shows an example of positioning an element over a form as a loading message; Figure 12-7 shows an example of disabling the form submission button.

Figure 12-6. Providing form-loading status by using the positionOver function


Figure 12-7. Providing form-loading status by disabling the form submission button


The last two functions in Ticket.js work in concert to allow us to dynamically update tables using a template approach. The first function, buildTable (lines 6375), takes an array of data and the ID of the table to update. It deletes all current rows from the table's body (if you put your headers in the <thead> tag, they will not be deleted) on lines 6567. Then it loops over all the data (lines 6974), creating a new row by cloning a template node and then runing updateRow using that row's data.

The updateRow function (lines 7787) takes the data and applies it to the row using a regular expression replacement. The function starts by setting the ticket_id on the tr element (line 78). This is an application-specific assignment because not all data will contain a ticket_id in each row, but similar approaches are often used for other IDs (or even all the row's data) because it makes the data available to later JavaScript code. The actual replacement has to be done by updating the innerHTML property of each table cell (td tag), because even though the table row (TR tag) has an innerHTML property, updating it won't work properly. Thus, we get all the td tags in the row (line 79) and loop over them, running a replace against their innerHTML, with a callback function grabbing the correct data with which to use in the replacement. The regular expression (line 82) is designed to match against strings such as {$variable_name}. The name of the variable is passed into our replacement function (lines 8285) and used to grab the correct index from the row of data. This allows us to create a template like this:

<tr><td>#{$ticket_id}</td><td>{$title}</td></tr>


This template is used to build each row of output. After replacing its tokens with data that has a matching ticket_id and title property, you will produce a table row like this:

<tr><td>#4</td><td>A Sample Ticket</td></tr>


This same replacement approach could be used against many other types of DOM elements; in fact, the most problematic elements for this approach are tables because they don't update properly when working with the innerHTML of their individual rows.




Understanding AJAX(c) Using JavaScript to Create Rich Internet Applications
Understanding AJAX: Using JavaScript to Create Rich Internet Applications
ISBN: 0132216353
EAN: 2147483647
Year: N/A
Pages: 154

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net