5.3. Tabular InformationAs stated previously, the first two types of web pages required both deal in some way with tabular information, either for display or for updates. When I was in high school, I took quite a few drafting classes, thinking that perhaps a career in architecture lay in my future. But I discovered computers, and, eh, a career in a different kind of architecture lay in my future. And that is exactly what we need now: an architecture upon which to build our creatureeh, er, e-commercesite. So let's send Igor to get a cold beverage and queue the storm sound effects before we start. Back already? Because programming is one of those fields, like politics, in which trotting out an old idea is a virtue, we'll drag the frameset from Chapter 2, "Introducing Ajax," into this chapter and use it again. If Congress can recycle the same bills year after year, surely we can do the equivalent with some code. Just in case you've forgotten what it looks like, Listing 5-11 shows it in its entirety, without commercial interruption. Listing 5-11. Frameset
Unfortunately, because of scope creep, the visible page from Chapter 2 doesn't make the grade for this chapter. It is almost there, but it needs a little more functionalitybasically, additional logic to make it bulletproof. By bulletproof, I mean able to withstand attack by Machinegun Kelly or any other "guest" who can click a mouse button upward of 200 times a minute. But before adding the necessary logic, let's see what JavaScript functions we already have that can be cloned for our nefarious purpose. The first JavaScript function to be cloned is changeEvent, which itself does a little cloning. The sole purpose of this little cross-browser-capable function is to handle an onchange event for HTML input, textarea, and select tags. The second function that can be cloned is submitForm, which, surprisingly, is also cross-browser-capable. At this point in designing the architecture, I have run out of code to clone and now must write code from scratch. But before I do, allow me to explain what I'd like to do. After all, explaining plots is a common weakness that we mad scientists all have, and if I can't explain it to you, I'll have to explain it to Igor, and the blank, glassy stare that he gets is so unnerving. First I'd like a routine that ensures that the peasantseh, guestsdon't muck around with the Back button. This is because the Back button is like fire to Victor's monsterit causes unpredictable results. With any kind of HTML frames, hitting the Back button is just as likely to cause the hidden page to go back as the visible page. In short, it is not a good thing. Fortunately, in this instance, a little JavaScript goes a long way, as the following line of code shows: window.history.forward(1); Doesn't look like much, does it? Well, it isn't the size of the boat, but the, um, never mind. Let's just say that it is all that is necessary to ensure that the current page is always the top page in the history, which is exactly what this does. Of course, it needs to be included on every page, both visible and hidden. It is also important to remember to provide some means of navigation; otherwise, shoppers will be lost in a "twisty little maze of passages, all alike," which isn't real good for repeat business. The next function isn't really a function at all; it is actually a Boolean global variable that merely indicates whether the web browser is Microsoft Internet Explorer or another browser. The reason this is an Internet Explorer indicator isn't because I'm in love with IE; it is because the larger the software company is, the more likely that it has wandered off the path when it comes to following standards. So with this in mind, the following code was written: var _IE = (new RegExp('internet explorer','gi')).test(navigator.appName); The third function that is necessary to this project is one that "clones" a form on the hidden frame to the visible. Although this sounds pretty simple, it is anything but simple. In fact, most developers never ask one major question unless they try this kind of thing for themselves: When loading the frameset for the first time, which page loads first? Fortunately, there is a simple answer to this question; unfortunately, the answer is that I don't know, which is a rather big stumbling block to overcome to complete the website. This means that not only will the function need to clone the hidden form to the visible form, but it might have to sit around waiting for the visible form to finish loading. The good thing is that the process of checking for frame completeness is very similar to what was done in Chapter 2, as shown in Listing 5-12. Listing 5-12. initialize Function
The initialize() function is invoked by the hidden frame's onload event handler, and the first thing that it does is use the _IE Boolean that I created earlier. The reason for this is that occasionally I do give in to temptation and use a nonstandard browser feature. In this instance, the feature is the document object's readyState property. Just test it against "complete," and we're good to go (that is, if the browser is Microsoft Internet Explorer; otherwise, it is necessary to give it the old college try and catch). If the visible frame isn't ready, it is necessary to use the window.setTimeout() method to invoke the initialize() function again after waiting the specified number of milliseconds. Don't confuse this method with the window. setInterval() method because setTimeout invokes the specified function only once. With setInterval(), the function repeats like salami does until it is stopped, which is bad, unless you are fond of debugging really weird client-side happenings. The next function that I want to add is one to restrict keyboard input to numeric values. Although the appropriate elements can be tested at submission time, we're dealing with guests who could potentially unleash a plague of giant hedgehogs on Spotswood, New Jersey, when ticked off. So why not avoid any problems before they occur? Listing 5-13 shows this function in all its glory. Listing 5-13. restrict Function
The final two functions are the changeEvent() and the submitForm() functions, which have been copied directly from Chapter 2. Listing 5-14 shows both of these functions. Listing 5-14. changeEvent and submitForm Functions
5.3.1. Read OnlyAs strange as it sounds, when I'm creating a website from scratch, I often find it simpler to begin coding nearer to the end than the beginning. This is probably some sort of unique mental defect, but it works, so I'm not about to mess with it. So let's start with the page that shows the garbage that the sucker orderedeh, the items that the customer selected for purchase. In fact, let's play nice and try to refer to customers as "guests" instead of "users" or "suckers"at least, to their faces (remember the Moon-Mounted Death Ray). So with my new and enlightened attitude, let's determine what information the guests require. Well, the order number would be nice, if only for our own protection. The same can be said for item numbers, item names, quantity, and both unit price and total item price. Showing the total along with any shipping charges and tax (at least, until our own Death Ray is operational) is an absolute must. So let's see, we have the following:
Now that we've got something that remotely resembles a plan, it is time to implement it. First there are the database tables that describe the guild (Mad Scientist, Alchemist, or Sorcerer), orders, items, and lines. From this SQL it is pretty easy to infer what some of the other tables are, but we ignore them for now because they're not needed at this point. Listing 5-15 shows the SQL necessary to define these tables. Listing 5-15. SQL to Create MySQL Database Tables
If you recall, earlier I stated that MySQL version 5 and higher support stored procedures; in fact, I even gave you an example. We've just covered the tables we're using for this example, so now is a good time to cover the stored procedure. The stored procedure lineSelect (see Listing 5-16) is relatively simple, just a select statement with a bunch of inner joins. Although it isn't heavy dutyno cursors, transactions, or anything like thatit is an example of a stored procedure in MySQL, currently a thing only slightly more common than unicorns. However, there are a number of reasons for the inclusion of stored procedures, especially in MySQL. The first of these is to avoid the use of Microsoft Access, which is technically a database; however, it really isn't very robust. Some might argue that Access is a replacement for SQL Server, which I agree to, but I'm on a budget here and a stripped-down developers' edition isn't what I want. Besides, both Access and SQL Server are Windows-only databases. Oracle, on the other hand, runs a number of platforms and is robust, but it isn't open source. As for my final reason for stored procedures, speed thrills. Listing 5-16. lineSelect stored procedure
Earlier I said that the examples would be in PHP, and because stored procedures are being used, it is necessary to use the mysqli library instead of the mysql library. This might not sound like a big deal, but it would be a good idea to provide some basic information on the parts ofmysqli that are used in this example. Table 5-1 outlines these "parts."
The odd thing is that after all the little details are covered, such as the client-side JavaScript, database tables, and stored procedures, there is actually very little code to write. Mostly it comes down to putting the pieces together and using the Cascading Style Sheets (CSS) shown in Listing 5-17 to give the website a consistent look and feel. Listing 5-17. CSS
The end result of this endeavor is the page shown in Figure 5-2, whose code is shown in Listing 5-18 along with some common PHP variables and routines shown in Listing 5-19. While we're on the subject of common routines, I should state now that there are several different approaches to handling inclusion of common code. The first, which I'm using here, is to include everything that could possibly be of any use from a single file. Later, however, I switch to an approach that breaks up variables and routines by function. For example, database-related items are here and rendering-related items are there, and anything else is handled on a case-by-case basis. This might seem like overkill now, but it falls under the category of defensive programming. Figure 5-2. The page resulting from our effortsListing 5-18. Code for the Page in Figure 5-2
Listing 5-19. PHP Variables and Routines
5.3.2. UpdateableAs with the previous page type, the next type of page to be generated is also tabular in nature. However, unlike the previous example, this page allows for input beyond the navigation to the next page type. In a nutshell, here is our first chance to use the majority of the architecture functions, and, in a nutshell, here is where there is a big chance that things can go seriously wrong. The big question is, just how can things go seriously wrong? Is it a flaw in the underlying concepts of Ajax? Nope, it is more of what I refer to as a "Homer Simpson Moment." These moments are caused by coding while the brain is on autopilot, and for me it usually manifests itself in the form of using the wrong event handler or forgetting an event handler altogether. Fortunately, by coding the submitForm() handler to deal with changes to HTML objects, I've managed to avoid one of my more common points of failure. Alright, now with that out of the way, I feel less likely to screw up in the same old way. If I am going to screw up, I want it to be in an entirely new and original way. After all, in most cases, more can be learned from getting something wrong than by getting something right. Now that we've covered the basics of what can go wrong when working with forms, let's put it into practice. Hmm, that didn't sound right. Okay, take two. Now that we've covered some of the potential pitfalls of working with forms, let's create a web page avoiding them. Whew! The purpose of the next page that we are working with is to display the contents of the guest's virtual shopping cart. As with its real-world counterpart, shoppers will have several possible actions available to them. First, they can remove individual items from the cart just like they do in the real world; how else do you suppose frozen peas find their way to the cookie aisle? The next possible action is to change the quantity, either up (yeah!) or down (pout!). Oh, I should mention that decreasing an item's quantity to zero has the same end result as removing the item from the cart. Finally, shoppers will have the option of giving up and just abandoning their shopping cart. This is a good time to point out that, unlike some virtual shopping carts where the contents are stored on the server, this one doesn't. Instead, I chose to follow the "why bother the server any more than absolutely necessary?" philosophy, so the shopping cart is cached in a hidden text box in a form on the visible frame as item-quantity pairs. Why? Because after being loaded, with the exception of the cloned form, the visible frame doesn't change. Although it sounds somewhat strange, it has the advantage of reducing server traffic. When the time comes to display the shopping cart, it can simply be coded into the URL, which, although it does have a 4K limit, should be more than enough for our purpose. Although we already have a lot of the code necessary for this to work (the numeric input function and the CSS), several bits of code are needed. First, there is the JavaScript function that builds the URL for displaying the shopping cart (see Listing 5-20). In addition, there is the stored procedure and two stored functions to retrieve all the necessary information from the tables shown in Listing 5-21. Finally, there is the page itself in Figure 5-3 and Listing 5-22. Figure 5-3. The shopping cart pageListing 5-20. JavaScript Function That Builds the URL for Displaying the Shopping Cart
Listing 5-21. The Stored Procedure and the Two Stored Functions
Listing 5-22. Code for the Shopping Cart Page
|