Dynamic Web Forms Professional Projects by Dan Ransom ISBN: 1931841136 Premier Press © 2002 (755 pages) A step-by-step guide to help you learn lively Web form development by means of JavaScript, PHP, MySQL, Apache, and more.

Table of Contents

Dynamic Web Forms Professional Projects Introduction Part I - Dynamic Web Forms Overview Chapter 1 - Introduction to Dynamic Web Forms Chapter 2 - Dynamic Web Form Concepts and Considerations Chapter 3 - HTML Forms Chapter 4 - JavaScript and Web Forms Chapter 5 - Server-Side Scripting and Relational Databases Chapter 6 - Modular Interface Design Part II - Project 1: Creating Membership Forms Chapter 7 - Project 1 Preparation: Designing for Modularization Chapter 8 - Creating the Forms Chapter 9 - Completing the Membership Interface Part III - Project 2: Creating Catalog Forms Chapter 10 - User Interface Design Chapter 11 - Designing a User Interface Chapter 12 - Creating the Online Catalog Interface Chapter 13 - Completing the Catalog Interface Part IV - Project 3: Creating Online Information Systems Chapter 14 - Functional Design Chapter 15 - Designing a Functional Interface Chapter 16 - Creating the Online Information System Itself Chapter 17 - Completing the Information System Interface Part V - Beyond the Lab Chapter 18 - XML and XForms Chapter 19 - Embedded Web Form Technologies Part VI - Appendices Appendix A - HTML Form Elements Reference Appendix B - Recommended Naming Conventions Appendix C - Resources for Web Forms Index List of Figures List of Tables List of Sidebars

Dynamic Web Forms Professional Projects Dan Ransom

© 2002 by Premier Press, Inc. All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording, or by any information storage or retrieval system without written permission from Premier Press, except for the inclusion of brief quotations in a review.

The Premier Press logo, top edge printing, and related trade dress are trademarks of Premier Press, Inc. and may not be used without written permission. All other trademarks are the property of their respective owners. Important: Premier Press cannot provide software support. Please contact the appropriate software manufacturer’s technical support line or Web site for assistance.

Premier Press and the author have attempted throughout this book to distinguish proprietary trademarks from descriptive terms by following the capitalization style used by the manufacturer.

Information contained in this book has been obtained by Premier Press from sources believed to be reliable. However, because of the possibility of human or mechanical error by our sources, Premier Press, or others, the Publisher does not guarantee the accuracy, adequacy, or completeness of any information and is not responsible for any errors or omissions or the results obtained from use of such information. Readers should be particularly aware of the fact that the Internet is an ever-changing entity. Some facts may have changed since this book went to press.

ISBN: 1-931841-13-6 Library of Congress Catalog Card Number: 2001098163 Printed in the United States of America 02 03 04 05 RI 10 9 8 7 6 5 4 3 2 1 Publisher: Stacy L. Hiquet Marketing Manager: Heather Buzzingham Managing Editor: Sandy Doell Acquisitions Editor: Kevin Harreld Project Editor: Estelle Manticas Editorial Assistant: Margaret Bauer Technical Reviewer: Michelle Jones Copy Editor: Hilary Powers Interior Layout: Shawn Morningstar Cover Design: Mike Tanamachi Indexer: Sharon Shock Proofreader: Lorraine Gunter Dedication This one’s just for me Acknowledgments

I’d like to take this opportunity to thank everyone who made this book possible. Thanks to Kim and Stacy; they took a chance and gave me a shot. Thanks also to Estelle, Kevin, and all the editors who had the patience to deal with my not-so-minor annoyances. A very special thanks to my friends and family who had to deal with a lot of unreturned phone calls and missed gatherings while I spent nights and weekends over a keyboard to put this together. Finally, thanks to God and everyone else who never quite gets thanked enough—you know who you are. About the Author Dan Ransom is a rarity among Web developers: a generalist. He has a solid grounding and experience in graphic design, programming, user interface design, Website architecture, and code optimization. Surprisingly, despite all this, he can still put together a bit of code that doesn’t look like it was based on an Escher drawing. Currently Dan works as an Internet consultant for Scribner and Associates (www.scribnerconsulting.com). When he’s not redesigning user interfaces Dan dabbles in Flash and Java, and he plays Unreal Tournament at least twice a week (for stress). Dan lives outside Sacramento, California and plays the harmonica, badly.

Introduction

This book is intended to take a developer to the next step in Web interface design. JavaScript programmers will learn how to leverage their skills to create effective server- side scripts, server-side developers will learn how to extend their knowledge to the client environment, and everyone will learn the concepts behind creating modular, usable, and functional form-based user interfaces.

What’s in this Book?

Creating dynamic Web forms is more than simply slapping some code onto an HTML form. This book provides a hands-on approach to learning dynamic Web form development using popular freeware technologies such as JavaScript, PHP, MySQL, and Apache. I have included many examples of ready-to-use code that can be applied to most any Website. Part I of the book covers the basic principles of how the code is created and reviews the technologies needed to start creating dynamic Web forms. The majority of this book—Parts II, III, and IV—consists of three professional projects. These projects are based on the types of real-life interfaces wherein dynamic Web forms can be of benefit. These projects move from simple dynamic membership forms to a multi-functional information systems interface. Along the way you’ll learn more about each of the technologies involved and the specifics of how they can be applied to Web forms. While the server-side code used for the three projects is based on the combination of PHP and MySQL, the focus is less on the specifics of code and more on how the functionality is achieved. I’ve included information on how these methods can be applied using other relational databases or server-side scripts. Developers familiar with JSP, ASP, or ColdFusion will not only come away with an understanding of the basics of PHP, but will be able to apply many of the same methods and techniques to their preferred development environment. Part V includes two chapters that offer information on other dynamic Web form technologies. Chapter 18 includes a comprehensive overview of XForms, the W3C specification that has the potential to utterly change the way Web interfaces are created. The time for XForms has not yet come, but with this introduction you should be prepared for when it does. Chapter 19 compares and reviews some of the embedded technologies: Flash, ActiveX, and Java Applets.

Who Should Read this Book?

This book is intended for the intermediate to advanced Web developer. You should have a grounding in some type of Web scripting language, either client- or server-side. Experience with relational databases will help, but is not required.

The projects in this book primarily use JavaScript and PHP. Knowledge of one or both of these languages will allow you to bypass much of the introductory chapters and go directly to the projects. Developers familiar with other scripting languages, such as PerlScript or VBScript, should be able to easily use these skills to adapt the projects to their preferred technology.

A solid grasp of HTML is required for all readers.

How to Use this Book

Because this book is intended for different types of Web developers, there are numerous ways that it can be used. The majority of readers will benefit by following along with the book through each project step-by-step. This will give you the most comprehensive coverage of all the topics. Some developers however will be more interested in a specific application of dynamic Web forms and may skip to the project that interests them the most. Still others will want the book mostly for the ready-to-use code examples. In this case, the projects serve more as a guidebook to how the code can be implemented rather than as a learning tool. Finally, some developers may want to use this book as a handy informational resource. This books gathers together information on a wide variety of topics relating to Web interfaces, and some developers may wish to give it a quick review then bookmark the sections most likely to be needed in the future.

The following conventions are used in this book: Note Notes give additional information that may be of interest, but is not required to accomplish the task at hand. Caution Cautions are used to alert you to the potential hazards the may result if a task is performed incorrectly.

New Term Definition

Terms that may be unfamiliar to you or that may have multiple meanings are noted and defined as part of the text.

Find It Online These messages direct you to Web sites that contain additional information, related material, or valuable resources. Because of the ever changing nature of the Web it is to be expected that some of these links could change or disappear altogether.

What’s on the Web Site? This book includes an accompanying Web site located at http://www.premierpressbooks.com. Here you will find the project files and much of the sample code. Whenever source code is displayed for a file located on the Web site, the following notation is used: filename.

Part I: Dynamic Web Forms Overview

Chapter List Chapter 1: Introduction to Dynamic Web Forms Chapter 2: Dynamic Web Form Concepts and Considerations Chapter 3: HTML Forms Chapter 4: JavaScript and Web Forms Chapter 5: Server-Side Scripting and Relational Databases Chapter 6: Modular Interface Design

Chapter 1: Introduction to Dynamic Web Forms If you’ve picked up this book, then you probably already have an idea what dynamic Web forms are. They allow a Web site to interact with its visitors, not simply in a static, predefined way, but dynamically. These dynamic Web form elements are presented to the user in some fashion based on information gathered by the Web server, previous interaction with the user, or the specific capabilities and preferences defined by the browser. This chapter covers the basics of what dynamic Web forms are all about and defines the major terms that will be used throughout the book.

Web Forms

Web forms have been around for a long time. Unlike other Web technologies such as tables, layers, or style sheets, forms benefited from the fact that every browser manufacturer and Web developer saw the advantage of using forms on the Web. Only the very first Web browsers did not include some form-based functionality, and today you would be hard-pressed to find any of these archaic browsers still in use. This universality extends to how Web forms are implemented. With very few exceptions, all of the basic form elements can be added to a Web page using a standard set of browser- independent HTML. To create a Web form using HTML, you define each form device, called a form element. Together these elements allow a visitor to enter text, choose an option from a number of selections, or upload a file. Gathering direct user input in this manner allows a site to interact with the user in a way not possible using simple hypertext links.

How Are Web Forms Used?

Simply put, Web forms are used anywhere that the Web site or the user would benefit from an exchange of information. The user benefits because the information exchanged most often allows the site to provide customized service, content, or access that would not otherwise be available. The Web site in turn benefits from the information itself, both directly and indirectly. Direct benefits might include an increase in product sales; indirect benefits might involve an increase in returning visitors or improved marketing information.

Most Web sites contain some type of Web form. From the ubiquitous search box to the pop-up survey, Web forms are as common on the Web as their paper forebears are in the physical world. In fact, in many cases Web forms are replacing or supplementing their paper counterparts. You can now use Web forms to file your taxes, apply for a loan, or get an insurance quote. This book presents three typical uses for Web forms: membership forms allow a Web site to gather information about visitors to the site; catalog forms allow online retailers to present a digital storefront where visitors can order products online; information systems forms allow Web sites to disseminate large amounts of information to the general public. Along the way, you will learn the basics for creating search forms, online community forms, and secure forms for maintaining a database of information.

How Do Web Forms Work? Chapter 3, “HTML Forms,” and Chapter 5, “Server-side Scripting and Relational Databases,” cover the specifics of how Web forms function, but for now you can understand forms like this: Forms are added to a Web page using HTML. The user interacts with the form elements through a browser, entering data and making selections. The form is sent, or posted, to the specific Web page that was defined by the form. The form information is then processed by an application or script on the server. Finally, a page is returned to the user. What Tools You Will Need to Create Web Forms

Creating Web forms requires a number of applications and tools, but I’m happy to say that versions of most of these can be found as freeware. You should be familiar enough with HTML to know what you need to do to create Web pages, and if you don’t have one you will need to acquire an HTML editor. Some of these editors will create Web pages for you using a WYSIWYG (What You See Is What You Get) interface. However, I don’t recommend using a WYSIWYG HTML editor to create Web forms. Even the best of these products produce unwanted HTML code or limit the developer to a predefined set of options based on the wizards built into the application. Instead, you should use a text- based HTML editor such as HomeSite or HoTMetaL. Of course, these programs are just glorified text editors that provide color-coded HTML and useful shortcuts. In a pinch you could use any text editor, including Notepad, emacs, or even vi.

Any HTML editor will also allow you to create the script files (JavaScript and PHP) used in this book. To capture form input, however, you will need to set up a Web server. Many of the latest operating systems—including Windows 2000, MacOS X, and most Linux flavors—provide a Web server. If you don’t have a Web server you should be able to find a version of Apache, a freeware Web server, that will work on your system. For the purposes of creating and testing the project files it will not be necessary to attach the server to a domain name or IP address. If you need information about how to publish the files on the Web, contact the network administrator or your local Internet Service Provider—that information is outside the scope of this book, and varies a lot depending on the location of the site. Find It Online You can find download and installation information for the Apache Web server at http://www.apache.org. The projects in this book make use of a relational database system called MySQL. As an alternative you could use virtually any SQL-based database that can be accessed by PHP. Chapter 5 describes how you can acquire MySQL and how you can modify the project files presented in the rest of the book for use with alternative databases. If your database is on a different computer from the one with the Web server, you will need to establish a connection. Depending on the server-database combination, this can mean creating a connection string, a Web access account, or an ODBC (Open Database Connectivity) record.

Some additional project-specific applications and files will be required. You’ll find download and installation instructions for each of these at the point where they’re needed. What You Need to Know to Create Web Forms

If you don’t know HTML, take some time to pick up a solid grounding before you start this book, or you aren’t likely to get much out of it—I’ve written on the assumption that you already know the basics of HTML and the Internet. What this book will do is get you up to speed with Web scripting and form application processes.

First, you will need to know the specifics of how Web forms are presented in HTML. You need to know what attributes are available for each form tag and how other HTML elements, such as CSS styles and table structures, affect Web forms. This will allow you to present your visitors with a uniform and functional form no matter what browser they use. Next, you will need to understand how to process form data using server-side scripts. This will allow you to validate the form data, make any necessary formatting changes, and store the information in a database. While it is possible to use Web forms with client-side scripting exclusively, the processing power of the browser is limited and leaving out the server prohibits interaction with a database. Finally, you will need to know the basics of how to communicate with a database. This will allow form data to be stored permanently.

What Are Dynamic Web Pages?

There is often confusion when developers talk about dynamic Web pages. As a simple definition, dynamic Web pages are Web pages that provide content or presentation elements that have not been explicitly set. For example, a Web page that contains only HTML cannot be dynamic, because both the content and presentation have been predefined by the code on the page. On the other hand, a Web page that includes a JavaScript function that presents a message to the user when a button is clicked is dynamic because the message may or may not appear, depending on what the user decides to do about the button. Server-side technologies can also create dynamic pages. Microsoft’s Web site, for example, provides different pages to the user depending on which browser he or she is using.

It is important to understand that while all dynamic pages include some client scripting element or server-side processing, not all pages that include JavaScript or are generated by server-side applications are dynamic. Both client and server-side scripts can be used to generate a page that says “Hi Mom!” to every visitor, but this isn’t a dynamic page. Only Web pages that have some conditional element can be considered dynamic. Note Don’t confuse dynamic Web pages with pages created using dynamic HTML (DHTML). DHTML is a catch-all term that refers to a combination of client-side technologies, usually involving JavaScript and layers, that change the content or presentation of a Web page after it has been loaded into a browser. DHTML Web pages are all dynamic, but not all dynamic Web pages use DHTML.

Server-Based Dynamic Pages Server-based dynamic pages rely on scripts or applications on the server to generate conditional content. Originally, servers were limited to using CGI technology to create this functionality. Modern developers, however, have many more choices available, including PHP, ASP, JSP, and ColdFusion. Chapter 5 describes these server-side technologies and how they can be applied to Web forms, but the inherent benefits and difficulties are the same no matter which server-side processing method you choose. The Benefits of Server-Based Processing

Server-side scripting allows a developer to take advantage of the processing power of the Web server. Most server-side technologies have far more raw data processing functionality than client-side scripts. In addition, the server itself is likely to be faster and more powerful than the machine used by the average Web surfer. This increased processing power means that server-based dynamic Web pages can be based on more complex criteria or present a larger amount of potential content.

Server-side processing is also the only way for information to be stored or retrieved from a database. Data can be better organized and more permanently stored in a file system or a relational database than is possible using client-side scripts alone. The advantage of database connectivity is magnified for larger sites that have more than a handful of pages. As the page count increases the maintenance costs go up; organizing information in a database can help reduce these maintenance costs.

Finally, server-side processing is browser-independent. Regardless of how the file is generated, Web servers still send simple HTML to the browser. The fact that the end product is always in this defined standard allows the developer of a server-based dynamic page to avoid the traps and tribulations that most browser-based projects encounter. Instead, the developer can concentrate on the dynamic data itself. Difficulties Associated with Server-Based Processing Server-based processing isn’t a bed of roses, however; it has a number of drawbacks. First, the process that the server uses to send information to the browser is inherently stateless. This means that the server cannot tell whether two requests came from the same person or two different people. It simply processes each request as it occurs, unconcerned with how the information is used by the visitor. This means that any Web site that wishes to link together functionality across multiple pages requires some type of workaround.

Second, server-based processing can only occur after a request has been sent to the server. This means that even the most elaborate server-side scripts can only function in the small interval between the time the user clicks a link or submits a form and the time the dynamic page is returned by the server. Not only does this force server-based processing to be very quick, it eliminates the chance of modifying a page after it has been loaded into the browser.

This means that all server-based dynamic Web pages are affected by the bandwidth of the client. The more dynamic a server-based page is the more calls to the server will be needed and the more bandwidth will be required. Visitors with slow connections can become frustrated with the wait for some server-based dynamic Web pages.

Browser-Based Dynamic Pages Browser-based dynamic pages use scripts that are included inside HTML pages to give instructions to the browser itself, which generates conditional content. Most browser- based dynamic pages use JavaScript, but other scripting languages, such as JScript or VBScript, can also be used. Chapter 4, “JavaScript and Web Forms,” describes JavaScript and the other browser-based scripting alternatives and how they can be applied to Web forms. All browser-based dynamic Web pages share the same set of basic benefits and difficulties. The Benefits of Browser-Based Processing

Unlike server-based processing, browser-based processing can continue after the page has been loaded into the browser. The content and display of the dynamic elements on the page are not limited to the HTML code that was sent by the Web server. Browser- based scripts can make content appear and disappear, change position, change formatting, and a host of other functions that are not possible using server-side scripts.

Additionally, because the processing is handled by the browser and not the server, browser-based dynamic Web pages are not affected by bandwidth. Functionality that might force a server-based page to connect to the server can be handled entirely within the current page by the browser. Other functionality, such as creating pop-up windows or moving the browser window, simply isn’t possible using server-based scripts. Difficulties Associated with Browser-Based Processing

Browser-based processing has its own set of difficulties, some of which are quite significant. First, all browser-based scripts rely on the visitor’s browser to perform the processing. This is significant when you consider that not every browser has the same set of functions and some don’t have any script-processing ability at all. This leaves the developer of browser-based dynamic pages with a choice: limit the functionality of the page to a qualified set of users or spend a significant amount of time creating alternative scripts for many different browsers.

Having the browser handle the processing of dynamic content also presents another problem. Most browsers cannot replicate the raw processing speed and flexibility possible using server-based scripts. While browser-based scripts are very good at changing the way data is displayed, they are notoriously difficult when it comes to processing the data itself. Compared to server-based processing, the browser-based alternatives are both clunky and complicated. Also, many visitors could have older or slower machines that handle the browser processing only very slowly and can affect the use of other simultaneous applications.

Finally, browser-based processing does not really allow for information to be stored in any permanent way. While small amounts of information can be stored by the browser for a limited time, this information is limited in both size and structure. Only small bits of text can be stored, and only on some browsers, and only on an individual user basis. Storing more complex information or gathering information from more than a single user is impossible using browser-based scripts alone.

Creating Dynamic Web Forms

So, if both server and browser-based dynamic Web pages have advantages and disadvantages, which should you use? And how do these technologies apply to Web forms? The answer to the first question is simple: use both. This way you will gain all the advantages of each and will be able to offset many of the difficulties. The second question is more interesting. The answer is in your hands.

The projects in this book will show you how you can apply technologies to Web forms. These projects focus on three of the basic fundamentals of Web form design: modularity, usability, and functionality. Each has its own benefits and considerations, but before you can learn about dynamic Web forms you should have an idea why you should use dynamic Web forms in the first place.

The Benefits of Dynamic Web Forms

Dynamic Web forms are the most directly responsive user interface possible on the Web. This might seem like a bold statement, but it holds up to scrutiny. The simple fact is that there is no current alternative that can match the flexibility, power, and low cost of dynamic Web forms. Whether the forms are created using the techniques covered in this book or through some entirely different application, dynamic Web forms go far beyond simple dynamic Web pages.

A typical dynamic Web page is only dynamic in one direction—toward the visitor. Using this model, nearly all information passes from the server to the browser, with only simple page requests being sent the opposite way. The addition of Web forms into the equation, however, allows the dataflow to extend in both directions. Dynamic Web forms allow for the processing of information at both the server and the browser.

You might compare the difference to the performance of a play. Actors practice and sets are prepared before the performance while audience members buy tickets and take their seats. When the curtain goes up and the play begins the actors can see and hear the reactions of the audience. This allows them to make minor changes—such as adjusting the emphasis of a certain line or tempo of a scene—in how the play is performed. The audience in turn is more likely to react favorably to the play and come away with a better experience. In the alternative scenario, the performance could have been recorded and shown to the audience at a later date. In this case, the actors have no chance to change their performance based on audience reactions and the audience may have a slightly less enjoyable experience. Dynamic Web forms allow you, as the developer, to create a page or a set of pages that can react to the input of the visitors. Beyond simple presentation or content changes, dynamic Web forms give you the ability to change the user’s experience for the better.

Alternatives to Dynamic Web Forms

Dynamic Web forms are the best way to transfer information between a community of geographically separated users, but it’s not the only way. There are a few alternatives; most are expensive, complicated, or restrictive. For example, recently there has been a movement toward ASPs (Application Service Providers). These companies provide custom applications that allow individuals to transfer information to and from a dataset. The marketing of these companies involves a simple premise: pay us to do all the coding and maintenance so that you can concentrate on the fundamentals of your business. The problem is that for the most part any data information architecture will require a company to pay someone to build and maintain the system. By choosing an ASP to perform this task, you allow this system to be built around proprietary applications or nonstandardized code. When you need to modify or add to the system you will be forced to deal with the same company at the price they set. In addition, extracting your company from the ASP-designed system can be incredibly difficult. The ASP itself has no motivation for providing a speedy changeover and not even the most benevolent ASP is likely to give you access to the source code for the system.

The same problems are evident with using distributed applications to replace dynamic Web forms. Distributed applications are programs that you purchase off the shelf. A good example is Microsoft’s Access, which allows a company to create its own data exchange system. Besides the fact that distributed applications are likely to involve a more highly specialized support staff, they are less likely to be flexible enough to handle all of your business’s needs. You might end up with a dozen small systems each based on a different distributed application rather than a single unified data system.

Some companies have recognized this problem and have developed super applications called Enterprise Resource Planning (ERP). An ERP purports to handle all the data processing needs of any company from a single application. How does this work? Simple, just take all of the functionality of two or three dozen distributed applications and cram them together using a proprietary data exchange model. Then charge tens of thousands of dollars for the program and the highly trained specialists required to install it. Finally, provide a few thousand pages of technical documentation and require an upgrade every couple of years. Sounds great for the manufacturers of ERP applications, but is hardly feasible for the vast majority of data systems.

Of course, I’m sort of biased when it comes to dynamic Web forms. After all, I did write a book about them—so I’m invested in the technology. But, I think most developers that haven’t developed dynamic Web forms before will be surprised at the power and functionality they provide. I’m sure after you spend a little time researching this technology, you’ll see that there really isn’t any meaningful alternative.

Chapter 2: Dynamic Web Form Concepts and Considerations

Overview It’s really simple to slap some HTML and JavaScript on a Web page and create a dynamic Web form. To create a pleasant user experience, however, you’re going to need to take into account a number of issues. First and foremost: most Web users hate entering information into Web forms. Make that Web form confusing, buggy, or unnecessarily complex and you’ll lose those few visitors who actually do want to fill out the form. Of course, some developers have the advantage of designing for a trapped audience. Intranet developers, for example, can force-feed employees just about anything they please—their end users have no alternative solutions. But even so, happy employees are productive employees, and when they hand out those profit sharing checks you may regret not giving a little more thought to your interface design.

A well-designed Web form may not make anyone jump for joy, but a poor design may make the users bang their heads against the keyboard—or worse, take their business elsewhere. A poorly developed Web form is no picnic for the developer, either. User complaints, bug fixes, and workarounds mean more work. Incomprehensible code or a confused development environment can make the hours, dollars, and stress pile up. This chapter covers some of the most important conceptual aspects of Web form design, and a few common practical suggestions to use as you develop your Web form interface. Of course not every project can implement all the suggestions presented here, but it’s still useful for the designer to have a proper grounding in the basics and an understanding of some of the issues involved in creating Web form interfaces. After you’ve completed a few interface design projects on your own, you’ll realize the best gift you can give yourself is to get it right the first time. Usability

If you wanted to put a nail into a piece of wood, what would you use? A rock, a shoe, your own head? It seems simplistic to say that the best tool for hammering is actually a hammer, but this is what usability is all about. Basically, usability breaks down into five simple precepts: 1. Provide the tools. 2. Communicate where the tools are. 3. Communicate how the tools are used. 4. Make the tools work every time for every user. 5. Provide help if the tools don’t work.

Seem simple? Well, in the case of computer interface design, developers have been grappling with this process since ENIAC (that is, the Electronic Numerical Integrator and Computer, in case you didn’t know). In Designing Web Usability, Jakob Neilsen—the leading authority on the topic—says, “Bad usability is like having a store that is on the 17th floor of a building (so nobody can find it), is open only Wednesdays between 3 and 4 o’clock (so nobody can get in), and has nothing but grumpy salespeople who won’t talk to the customers (so people don’t buy too much).” Neilsen was talking about the general design of most consumer Web sites and their relation to their users. For Web forms the issue of usability can be simplified even more: bad usability in Web form design is like asking someone to drive a nail with a broken hammer—they may accomplish the task, but they won’t thank you for it.

Usability Concepts Don’t expect to become a usability expert overnight. Successful professional interface designers spend many years watching users, studying their behavior, and learning the intricacies of proper user interface design. But though you can’t learn everything, you can learn about usability and how to apply the basic principles to your Web forms.

Interface

Any medium whereby two or more sources of information can communicate.

In Chapter 1 I described Web forms as the most directly responsive user interface possible on the Web, but forms are not the only user interface out there. Actually, any Web site is an example of a user interface. Every Web site attempts to supply information to the visitor, and some even gather information using cookies, header information, or server logging. Web forms, however, add another dimension to a Web site. Form users are asked to consciously supply information.

User Interface

Any interface where one or more of the primary sources of information is a human.

It is important to understand this difference, as it adds an extra burden to the Web form developer. A Web site whose sole goal is to increase brand awareness can still succeed with a bad design. The site can accomplish the goal, although dubiously, simply by being seen. But even the simplest Web form requires that both the user and the form complete some function. A form that is never filled out is as bad as a form that never works.

The first step in designing usable Web forms is research. Perhaps the best place to start is to look for examples of Web forms that don’t work. Undoubtedly you’ve been to a Web site that asked you to fill out dozens of form fields just to add your name to a mailing list. Or a form that claims your e-mail address is invalid, despite the fact you’ve been using it for years. Have you ever tried to back up a page on a multi-page form? Ever fill out a form in order to gain access to a restricted part of a Web site and learned later that you’ve signed up for a newsletter you never wanted? If any of these things bother you, they are sure to bother your users. Looking at other Web forms should only be the first step of your research. It is also very important to study the users. This kind of research is generally termed usability testing. Usability testing is a multimillion-dollar industry that has been used to study everything from how drivers operate their vehicles to how people hold their pens. If your project budget will let you hire a usability-testing firm, by all means do so. But even if you don’t have sufficient funds, take some time to do some user research on your own.

The basics of usability testing can be quite simple. Sit down and ask a typical user to perform a series of tasks using the interface. By watching how the user performs each task you can determine problem areas, establish a typical workflow, and generate concepts for improvement. After the tasks are done ask the user questions about the interface: What did you have difficulty with? Was there anything that seemed unintuitive? How could the interface be made better?

Some of the most revealing usability testing doesn’t even have to be done on your own interface. If you have a competing product available, usability testing on that can help make yours better. In other cases Web forms are developed to replace older legacy systems. Watch how these old interfaces are used and spend a lot of time talking with the users. After all, these will be the people who’ll come to you if your interface doesn’t do what they need.

If you’re really lucky, all that research will give you a priceless gift: an insight into how the user thinks. This leads directly to the second step in usable Web form design: keeping a user-centered mindset. This sounds easy; it’s not. Developers are often tempted to create an interface that is programmatically very simple but ends up being unnecessarily complex for the user. On the other side of the coin, they may feel the need to create a spectacular-looking form that won’t work at all for users of lynx.

Of course you can’t always jump through hoops for the users. Sometimes the detriment of having to write 200 extra lines of code outweighs a minor benefit to the user. Creating a form that works on every browser ever made but that puts users to sleep doesn’t help much either. The key isn’t to become a slave to the user, but to keep in mind users’ needs, wants, and expectations.

After creating your interface the final step is testing. Skip this step at your peril. There’s far more to testing than simply seeing if your form properly submits the data, although that’s certainly an important part. You must also be sure that the form is actually likely to accomplish the task. Designers and developers, especially the good ones, have a tendency to get drawn into their work. While they are stressing about minutiae they can easily miss glaring problems. Be sure to test both the technology and the user. A round of usability testing should precede any release. Remember: the user is one half of your form interface, and in the final analysis designing and testing for the user will save you more time than it costs.

Accessibility It goes without saying that if your users can’t see your form then they can’t use your form. While it is unrealistic to believe that you can design an interface that will work perfectly for everyone, it is important to try to develop for the largest possible audience. Whenever feasible, offer options for those users with special needs or out-of-date software. Getting users to enter form data is an uphill battle; don’t add to the burden by creating a design that inherently excludes some users.

Intranet and extranet developers would be wrong to think that they don’t have to worry about accessibility. Even though you may think you know about all your users, there are always a few unknowns. You may know what brand and version of browser your users have installed, but how long will that last? A browser version change can wreak havoc with client-side scripting and form layout. Are you prepared? What’s the monitor resolution for your users? Is there anyone who is visually impaired? How many of your users have images or cookies turned off? Can your form work for all these people? Designing for the Visually Impaired

I have to wear glasses to drive. A coworker of mine is color-blind. Another requires a special monitor shade to block out low-frequency radiation and glare.

Quite a number of people have some kind of peculiarity relating to sight. When designing anything for the Web, especially a form that you want people to use, you need to remember that not everyone has 20/20 vision. Spend a little time surfing the Web and you’ll come to the conclusion that most sites don’t take this into account. Font sizes are often fixed to a small size to increase aesthetics. Graphics are used instead of text because a specific font or effect was desired. Web sites are designed for users of large monitors. Low-contrast or graphical backgrounds are everywhere. These issues are not uncommon, and they can be problems for some users. Most people simply have to make do with what they see on the Web.

Recently, however, there has been an increased push toward accessibility on the Web. The U.S. government recently added Section 508 to the Rehabilitation Act. In a nutshell, Section 508 establishes accessibility requirements for any electronic documents or applications developed, maintained, procured, or used by the federal government. In addition, the World Wide Web Consortium (W3C) has begun the Web Accessibility Initiative (WAI), which is designed to promote acceptable Internet functionality for everyone regardless of disability. Find It Online You can find out more about Section 508 and the Rehabilitation Act at the government’s Web site: http://www.section508.gov/. Information about WAI can be found at http://www.w3.org/WAI/.

Use the following tips to design for visually impaired audiences: § Never set the font explicitly in pixels or points. Use relative font sizes only. § Avoid using very small fonts. § Use only high-contrast colors for the background and text. Black text on a white background is the best. § Avoid graphical text or buttons whenever possible. If you must use them, be sure to include an “alt” value. § Organize your HTML so that an auditory browser can understand it—auditory browsers read from the top of the HTML down. Tables in particular can produce unintended results. § Consider using CSS aural tags. § Most importantly, talk to visually impaired users and get their input. Designing for Non-Graphical Users

Not everyone can afford to fly on the Concorde, and not everyone has T3 Internet connections. Many users will choose to save bandwidth by setting their browsers to not download images. Others just use a text-only browser such as lynx. In these cases the basic form elements still work, but any graphics will be missing. Most of us are so used to the graphical nature of the Web that we don’t realize just how integral graphics are as a part of our design. Navigation is often accomplished using image maps. Graphical buttons replace text links. Image roll-overs are commonplace. But what happens to these pretty sites when the graphics go out?

Here are a few tips for designing for the non-graphical user: § Don’t use image maps. § Use text links whenever possible. § Include the “alt” value for all images, but don’t rely on it. Many browsers don’t fully display the text, or it may be invisible because of a dark background. § Don’t use images to display information. § Test your interface with the images turned off or on a text-only browser. Find It Online The lynx browser can be found at: http://lynx.isc.org/ or visit http://www.delorie.com/web/lynxview.html for an online lynx emulator. Designing for Multiple Browsers

It’s likely that very few of the users who come to your site will be using lynx. Most will be using some version of Microsoft’s or ’s Navigator. Anyone who’s developed for the Web has bemoaned the fact that these two companies couldn’t get together on a set of standards for displaying Web pages. But even within a single product there are vast differences. Take, for example, the jump from Netscape 4.7 to . The newest version includes a completely new display and scripting engine. Pages that look one way on version 4.7 look significantly different on version 6.

Some users may be using browsers you’ve never heard of. Opera, iCab, Konqueror, Ariadna, HotJava, WebTV, and are just some of the alternative browsers available. Each one displays HTML and runs JavaScript a little differently.

Designing for multiple browsers isn’t as difficult as it seems. Here’s a bit of advice for multi-browser design: § Know your audience. An intranet developer should be able to eliminate all but the most popular browsers from the possibilities. Additionally, Web server logs can tell you a lot about the browsers your visitors are using. § Don’t sweat the small stuff. Don’t spend weeks trying to make the site perfect on one minor browser. It’s likely that any change that will work will adversely affect the site on other browsers. Concentrate on the big browsers. § Test on multiple browsers and platforms. IE 5 looks a lot different on a Macintosh than on a PC. § If possible, provide alternative pages on a per-browser basis. Both server-side and client -side scripts can detect the browser type. If you have the time and resources, use that to your advantage. § Degrade the site gracefully. Provide non-JavaScript alternatives and layouts that don’t require style sheets or tables. Find It Online A treasure trove of old, international, and rare Web browsers is available at http://browsers.evolt.org/.

Technical Support

Let’s face it: things break. Whether you’re designing cars, buildings, bridges, or programs, eventually even the best-laid plans fall victim to age, inattention, the elements, or simple entropy. Web forms are no different. Databases and servers crash in the middle of the night, new browsers butcher your perfect interface, a key file is accidentally deleted just before the automatic backup, hackers replace your site with a tribute to Bob Barker—these are just a few of the problems you could encounter. While you can’t be ready for every situation, you can keep your user interface responsive and flexible enough to quickly diagnose any problems that occur.

All Web forms will break. Accept it. Someone out there will type out their age alphabetically in a field you didn’t think to validate. Another individual will decide to register his 11-month-old son on your Web site only to receive an e-mail note wishing the infant a happy 101st birthday a month later. An international user will enter a non- Gregorian date on an online invoice. Daylight savings time will cause an e-calendar user to miss an important meeting. Of course, just because you know that your Web form will break, that doesn’t mean there’s nothing you can do about it. Your primary defense against problems is data validation. Save yourself a migraine and validate every single field right from the beginning. Validation for Web forms means that the data type and size of a submitted value must fit within the acceptable parameters of the storage medium. In short, if a database field only takes integer values, be sure that the data submitted by the user is an integer. Don’t forget about the size of the data either. Many databases accept no more than 255 characters for varchar fields. Others might have limits on the size of integer values or defined date value limits.

Here are a few things to look out for when validating form information. These tips apply to server-side validation. § Never assume the user won’t enter bad data. You’ll discover early on that people are more confusable than you’d think possible. § If feasible, limit the users’ choices using selector elements such as check boxes and radio buttons. § Validate important fields twice: once on the client, once on the server. A little later on I’ll explain the security reasons for this. § Don’t assume that the maxchar attribute or JavaScript functions will limit user input. Some users may be using incompatible browsers and malicious users can easily get around these precautions.

The next defense against the inevitable problems is training. Some of the biggest problems for end users are not bugs or errors but simple confusion. If the users don’t know how to properly work the user interface, expect the tech support e-mail messages to pile up. The issues of usability and accessibility discussed earlier can go a long way toward resolving this issue, but even the simplest interface can be trouble for some people. You need to provide a way for your users to learn your interface, find answers to questions, and contact someone who can solve any additional problems. The easiest way to be sure that a user understands how to use an interface is to provide a tutorial. Even intranet or extranet users who have been personally trained by a technician can benefit from an online tutorial. After all, memories are short and not everyone takes great notes. Tutorials can be a simple text-and-image slide show, a dynamic interactive interface, a full-blown multimedia presentation, or anything at all that explains in plain language how to use the Web form application. The best tutorials include a walkthrough of a common scenario using the interface. Professional application developers have long understood the need to provide immediate technical assistance (see Figure 2.1), but sadly this understanding hasn’t transferred well to the Web. For most Web sites and Web forms the only technical assistance is an frequently asked questions (FAQ) page. If done well, an FAQ can be a great resource, but most sites only provide a half-dozen or so questions and answers. Others are crowded with hundreds of questions, leaving the user lost in a sea of unnecessary information. For any truly usable and complex Web form you will need something beyond an FAQ.

Figure 2.1: Windows 2000 and MacOS X Help screens

Return again to the concept of a user-centered mentality and think about it from the user’s perspective. A user who encounters a problem at a specific point in a Web form doesn’t want to go to a new page that answers only general questions or is cluttered with answers that don’t apply. Users want help about their specific problem at the specific point in the Web form application. Ideally they shouldn’t have to go to another page or log into a special help site; they want the help in the context of their current situation. Context-sensitive help is not a new idea. Operating systems and applications have used context-sensitive help for years. The concept is to get the information as close to the end user as possible without distracting the user or interrupting the workflow. An obvious (and annoying) attempt at context sensitive help is the Microsoft Office paper clip (see Figure 2.2). “Clippy” provides useful information about the current application, document, or function, and provides links to get more information. It also serves as the entryway for a larger, indexed help system.

Figure 2.2: Everyone’s best friend, Clippy

I definitely don’t recommend putting a talking paper clip on your Web form, but I do show a simple context-sensitive example in the third project of this book. Feel free to expand or modify it to meet your needs. Whatever help solution you go with, here are some issues you should consider: § Don’t make the users lose their place by leaving the page to get help. § Provide short bits of information, but give the ability to get more in-depth coverage. § Provide links to FAQ pages, support forums, or contact lists if available. § Be consistent in the way help is accessed, displayed, and used. § Keep the information up-to-date. § Make the system expandable to handle future growth. For an interesting article on one idea of how context-sensitive help applications should look, visit http://www.smountain.com/resource/ContextHelp.pdf.

Finally, in case the context-sensitive help, tutorials, and FAQs don’t work, you’ll need to provide a way for users to contact someone to solve their problems or simply hold their hand as they go though a transaction. Hopefully you can provide more than a general customer support e-mail address. If yours is a large project with a big budget you might consider purchasing a toll-free number for 24-hour assistance or developing a real-time, IRC-like communication system for technical support. Even developers of small projects should consider adding a technical support e-mail address to a contact list. Your goal should be to provide the most accurate, timely, and personalized assistance possible.

Security

Those who need to spend the most time on security issues are the ones who don’t spend enough time dealing with security. For the Web, security is more than just a strong firewall. It means safeguarding client information, protecting data assets, preventing fraud, and, most of all, creating a trusting relationship between your organization and the users.

Perception of Security

In terms of the Internet, perception is key. It doesn’t matter if your site is the Web equivalent of Fort Knox, if the users perceive it to be a threat to privacy, pocketbook, or peace of mind they will take their business elsewhere. You must create an environment where the users can feel safe entering credit card numbers or other valuable personal information.

The first step in creating a trusting environment is to tell the users how the information is going to be used. Can you think of a reason why some registration forms ask for your Zip code but not your address? Did you ever wonder why an online bookstore needs your work phone number? Is it really necessary to declare your gender before joining a mailing list? Requests for this kind of information undermine the trust between the users and Web sites. If users don’t know what you’re going to do with a piece of information, their imaginations will create any number of unhappy possibilities. Preventing this can be as simple as providing a separate “how this information is used” page or, even better, text right on the form itself explaining how each field is to be used. You might find that you can easily add this text to an existing context-sensitive help system. Once you start explaining why you need the information, you’ll begin to see the importance of collecting only the information that you absolutely require. Differentiating between optional and required fields is a start, but many users will still make an unconscious connection between this unnecessary information gathering and a threat to privacy. A better option is to completely separate the required and optional portions of any form. For example, to send users an e-newsletter you only need their e-mail addresses. Later on, users could be given the option of personalizing their e-newsletter service by answering a few questions. A similar technique could be used for site registration, forum membership, and online commerce transactions.

After you have explained how the information is to be used, you need to reassure the users that unwanted outside forces can’t access the information. You can accomplish this in two steps: provide a functioning privacy policy, and explain the secure server transactions involved. Explaining Your Privacy Policy

Privacy policies are all over the Web. More people are concerned about online privacy than ever before, and the best reassurance a user can have is an effective privacy policy. An effective privacy policy is more than just a page that informs users that their information won’t be sold to a telemarketing firm. A privacy policy should state what information will be shared, how it will be shared, and with whom it will be shared. Give examples of how the information has been shared in the past. Explain the types of privacy restrictions third parties are required to follow. Be sympathetic to the privacy concerns of the user. Write the policy in plain language, not legalese—the personal touch can do wonders. Finally, stick to the policy. Avoid changing your privacy policy unless absolutely required, and never, ever break it. Once trust is lost, it’s ten times harder to get back. Explaining Server Security

The final step in creating a perception of security is to assure users that their information is safe from hackers and other malicious agencies. Internet users are constantly bombarded with news of hackers stealing credit card numbers, personal information, or mailing lists. You need to convince them that this won’t happen to your site. There are hundreds of security options ranging from multimillion-dollar “bank-vault” systems to open source firewalls—and in the end no security system is perfect. From the users’ perspective, however, it doesn’t really matter what the security measures are, just that you have taken the time to consider the issue and are prepared for any attacks. This doesn’t spare you from creating the most secure transaction system feasible for your project, but it does mean that you need to consider how to communicate your security to the user.

Giving precise product and version numbers of your security products is an invitation to hackers, but you can explain the types of products and technologies you use to protect sensitive data. Stress your dedication to security and your sympathy with users’ concerns. Give an account of your site’s security history. Has the site ever been hacked? What information was compromised? What steps have been taken to ensure it won’t happen again? Express your confidence in your security, but don’t be boastful. Secure Sockets Layer (SSL)

Some users never feel safe transmitting personal information across the Internet. Savvy users know that third parties can intercept unsecured transactions, but secured transactions can be reasonably safe. The basic rule of thumb is that any transaction involving money (including e-commerce), sensitive personal information (contact information, medical history, employment history, and the like), or confidential business information should be handled using a secure transaction system.

Secure Sockets Layer (SSL) is the most common way of securing an Internet transaction. Developed in 1994 by Netscape, SSL is an open standard that uses a system of public and private keys transmitted between the client and server. SSL uses a certificate system to allow the client to verify the identity of a server. Only a designated Certificate Authority (CA) can issue SSL certificates. The client is alerted to any outdated or invalid certificates. The latest version of SSL uses the same system for servers to validate the identity of a client. Finally, SSL mandates that all information that is transferred between client and server be encrypted before transmission. SSL has a number of functions to determine whether a transmission has been tampered with.

The first step in setting up an SSL connection is to provide a Certificate Signing Request (CSR) to a CA. The price of SSL certificates varies from authority to authority, but expect to pay between $100 and $200 per year. The CA will provide you with an SSL key certificate. This key is the file that allows your server to decrypt data transferred from the client and allows clients to validate the server identity. Using certificates to validate client identity is optional and can be a bit more complicated. To create an SSL client certificate, each user must submit a request CSR to the CA. These certificates are stored within the browser and are not transferable. Most transactions do not involve client validation, but business-to-business interfaces and some intranet solutions should consider using that approach. Tip A number of applications allow a server to be its own CA. While this isn’t recommended for public sites, interfaces that require client validation can save a lot of money by issuing their own SSL certificates.

After the certificates have been established, the Web server needs to be configured to use SSL. See your server documentation for instructions on accomplishing this. Typically specific directories or pages will be marked as secure and will activate an SSL transaction, while the majority of the site is unsecured. SSL data is transferred using HTTPS. HTTPS is the set of transfer protocols that determines encryption and transmission of data through SSL from client to server. All of the latest browsers and servers support SSL.

Data Validation

Beyond data encryption and certificate authentication, developers of Web forms have to take into account straight data validation. An SSL certificate can’t prevent a user from entering data that will, intentionally or otherwise, bring your interface to its knees. The only protection against dangerous data is validation. When to Validate

This book is designed to allow a developer to take advantage of the full power of Web forms by using a combination of client - and server-side scripting. Throughout, the emphasis is on combining the needs and wants of the user with the functionality the developer desires. This concept should apply to data validation as well.

First, validate the basic form contents on the client using JavaScript. The client should not have to wait for a call to the server to find out that the password field requires more than two digits. Client-side validation assists the user, who benefits from the faster, more context-sensitive responses. Client -side validation does not need to be as strenuous as the server-side validation covered earlier, but it should be used for the more common mistakes. Dates should be checked for proper formatting, submitted text should be checked for proper size, integer values should be reviewed against possible values.

Next, validate on the server. Earlier I covered the basic concepts of server-side validation, but from a security standpoint server-side validation takes on a new urgency. Web form interfaces that do not use SSL are particularly vulnerable to security breakdowns relating to a lack of proper server-side validation. All the client-side scripting in the world won’t prevent a hacker from copying the source code and modifying the JavaScript. Revalidating the data after submission is the way to prevent an attack like this from being successful. How to Validate

The validation process includes three steps: 1. Validate that all required fields have been submitted. 2. Confirm that each field contains the correct type and format of data. 3. Determine that the data itself is correct.

The type of scripting language you use for validation isn’t important as long as you include all of these steps.

Here are a few security considerations to keep in mind when validating Web forms: § Don’t trust data in hidden fields. Never use a hidden field for product prices, SQL commands, or tamper-sensitive information. Hidden field values are very easy for a hacker to manipulate. § Don’t rely on form values for more than the basic information. For example, an e-commerce site that uses a form to submit a sale should submit only the product name or ID and the quantity. Price, tax, and product information should not be submitted in a form. Otherwise a malicious user could change data that should remain static. § Always hash data before submitting to a database. Hashing data means escaping out special characters such as double quotes and brackets. Hackers can always try to enter unwanted JavaScript or SQL commands into text fields, and hashing prevents these commands from being executed.

Best Coding Practices

If you were to take a look at my office you’d see open books scattered about the room, along with index cards, sticky notes, papers, writing implements, office supplies in various states of repair, and computer equipment (working and otherwise). The system works for me because I use my office on a daily basis and I’m the only one who ever goes in there. If you’ve got a great memory and are the only developer on a project you might be able to get away with that kind of clutter in your code. The rest of us need to organize, standardize, and label.

Each development team or individual should define—before the project begins—a set of best coding practices. This document defines the steps in the development process and explains the required procedures. The advantages of a defined process are obvious. Developers can easily fix, update, and use each other’s code; the workflow isn’t hampered by duplicated work or split resources; and locating and fixing code-based problems is streamlined. Code Commenting and Formatting

Imagine an interstate highway system without any road signs—that’s what working with uncommented, sloppy code is like. It becomes easy to get lost and valuable time is spent simply figuring out what is going on. Commenting

Code commenting is a lost art among Web developers. Most scripts have little more commenting than a copyright notice at the top. You can take this one of two ways: Web developers are overworked and underpaid and simply don’t have the time or resources to spend commenting code—or Web developers are shortsighted and lazy. I think it’s a combination of both.

No one likes to spend valuable coding time documenting how each and every function or subroutine works, but commenting can be invaluable. First, commenting makes the original developers think about their code in terms of how others will view and use it. This alone is a reason for enforcing a commenting policy. Many developers will produce cleaner, less buggy, and more sophisticated code when they take the time to comment each code step.

Second, commenting makes it easier to remember what code does. Many times I’ve come back to a tricky code block after a long weekend only to discover that I don’t remember where I was in the script. It’s more than just remembering the exact bookmark; I’ve forgotten the thought process that I was exploring and the options that I’ve already tried. Commenting allows developers to easily pick up where they left off.

Finally, and most obviously, commenting makes code easier to understand. Take the following block of JavaScript: function move(f,truefalse,sName) { var el = f.elements[sName]; var selectedItem = el.selectedIndex; if (selectedItem==-1){ alert("You must first select the item to reorder.");} else { var nextSelectedItem = selectedItem+( truefalse? -1 : 1); if (nextSelectedItem<0) { nextSelectedItem=el.length-1;} if (nextSelectedItem>=el.length) { nextSelectedItem=0;} var oldVal = el[selectedItem].value; var oldText = el[selectedItem].text; el[selectedItem].value = el[nextSelectedItem].value; el[selectedItem].text = el[nextSelectedItem].text; el[nextSelectedItem].value = oldVal; el[nextSelectedItem].text = oldText; el.selectedIndex = nextSelectedItem;}}

Anyone trying to modify this function would have to work through it step by step. Each parameter would have to be traced through the function. Control structures would have to be evaluated. In short, unfamiliar developers would have to recreate the entire functionality inside their heads. This adds time and frustration to any project. Here are some recommendations to use when developing a commenting policy: § Standardize your comments. Put them in the same place every time. Make them easy to find. § Describe the purpose of each page, function, subroutine, class, or code block. § Label variables the first time they occur in the document and the first time they occur in each function. § Explain control structures such as loops and conditionals. § Describe the destination and content of any output. § Explain the source and content of any input. § Include a large comment block for any major overhauls or changes. § Sign and date all code. Formatting

Don’t underestimate the ability of sloppy code to waste your time. Any minuscule amount of time that a developer might save by not putting in a few regular blocks of spaces is eclipsed by the hours another might waste trying to figure out a series of nested if statements. Formatting is all about readability. As an example, look at the JavaScript from earlier, now that it’s been formatted and commented: /* This function runs the up and down buttons that reorder a select box. Passes through the name of the form (objForm), whether to move up (boolUp) and the name of the select box (strElementName). */ function move(objForm, boolUp, strElementName) { var objSelectBox = objForm.elements[strElementName]; // objSelectBox = the select box var objSelectedItem = objSelectBox.selectedIndex; // objSelectedItem = the selected option in the box if (objSelectedItem == -1) // ensures that an option has been selected. { alert("You must first select the item to reorder.") // this error message appears if no option has been selected to move } else { if (boolUp) // determines whether to move Up (backward) or Down (forward) in the element array { var objNextSelectedItem = objSelectedItem - 1; // objNextSelectedItem = the previous option } else { var objNextSelectedItem = objSelectedItem + 1; // objNextSelectedItem = the next option } if (objNextSelectedItem < 0) // determines if option to move is at top (beginning) of the select box { objNextSelectedItem = objSelectBox.length - 1; // change objNextSelectedItem to the last option } if (objNextSelectedItem >= objSelectBox.length) // determines if the option to move is at the bottom (end) of the select box { objNextSelectedItem = 0; // change objNextSelectedItem to the first option } var strOldVal = objSelectBox[selectedItem].value; // strOldVal = the value of the originally selected option var strOldText = objSelectBox[selectedItem].text; // strOldText = the text of the originally selected option objSelectBox[objSelectedItem].value = objSelectBox[objNextSelectedItem].value; // replace the value of selected option with value of a neighbor option objSelectBox[objSelectedItem].text = objSelectBox[objNextSelectedItem].text; // replace text of the selected option with value of a neighbor option objSelectBox[objNextSelectedItem].value = strOldVal; // replace value of a neighbor option with value of the selected option objSelectBox[objNextSelectedItem].text = strOldText; // replace text of a neighbor option with text of the selected option objSelectBox.selectedIndex = objNextSelectedItem; // select neighbor option, which now has value and text of original option } }

Yes, it takes up a lot of room and the comments are a little verbose, but now even someone who has no experience with JavaScript can follow the code. Actual developers should be able to jump in and begin making changes in mere seconds.

The formatting conventions you use should be tailored to needs of the development team, but here are a few suggestions: § Add extra empty lines around functions, subroutines, classes, and code blocks. § Section control structure using tabs or multiple spaces. § Avoid programmatic shortcuts if possible. § Avoid splitting up a process unnecessarily. For example, when you declare long strings, don’t use multiple declarations and concatenation operators when a single declaration will do. § Put comments on their own line. § Put spaces around operators. § If quotation marks, semicolons, or other code elements are optional, either always use them or always leave them out.

Naming Conventions

Call me Dan, or Daniel, or Danny, or son, or buddy, or dude (just don’t call me Ishmael). When it comes to people we can pretty much call them anything we can get away with, but as you’ve learned, proper coding is all about standardization. Before two developers can work together on a project they have to agree on what the project entails—they have to define it. Naming conventions give you a way to define the common elements of a scripting environment so that developers can communicate using the same basic parameters. In a way it’s like defining the grammar that developers should use. Hopefully, they already have the vocabulary; that may be enough to get basic ideas across, but only an organized and defined grammar system can facilitate complex communication. File System Naming Conventions

Naming conventions must start at the file level. More than once, on projects with disorganized file systems, I’ve spent more time tracking down where the problem code is than actually changing it. A properly organized file system and naming convention can give a developer a head start on tracking down problems. Additionally, file systems are among the more difficult parts of a Web site to change after it has been established, so getting the structure correct from the beginning is critical.

Each file system convention should be tailored to the specific project, but here are a few recommendations: § Divide the pages of code along the same lines as the Web site. A folder should exist for each of the major areas of the site. Global code should have its own folder. § Consider separating the following types of pages into different folders when possible: server-side scripts, client-side scripts, HTML, CSS or other style elements, and Flash or Shockwave files. § Images should always have their own folder at the root level. Divide images up based on type rather than page (headers, form elements, navigation, and so on). § PDFs, movies, audio files, executables, and other media files should all have their own folders.

Once you have a file system ready, you can put your files into it. Now, however, you need to consider what to name those files. It would be nice to name files in great detail— say, This_Code_Works_The_Search_Function.php—but most developers are a little more pragmatic than that. Considering that many developers work from the command line, where they have to type in file names by hand, it is not surprising that file names are often truncated.

You have two choices when it comes to naming files: let all the developers name the files as they create them and hope that the chosen names have some kind of vague relation to the actual function of the page, or create and enforce a file naming convention. I’ve seen what happens when developers are given a free hand. On a recent project two different displays of an “Ask the Experts” section were called for: a normal display and a smaller teaser. The developer decided to name the files bigask.php and smallask.php. So naturally whenever anyone had to make a change to the page the developer would be told to modify “that bigask file.” Good for a laugh, but not very informative.

Here are a few things to consider when developing a file naming convention: § If some of your users have older browsers, then images should be named with the eight -dot -three format. Some of the older browsers versions won’t accept longer names. § Name display and formatting pages as close to the actual URL as possible. For example, if a page is located at www.mydomain.com/catalog/books/, name the display file books.html and the formatting page books.php. § Mark files that are included with an initial “i”, as in i_global.php, i_ formFunctions.js, i_style.. § Use a defined set of extensions: JavaScript (.jss or .js), CSS (.css or .cs), HTML (.html or .htm), PHP (.php), ASP (.asp), ColdFusion (.cfm). § Capitalize every word after the first. For example: catalogByDate.php, confirmTransaction.html, i_validateUser.js. Caution Remember to use the correct file capitalization inside your code as well. Even if you’re not using Unix, your users might be.

Function and Object Naming Conventions

The next step in the naming convention process is to standardize the names of functions and other programming objects. Scripting languages tend to have many built-in functions and objects. Sometimes it can be difficult for a developer to tell if a specific function call is referencing a built-in or custom-defined function. A function naming convention can solve this problem. Additionally, a function naming convention can make it easier for a developer to determine the general purpose of a function. Note The class-based system of JSP makes it possible to overwrite built-in classes—and often requires that built-in classes be overwritten. Also, multiple classes can have the same name. This makes sticking to a JSP class naming system extremely difficult. Likewise, ColdFusion doesn’t allow for user-defined functions. Developers who are dedicated to these scripting languages should skip ahead to the next section on variable naming.

Below are a few function and object naming basics that I use to keep my code organized. Of course, these are just suggestions. § Capitalize every word after the first. § Mark user-defined functions with a preceding “f”, as in f_getData(). § Mark user-defined objects and classes with a preceding “o”, as in o_catalogItem. § Designate functions that modify the external environment with a trailing “x”, as in f_swapImage_x(). § Designate object and class methods with a preceding “m” and use “p” for object properties. Chapter 3 includes some suggested rules for naming form elements and other HTML objects. Variable Naming Conventions

The final stage in developing a naming convention is to define to proper syntax for variables. Variable naming is an extremely common part of any coding process, so developing proper procedures can make a big difference in the readability of code. Beyond making code dramatically easier to understand, variable naming conventions often prevent bugs such as improper variable capitalization, misplaced variable scope, and accidental reuse of a variable name. I recommend giving variables a prefix based on the (most likely) data type. Table 2.1 provides a breakdown of some sample prefixes—but note that not all scripting languages use all data types. Table 2.1: Variable Prefixes Data Type Prefix Example

byte byte byteTool

short shrt shrtField

integer int intLocation

long long longMole

float flt floatPi

double dbl dblMolePi

character char charFirstLetter Table 2.1: Variable Prefixes Data Type Prefix Example

Boolean bool boolTrue

date date dateYesterday

point pnt pntLocation

time time timeNoon

void or null void voidNull

string str strName

hexadecimal hex hexRedColor

octal decimal oct octIPAddress

array arr arrFormElements

object obj objTextBox

variant or unknown var varUserInput

Additionally, here are a few suggestions to consider when creating a variable naming convention: § Capitalize each word. § Name constants in all caps with underscores between words. For example: SERVER_NAME, BACKGROUND_COLOR, PAGE_NUMBER. § Avoid numbering variables. § Loop counters can be single characters. For example: for (k = 0; k < 9; k++), where k is the variable. § Popular global variables should be noted: intGlobalBrowserVersion. § Variables limited in scope to a single function or class should be indicated: intSwapOldSRC, intSwapNewSRC. § Make the names accurate and meaningful.

Version Control

Now that you’ve assured yourself that every developer on your project speaks the same language, you need to make sure that they’re not all talking at once. Version control is a way for a project team to coordinate the modification of source files. A good version control system has two functions: it ensures that multiple users do not attempt to modify the same file at the same time, and it provides a way to track changes to source code. Concurrent Version Control

Have you ever seen two ball-players crash into each other at high speed while trying to catch the same pop-fly? Ever imagined how that must feel? Well, the same type of sensation occurs (without the bruises, of course) when two developers try to modify the same source file at the same time. Changes are made by one programmer, then undone by another, then remade by the first only to erase the changes of the second. Inevitably, this leads to increased project times and hotter tempers. Concurrent versioning can prevent one developer from stepping on the toes of another.

Concurrent versioning is a process that lets developers share control of different versions of files in a common repository of files. A developer will typically download or “check out” the latest version of the source code. After changes are made the source code is checked against the latest version of the file in the repository. If another developer has made a change to the same file a warning is given. Concurrent version software typically marks the specific sections of code that are in conflict. These conflicts can then be investigated and merged or discarded if necessary. Revision Control

Who modified that file? When was that change made? Is the unmodified code still available? These are the types of questions that a revision control system can provide. Revision control involves marking source code files with the name of the developer and the date each time the file is modified. Each change is documented allowing a developer to restore code that has since been modified.

Revision control systems work much like concurrent version systems in that typically a developer checks out a file or a set of files, makes changes, and then submits the files back to the repository. The revision control application notes what section of the code has been modified and marks each file with the developer’s name, the date, and any notes that the developer may choose to leave regarding the changes. These notations are made available through the revision control system to any user who requests them.

Chapter 3: HTML Forms Any Web-based form begins with HTML. As a Web developer you no doubt already have a good grasp of how HTML works. This chapter will simply review the HTML tags associated with Web forms and give a few introductory examples of how these form elements can be used. Many of these examples use JavaScript, but don’t worry if you aren’t very skilled in using client-side JavaScript; these are just quick samples of what is possible. Chapter 4 will explore JavaScript and its use in Web forms in more detail. Experienced HTML coders can skip this chapter, but you might want to take a look at the example files, as some of the techniques may be new to you.

Form Elements Before you learn about the individual form tags and elements it is important to understand how HTML forms work. Web forms pass information to the server in name- value pairs. This means that the name of each part of the form is linked to the corresponding value. Values can be user-entered text, a selected option, hidden variables, a file location, or similar information. The processing page or application can then reference the submitted value through the assigned name from the form. Name- value pairs are submitted to the server either in the requesting URL or in the page header, depending on the method attribute assigned to the FORM tag. Each part of the form is called an element. Elements come in four types: control elements, text input elements, selectors, and special elements. Each element has a number of properties that can be assigned. These properties are called attributes. In this chapter attributes in bold are mandatory, and text in italics represents an example of a possible attribute value. For example, in this code text: Type=hidden and name are mandatory while formHidden and Hidden Value are sample values.

The Form Tag Every HTML form begins and ends with the FORM tag. The FORM tag names the form and defines the server-based page or program that will process the form information. The basic structure of this tag is as follows:

Following are brief descriptions of the most common attributes for the FORM tag: § action. This attribute holds the URL of the processing Web page or application. The URL can be relative (../cgi-bin/form-processor.cgi) or absolute (http://www.premierpressbooks.com/search.php). action is the only required attribute of the FORM tag. The action attribute can also be set to a mailto value. A form with a mailto action will use the visitor’s own e-mail software to submit the form via e-mail. Most of the time this results in a security warning like the one shown in Figure 3.1. Most form designers get around this by submitting the data to a processing page that can create e-mail using a server-based e-mail client.

Figure 3.1: A mailto form security warning § name. Use this attribute to give the form a unique identifier. Client- or server-side scripts can then use this identifier to distinguish form elements and values specific to each form. The use of this attribute has mostly been deprecated in favor of the id attribute, but some older browsers and server-side scripts don’t recognize id. To prevent potential errors, it’s a good practice to always include identical name and id attributes for the FORM tag. § id. This is an attribute for establishing a unique identifier for the FORM tag. This attribute can be used by CSS and JavaScript to reference a specific form. § target. This attribute determines the frame or window in which the form-processing page will appear. For example, a Web page that has a search form in a navigation frame can make the search results appear in the main body frame. Possible values of the target attribute are: "_self" (the same window/frame), "_top" (breaks out of a frameset and places the resulting page in the current window), "_blank" (opens a new window for the results), "_parent" (opens the results in the parent frameset of the current window), windowName (opens the results in the named window), frameName (opens the results in the named frame). The default value is "_self". § title. Use this attribute to assign text to the FORM tag. This text can be used for context-sensitive help, tool tips, or as accessibility instructions for auditory browsers. § enctype. enctype sets the MIME type used to post the form data on the server. For most forms this value is application/x-www-form- urlencoded. Generally there is no reason to assign this value, as it is the default used for Web forms. Other possible values include multipart/form-data (used for forms that include files), and text/plain (used for mailto forms). § method. The method determines how the data is submitted to the server. The default value, get, appends the name-value pairs to the URL. The processing page can then retrieve the form information through the environmental variable (QUERY_STRING) or by parsing the URL string. As the length of any URL is limited the get method should only be used for short forms with limited user input. The most common usage of the get method is in search forms. With a get form the resulting pages can be bookmarked or shared without requiring the form to be reentered. The post method sends the form data to the server in the HTTP Header. The submitted form information is hidden from the user and the processing page cannot be bookmarked or shared with the form information intact. The post method should be used on any form that uses the fileupload, textarea, password, or hidden form elements. § accept-charset. Use this attribute to specify the character set that the processing page or program should accept. Multiple character sets are comma delimited. The default value is ISO-8859-1. You should use this attribute if you expect your users to use multiple or unusual character sets, such as Japanese or Korean. The FORM tag can accept style attributes such as class and style and the standard event attributes such as onClick, onMouseOver, and onMouseOut. Additionally, two FORM specific event attributes are available: onSubmit and onReset. OnSubmit is fired when the form is submitted. The most common use of this attribute is to validate form input through JavaScript on the client. By combining the onSubmit attribute with JavaScript and the return false command you can prevent the form data from being submitted unless specific conditions have been met. Note The file name and contents are reprinted here for your convenience. Throughout this book you will find similar references to files located on the Web page for this book, at http://www.premierpressbooks.com. confirm-form.html Confirming the Form Submission

The onReset attribute works in the same way, except that it is fired when the form is reset, either from a reset button or a script command. This attribute can be used to confirm the deletion of entered data or to reset JavaScript variables to the initial values.

Control Elements

Form control elements include the following input types: submit, reset, button, and image. Unlike the other form elements, these do not collect user information. Rather, they allow the viewer to control the actions of the form itself. These elements can be used to submit the form, clear the form contents, or fire JavaScript functions. The Submit Button The submit button is the most common of the control elements. It represents the easiest way—and for non-JavaScript browsers the only way—to submit a form and send the data to the processing page or program specified in the action attribute of the FORM tag. Clicking the submit button also fires the onSubmit event for the form. The basic structure of this tag is

Here’s a breakdown of the attributes available for the submit button: § name. Use this attribute to give the submit button a unique identifier. Control elements are the only form elements that do not require the name attribute. § id. This attribute can be used by CSS to reference this element. § title. Use this attribute to assign text to the submit button. This text can be used for context-sensitive help, tool tips, or as accessibility instructions for auditory browsers. § value. The value of a submit button serves two purposes. First, value represents the text that appears on the button. This text should be instructional and unique. Second, the value of this attribute is posted to the server with the rest of the form data. This allows forms to have multiple submit buttons. The server-side processing scripts or programs can use the value attribute to determine which submit button has been pressed. Here’s an example of a form with two submit buttons: submit-form.html Double Submit Buttons

Additionally, IE offers a number of browser-specific attributes designed to improve usability, such as notab, tabindex, and accesskey. These attributes and the cross- browser JavaScript alternatives are discussed in Project 1. A submit button can also accept style attributes, such as class and style, and the standard event attributes such as onClick, onMouseOver, and onMouseOut. Caution Do not confuse the onClick event of the submit button with the onSubmit event of the FORM tag. With some browsers, clicking a submit button with an onClick event will post the form regardless of the results of the activated JavaScript function. For this reason you should always use the onSubmit attribute to validate form data on the client.

The Reset Button The reset button is another common control element. As the name implies, the reset button is the easiest way to reset (clear) a form. Clicking the reset button also fires the onReset event for the form. The onReset event removes all data entered by the user and returns the form to the default state. The basic structure of this tag is

The reset button uses the same attribute set as the submit button. Other Buttons Not all buttons need to submit or reset a form. As discussed in Chapter 1, using client- side scripting also allows you to perform a large number of actions within the browser. While you can apply JavaScript functions to all form elements, buttons are the most intuitive for the end user. The general button tag can be written like this: The button element uses the same attribute set as do the submit and reset buttons. However, the button element is nonfunctional without an onClick or other event attribute. The most common use of a form button is to activate JavaScript functions. Here’s an example of a possible use of a button element: date-form.html Date (General) Button

Date:
Form Images The standard types of buttons don’t give much flexibility in appearance. CSS and JavaScript can change the color, size, and position of form buttons, but that’s all. Sometimes—for aesthetic appeal or usability—a graphic button is preferable. This is where the input type image can be used to replace the submit button. As with the submit button, clicking on a form image will send the data to the processing page or program specified in the action attribute of the FORM tag. Unlike the submit button, the form image will also send x and y coordinates of the mouse position. The form image tag can be written like this:

The form image tag uses all the attributes of the submit and reset buttons, and has a few additional ones: § src. This attribute specifies the location of the image file. The URL can be relative (../search.gif) or absolute (http://www.premierpressbooks.com/ tech/images/go.gif). § alt. This attribute works just like the alt attribute for the IMG tag. Those users unable or unwilling to view images in their browser can still read the alt tag value. § align. Identical to the align attribute of the IMG tag, this attribute specifies the alignment of the form image. § width. Sets the width of the form image. § height. Sets the height of the form image. § hspace. Sets the horizontal spacing, in pixels, of the form image. § vspace. Sets the vertical spacing, in pixels, of the form image. § border. Sets the border width, in pixels, of the form image. § usemap. This attribute specifies the URL and name of a client side image map to use with the form image.

By reading the x and y coordinates submitted with the image element, you are able to determine what part of the form image the user clicked on. Here’s an example of this process in action: image-form.html Choose a Square Image Button

Choose a square:

While this page uses client-side JavaScript, the same principle can be used with server- side scripts to generate CGI-free server-side image maps.

Text Input

The most basic function of online forms, or any form for that matter, is to gather user input. Text is the most versatile of all user input types. Text input elements can accept large amounts of information. This information can be put to any number of uses, including search terms, user names, addresses, passwords, e-mail text, and more. Single-Line Text Fields

By far, the most used text input element is the simple text field. This text field produces a single-line input box. Because it doesn’t have scroll bars or accept carriage returns, the single-line text field should be used only if you anticipate user input of 100 characters or fewer. This is just a general guideline, of course; you can have any character limit (or none at all) if you wish. The text field tag can be written like this:

Here’s a breakdown of the attributes available for text fields: § name. Use this attribute to give the text field a unique identifier. This is the first part of the name-value pair that is posted to the server when the form is submitted. § id. This attribute can be used by CSS to reference this element. § title. This text can be used for context-sensitive help, tool tips, or as accessibility instructions for auditory browsers. § size. This attribute determines the width, in text characters, of the text field. Because the width (like the height) is affected by font size and type, different browsers and users may see different-shaped text fields even when the size is expressly set. § value. This attribute represents the text inside the text field. This information is attached to the name attribute and sent to the server when the form is posted. § maxlength. This number is the maximum length in characters that the text field will allow. § disabled. Adding this attribute to the INPUT tag prevents the user from changing or submitting the text (value) of the text field. This attribute only functions in IE 4+ and Netscape 6. Additionally, IE offers a number of browser-specific attributes designed to improve usability, such as notab and tabindex. These attributes and the cross-browser JavaScript alternatives are discussed in Project 1. A text field can also accept style attributes such as class and style, and the standard event attributes such as onClick, onMouseOver, and onMouseOut. Additionally, three other event attributes are available: onFocus, onBlur, and onChange. onFocus is fired when the insertion point is moved inside the text element. onBlur is fired when the insertion point is moved out of the text element. onChange fires when the user changes the text (value) of the text field. One possible use of these attributes is to simulate the effect of the disabled attribute. text-form.html Disabled Text Form



Note While the script in text-form.html can prevent users from changing the value of the text field, its effect is different from that of the disabled attribute; with the script, the value of the text field is still posted to the server when the form is submitted.

Password Fields

Sometimes security or privacy concerns require that text entered into a form not be displayed on the screen. This is where the password field can be most useful. The password field works exactly like the text field except that the text (value) entered into the field is not displayed on the page. Instead, asterisks or bullets appear in the field. A password field can be written like this: It is important to understand that this information is not encrypted or otherwise secured in any way. Any data entered into a password field is submitted with the rest of the form as standard text and is therefore vulnerable to security and privacy concerns. To prevent this, be sure to place any form that includes elements that should be secured on a Secure Sockets Layer (SSL) Web page, as described in Chapter 2.

SSL Secure Sockets Layer (SSL) is an Internet protocol developed for the exchange of secure information over the Web. SSL works through a combination of private and public keys that encrypt and decrypt data. For more information, visit http://home. netscape.com/security/techbriefs/ssl.html.

Multi-Line Text Areas One line of text isn’t always enough. When your form requires multiple lines of user input, the TEXTAREA tag is the way to go. This element has many of the same features as the text field, yet has a number of distressing limitations. For instance, the TEXTAREA tag has nothing like a maxlength attribute, so there’s no built-in way to limit user input the way you can with a text field. The TEXTAREA tag can be written like this: The name, id, disabled, and title attributes are the same as with the text and password elements. In addition, the TEXTAREA tag has a few unique attributes: § cols. Specifies the width of the text area. Netscape browsers specify this in characters. IE uses its own algorithm roughly equivalent to 10- pixel units. Because the width is rendered differently, users may see different -sized text areas depending on their browser and platform. § rows. Specifies the height of the text area. All browsers specify this in characters, but Netscape browsers can add extra space. Netscape browsers are also affected by the current font size and face, while IE requires style tags to change the font inside the text box. Because the height is rendered differently, users may see different-sized text areas depending on their browser and platform. § wrap. This attribute determines how the text inside the text area behaves. "Soft", the default value for IE, wraps the text when it reaches the edge of the text box. "Off", the default value for Netscape, places carriage returns only when the user enters them into the text box. The multi-line text area can be frustrating to fit in a complex site design. A number of factors can make the area too big in some browsers and too small in others. The best way around this is to use the cols and rows attributes to assign the best height and width for Netscape browsers, and then use style tags to assign dimensions for IE. You’ll never be able to make the text area look exactly the same in all browsers, but you can get close. Following is an example of a page that uses this technique (check it out in a number of different browsers, as in Figure 3.2): textarea-form.html Text Area Form


Figure 3.2: Textarea-form.html on different browsers

Selectors

Selector form elements allow the form designer to limit the user to a specific number of preset responses. Common uses include forms elements that ask for information on gender, state or province, age, and income level. The three types of selector elements available for HTML forms are radio buttons, check boxes, and select boxes. Radio Buttons

Radio buttons allow the user of an online form to select one and only one option from a list of possibilities. Because a long list of options creates a lot of radio buttons, they are best used when the number of options is low (say, two to six). A radio button can be written like this:

Radio buttons have the following attributes: § name. Use this attribute to give a group of radio buttons an identifier. Each radio button in the group should have the same name. When the user selects one button the others are deselected. This is the first part of the name-value pair that is posted to the server when the form is submitted. § id. This attribute can be used by CSS to reference each individual radio button. § title. This text can be used for context-sensitive help, tool tips, or as accessibility instructions for auditory browsers. § value. This information is attached to the name attribute and sent to the server when the form is posted. Unlike other form elements, selectors do not make the value attribute visible to the form user. § checked. The checked attribute designates the pre-selected element. Additionally, IE offers a number of browser-specific attributes designed to improve usability, such as notab, tabindex, and accesskey. These attributes and the cross- browser JavaScript alternatives are discussed in Project 1. A radio button can also accept style attributes such as class and style and the standard event attributes such as onClick, onMouseOver, and onMouseOut. Additionally, three additional event attributes are available: onFocus, onBlur, and onChange. onFocus is fired when the user selects any element in the element. onBlur is fired when the insertion point is moved out of the element. onChange fires when a new element is selected.

Radio buttons interact strangely with some browsers on pages with background colors. In older Netscape browsers a white box appears around the radio button. To get around this, assign a matching background color using the style tag. button-form.html Radio Button Form







Check Boxes Check boxes allow the user to give a true or false response to any query. Unlike radio buttons, check boxes can be deselected, allowing the user to enter a new preference. Also, by adding multiple check boxes, the form designer allows the user to select multiple options. Check boxes are most commonly used on Web forms to opt in for newsletters, select optional extras for products, or modify display preferences. Check boxes are written like this: The id, value, title, checked, style, and event attributes work exactly the same with check boxes and they do with radio buttons. The only difference is with the name attribute: § name. Use this attribute to give the element a unique identifier. This is the first part of the name-value pair that is posted to the server when the form is submitted. By combining the onClick event with some JavaScript you can simulate the effect of the onChange event. checkbox-form.html Radio Button Form What is your annual salary?
under $20,000
$20,000 - $50,000
$50,000 - $100,000
Other
Select Boxes

Select boxes are the most versatile of the form selector elements. With select boxes the user can select a single item or multiple items. Select boxes can save space on the page by holding the entire list of options inside a drop-down list, and they can give a clearer picture of the selections available by displaying multiple options at once. Select boxes are often used for site navigation, to set search options, or to designate a product option. A select box can be written like this: The id, name, value, title, style, and event attributes work exactly the same with select boxes as they do with check boxes. The select box, however, has a number of additional attributes: § size. This attribute specifies the number of options that are visible at one time. If no size is given or if the size is set to 1, all the options will be hidden in a drop-down list. The size should always be more than 1 if multiple selections are allowed. § multiple. By designating a selected box with the multiple attribute you allow the user to select more than one option by holding down the Ctrl (Command on the Mac) key while clicking on additional selections. Holding down the Shift key allows the user to select multiple consecutive options. § selected. This attribute is assigned to the option that should be selected by default when the page loads. IE 4+ allows you to specify the width and line height of the select box using the style tag. Netscape browsers, however, set the width and line height from the appropriate font tag and the size of the largest option. To work around this, you can create an unselectable option at the end of the select box that is the width you desire. This can be accomplished using the onChange attribute of the SELECT tag and a little JavaScript. select-form.html Select Box Form

Special Form Elements

There are two additional types of form elements that do not fit in any of the other categories: hidden and fileupload. The hidden element holds information that the end user should not see or does not need. The information stored in the hidden element is sent to the server with the rest of the form when it is submitted. The fileupload element allows the user to upload files to the server through a Web form interface. Hidden Elements

The hidden form element works nearly the same way as does the single-line text field. The only difference is that the user cannot see or directly modify the value of the hidden element. The most common uses of the hidden element include passing information to the server about product identification numbers, cookie contents, or environmental variables such as the referring page or browser type. A hidden element can be written like this:

Here’s a description of the attributes available for the hidden element: § name. Use this attribute to give the hidden element a unique identifier. This is the first part of the name-value pair that is posted to the server when the form is submitted. § id. This attribute can be used by CSS to reference this element. § value. This attribute represents the information inside the hidden element. This information is attached to the name attribute and sent to the server when the form is posted. Because the hidden element is not visible to the user you don’t have to assign style attributes, and the hidden element has no event attributes. You might expect the onChange attribute to work, but to date no browser supports the onChange event for the hidden element. However, you can modify the value of the hidden element using JavaScript just as you would with a text box. hidden-form.html Hidden Element Form

Fileupload Elements Some server-browser combinations allow the user to upload a file to the server through a Web form. This is accomplished using the fileupload element. Common uses for the fileupload element include submitting photos, lesson assignments, or multimedia files. The fileupload element can only be used with forms that are assigned the “post” method and the "multipart/form-data" enctype. The fileupload element can be written as:

This element has a number of attributes: § name. Use this attribute to give the fileupload element a unique identifier. This is the first part of the name-value pair that is posted to the server when the form is submitted. § id. This attribute can be used by CSS to reference this element. § title. This text can be used for context-sensitive help, tool tips, or as accessibility instructions for auditory browsers. § size. This attribute determines the width, in text characters, of the text box included with the fileupload element. Because the width and height are affected by font size and type, different browsers and users may see differently-shaped text fields even when the size is expressly set. Additionally, IE offers a number of browser-specific attributes designed to improve usability, such as notab, tabindex, and accesskey. These attributes and the cross- browser JavaScript alternatives are discussed in Project 1. A fileupload element can also accept style attributes such as class and style, and the standard event attributes such as onClick, onMouseOver, and onMouseOut. Additionally, three other event attributes are available: onFocus, onBlur, and onChange. onFocus is fired when the insertion point is moved inside the fileupload element. onBlur is fired when the insertion point is moved out of the fileupload element. onChange fires when the user changes the text (value) of the fileupload field. The value attribute of the fileupload element contains the path to the file on the client system. For security reasons the value attribute is read-only. This prevents Web sites from uploading users’ files without their knowledge or consent. Only the user can change the value of the fileupload element, by clicking the associated button and selecting the file. The following file demonstrates the read-only value property of the fileupload element. file-form.html File Element Form


Note Two additional form elements are available. has been deprecated in favor of the more versatile . , a more customizable version of the control elements described in this chapter, is new to the HTML specification and so far is available in only a few browsers. For these reasons, and because most of the effects can be duplicated using other HTML tags, using these form elements is not recommended at this time.

Form Layout As you learned in Chapter 2, proper layout of any interface can greatly improve its usability. The following section describes a number of the layout methods available to the Web form designer. Each of these methods includes an example of the layout using a form template. This template is a moderately sized registration form for a Linux information Web site. By comparing the resulting pages you can get an idea of the appearance of each layout type. In the final analysis, though, the best solution for your Web forms may be a combination of two or more of these techniques.

Unformatted Forms

Unformatted forms use only paragraph tags, horizontal rules, and line breaks to separate form elements. Only the simplest forms should be left unformatted. Besides being aesthetically less pleasing, unformatted forms tend to be long and disorganized. Form elements can only be grouped using vertical spacing and font style. unformatted-layout.html Unformatted Layout

Register

Name:

E-Mail:

Birthday: 19

Gender:  Male     Female

Choose a Username and Password:

Username:

Password:

Confirm Password:

Optional Info:

What Linux distribution do you use?

How much time do you spend on the Internet each week?

What is your Internet connection?

What is your favorite Linux Web site?

  I would like to receive your newsletter.

  Check here to receive special offers from our partners.

Advantages of unformatted forms: § Appearance is very similar regardless of browser. § Quick to produce, easy to maintain. § Supported by every form-enabled browser.

Disadvantages of unformatted forms: § Long—the user will most likely have to scroll to see the entire form. § Unorganized, so logical relationships between form elements are often not apparent. § Aesthetically displeasing, as sharp alignment of elements is not possible.

Recommendation: Use only on short forms.

Preformatted Forms Using the PRE tag you can format a form on the page. This style is best used for short forms that do not have to match the surrounding page’s style. The advantage to this method is its simplicity, but that can also be its drawback. Preformatted forms are very limited in structure and style, as only monospace fonts can be used. preformatted-layout.html Preformatted Layout

Register

Name:  E-Mail:  Birthday: 19 Gender:  Male  Female 

Choose a Username and Password:

Username: Password: Confirm Password:

Optional Info:

What Linux distribution do you use? How much time do you spend on the Internet each week? What is your Internet connection? What is your favorite Linux Web site?

I would like to receive your newsletter. Check here to receive special offers from our partners.

Advantages of preformatted forms: § Appearance is very similar regardless of browser. § Quick to produce, easy to maintain. § Supported by every form-enabled browser. § Improved aesthetics, as form elements can be aligned.

Disadvantages of preformatted forms: § Long—the user will most likely have to scroll to see the entire form. § Unorganized, so logical relationships between form elements are often not apparent. § Limited to monospace font.

Recommendation: Use only on short-to-medium forms where style is not important. Table Formatted Forms

The most common way to format a Web form is to use a table structure. Using HTML table elements you can create multicolumn forms. This not only helps to group form elements, but also saves screen space. Additionally, table cells can be colored to further separate form elements. The disadvantage is that table formatting can be difficult for the beginner or the hand coder. Experienced HTML developers and those using WYSIWYG HTML editors shouldn’t have any problem creating complex table structures. table-layout.html Table Layout

Register

Name:
E-Mail:
Birthday: 19
Gender:  Male     Female

Choose a Username and Password:


Username:
Password:
Confirm Password:

  I would like to receive your newsletter.
   Check here to receive special offers from our partners.

Optional Info:
What Linux distribution do you use?
How much time do you spend on the Internet each week?
What is your Internet connection?
What is your favorite Linux Web site?

Advantages of table formatted forms: § Appearance can be more directly controlled. § Takes advantage of screen space, making multicolumn layout possible. § Organized, so logical relationships are easy to convey using cell colors. § Supported by the most common form-enabled browsers.

Disadvantages of table formatted forms: § Can be difficult to code. § Appearance may vary slightly from browser to browser. § Some very old browsers do not support tables.

Recommendation: Use on medium-to-long forms if browser compatibility is not an issue.

Layer Formatted Forms

Formatting a Web form using layers can produce many of the same effects you get using tables—including multicolumn display and color use—while taking up less file space and client processor power. You can use JavaScript or CSS to create layers and then position them precisely on the page. The downside to using layers is that browser support is problematic. Older browsers do not support layers at all, and some newer browsers only support a few layer attributes. Even browsers that support similar attributes can implement them in different ways. It is very important when using layers to thoroughly test the final page on all the browsers that your visitors may be using. Because of this, layer formatted forms are best used on intranets or extranets where the end user’s browser and platform can be controlled. layer-layout.html Layer Layout

Register

Name:
E-Mail:
Birthday: 19
Gender:  Male     Female

Choose a Username and Password:

Username:
Password:
Confirm Password:

  I would like to receive your newsletter.

  Check here to receive special offers from our partners.

Optional Info:

What Linux distribution do you use?


How much time do you spend on the Internet each week?


What is your Internet connection?


What is your favorite Linux Web site?

Advantages of layer formatted forms: § Appearance can be directly controlled. § Takes advantage of screen space, as multicolumn layout is possible. § Organized; logical relationships are easy to convey using layer colors. § Allows for DHTML animation. § Small file size; less client processing required.

Disadvantages of layer formatted forms: § Can be extremely difficult to code properly. § Appearance varies from browser to browser. § Extensive layout testing required, as layers often require workarounds. § Many browsers do not support layers; layers tend to degrade very badly.:

Recommendation: Use only on forms where you know the browser type of the end user and plan to use DHTML to animate form elements; otherwise they are rarely worth the effort. Caution The is extraordinarily particular about combining layers with forms. Nested layers will often produce unusual effects or even drop form elements from the page completely. This is another reason I recommend using layer formatted forms only for Web pages that can restrict the end user to a specific browser.

Chapter 4: JavaScript and Web Forms The “dynamic” part of Dynamic Web Forms comes from JavaScript. Sure, you could post the form every time the user presses a button, then produce pages and pages of server- side script that respond with new HTML for each situation, but you still wouldn’t come close to the flexibility, speed, and power of client-side JavaScript. The key to increasing the functionality and usability of your Web forms is to respond to user input quickly and without interrupting the workflow. This can only be done on the client side and the only real choice for client-side scripting is JavaScript.

A JavaScript Short Course

Teaching all of JavaScript would take far more than a single chapter. The intent here is not to explain the entire scripting language but to leverage your existing programming skills toward learning JavaScript. All the basics are covered here, including control structures, array handling, and string manipulation. You should come away with enough knowledge of JavaScript to be able to understand the more complex scripting in the projects. Before you learn JavaScript, you should understand a little about how it works. All client- side scripts are either included within the HTML sent to the browser or in an exterior file that is referenced from the page. Modern browsers have a utility called a scripting engine that recognizes this script and parses the commands at run time.

The JavaScript scripting engine is included with all the latest releases of the most popular browsers—Opera, iCab, Netscape, Internet Explorer, WebTV, and Hot Java, among others. Each scripting engine interprets only a defined set of commands and no others. This makes JavaScript great for those concerned about security—it is impossible to cause a JavaScript code run inside a to act as a virus or other destructive entity. It also makes JavaScript a headache for Web developers. You’re limited in the functionality of your script based on the complexity of the users’ JavaScript scripting engines.

Another hair-puller is that not all JavaScript scripting engines are created equal. While some engines attempt to match up to the standards defined by the World Wide Web Consortium, most fall woefully short. Scripting engines are littered with proprietary commands, quirky implementation, and just plain gaps. As a JavaScript developer you must try to work around these differences. My advice for how to do this: practice, test, practice, test, test again, code, test, test again, test yet again, then launch, fix any bugs; repeat as necessary.

JavaScript Basics

Client -side scripting presents a number of new challenges for a developer experienced in server-side scripts. Most importantly, as a JavaScript programmer, you can’t control (or even know with any certainty) the operating environment of the client. Your code will have to take this into account and you will need to plan for any number of contingencies. This also means the testing phase is far more in-depth than with server-side scripting. JavaScript programmers should test their code on multiple browsers and operating systems.

Developers familiar with other scripting languages such as PHP or VBScript will find many similarities with JavaScript. The basics for any scripting language are all present in JavaScript: variables, operators, arrays, control structures, and functions. For the experienced scripter it is just a matter of learning the new format and nomenclature. If you are unfamiliar with scripting languages or desire more in-depth coverage, see Appendix C for a listing of recommended JavaScript references. Adding JavaScript to a Web Page You have three ways to insert JavaScript into a Web page. The most direct (and common) way is to use the SCRIPT tag. This HTML tag either brackets the script on the page or references a separate JavaScript file. Although many client-side programmers prefer to place the code on the same page as the HTML for quick access and simplicity, referencing external files is often your best option. Chapter 6 will discuss this option in depth and explain my reasoning. SCRIPT tags can be located anywhere in the document, but the typical placement is in the head or between the HEAD and BODY tags. JavaScript that needs to write to the document, however, should be inside the BODY tags. The basic format of the SCRIPT tag that references an external document is The format of a SCRIPT tag that holds JavaScript on the page is The SCRIPT tag has three attributes: § language. This tells the browser which client -side scripting language is being used. Any given browser may have more than one scripting engine—Internet Explorer, for example, includes scripting engines for VBScript and JScript. Browsers without a JavaScript scripting engine will ignore the SCRIPT tag. § src. Only used for SCRIPT tags that reference a separate file, the src attribute gives the name and location (relative or absolute) of the external JavaScript file. This file is usually given the .jss or .js extension, but it isn’t required. § type. The type attribute sets the MIME type of the JavaScript. It is mandatory only when referencing an external file, but can also be used as a supplement to the language attribute. The second way of inserting JavaScript is through event attributes of HTML tags. You might remember seeing this inline technique in the last chapter. The most common use of inline scripting is to reference a JavaScript function located inside a SCRIPT tag, but entire multifunction scripts can be included. I recommend, however, that you keep your inline scripts small. When you need to make a change to your code, long inline scripts can be difficult to find and confusing to edit. Inline scripts are written like this:

Event attributes associated with forms include the following: § onBlur. This event activates when the focus moves from the attached object to another. For form elements this means that the insertion point has moved to another element. § onChange. Fires when the content of an object is changed and the focus is moved from the attached object. Changing form elements can mean typing new text in a text input element, selecting a new option in a selector, or changing the referred file in a fileupload element. § onClick. This event occurs when the mouse button is pressed while the mouse pointer is over the attached object. The most common use of this event with Web forms is to assign a JavaScript function to a control element. § onDblClick. This event activates when the user quickly presses the mouse button twice while the mouse pointer is over the attached object. The speed of the double-click depends on the operating system and browser. § onFocus. Fires when the insertion point is moved to the attached object. § onMouseOver. Activates when the mouse pointer is moved over the attached object. This event can be assigned to any of the visible form elements. § onMouseOut. This event fires when the mouse pointer is moved away from the attached object. § onReset. Only assigned to the FORM tag, this event activates when the Web form receives a request to reset the form. This event occurs before the information is returned to the starting values. § onSubmit. Another event only assigned to the FORM tag, this event fires when the form receives the submit command. The onSubmit event runs before the form information is sent to the server. The final way to include JavaScript on a Web page is to include the script in the href attribute of an anchor tag. The script runs when the user clicks on the text associated with the link. You might ask why this would ever be used—after all, you can assign an onClick event to an anchor tag. The difference is that the onClick event does not prevent the browser from following the link included in the href attribute. This can cause the page to change or at the very least refresh, something not always desired with online forms. Including the JavaScript in the href attribute prevents this.

Bookmarklets Bookmarklets let you bookmark the JavaScript associated with an anchor tag just like with any other link. Common uses for bookmarklets include resizing your browser, navigating to pages in the browser’s history, or changing the appearance of the page. For more information about bookmarklets visit http://www.book marklets.com/.

You can add JavaScript to an anchor tag like this: Click me Commenting and Escaping JavaScript Commenting JavaScript involves including text that is not processed by the scripting engine; the sole purpose is to leave a message to those vi ewing your script. Chapter 2 covered the importance of commenting code: proper commenting produces a far cleaner, smoother, and simpler development environment. Of course, the degree of commenting is left up to you and your development team.

JavaScript provides a number of ways to comment your code. To comment a single line of code use double slashes (//) at the beginning of the comment. This will cause the browser to ignore everything that follows until it reaches a carriage return. This is the most common type of JavaScript commenting and can be written as: var strVariable = 'Welcome!'; // sets the variable to the welcome message document.write(strVariable); // displays the welcome message An alternative to the double slashes is the opening bracket of an HTML comment Find It Online In the Chapter 4 folder of the book’s Web site is a file called badscript.html that includes a SCRIPT tag without the comment tags. Figure 4.1 shows how the page looks in a non-JavaScript browser. In this case the browser is NSCA Mosaic.

Figure 4.1: Mosaic displays JavaScript comments as though they were uncommented. Multiline commenting is accomplished in JavaScript by bracketing the comments with /* and */ characters. The scripting engine ignores all code inside these notations. Here’s an example: These comment tags are normally quite useful, but sometimes they get in the way. For example, what if you wanted JavaScript to write out a complete URL, such as http://www.premierpressbooks.com/? Normally everything after the // would be dropped off and the result would be http:. This is where the escape character comes in to play. Adding a backslash (\) tells the JavaScript engine not to consider the next character as part of a script command. This also comes in handy when you need to pass through a single quote character. Here’s an example of the escape character in use: document.write('Premier Press\'s Website is located at http:/\/www.premierpressbooks. com/'); Variables

Variables are like placeholders for information. You can assign text, objects, numbers, Boolean values, or functions to variables and then reference them using the variable name. What makes variables so useful is that you don’t have to know the value of a variable in advance to use it in a script. For instance, you could create a Web page that asks for the visitor’s name. By assigning the name entered to a variable you can then display it later, store it in a cookie, automatically enter it into a Web form, or put it to any number of other uses.

Declaring a variable is as simple as this: var VariableName;

JavaScript includes many variable types, the more common of which are described below. JavaScript automatically switches between many of these data types as the value of the variable changes. For instance, if a variable began as the string “Hello” and changed to “7”, the variable type would change to integer. For the variable types where this conversion is not automatic I’ve included an example of how the variable type might be declared. Chapter 2 describes the importance of using a standardized naming convention. Here are my recommendations (in italics) for naming each of these JavaScript variable types: § String. The most common variable type, string variables contain text. There is no stipulation on how much or how little text can be included in a string variable. Common string handling functions are included a little later in this chapter. “Welcome!” is an example of a string variable. strVariableName § Integer. An integer variable contains any whole number—positive, negative, or zero. A whole number has no decimal point. For example, -51, 13, and 0 are all integers. intVariableName § Hexadecimal. Hexadecimal variables are integers formatted in a base-16 format. You can declare a hexadecimal variable by adding 0X to the value like this: var hexVariableName = 0XA1CC; Examples include FF, C0, and 1A. hexVariableName § Floating-Point. These variables contain any number with a value to the right of the decimal point. Floating-point numbers require more processing time than integers, so whenever possible integers should be used instead. Examples include 3.1415, -6.002, and 0.1. floatVariableName § Object. Represents a JavaScript object such as a form element, image, or property. You declare an object variable like this: var objVariableName = new Object; For example, document.title, image.src, and form.action are all object variables. objVariableName § Date. This is a special type of object variable that holds a date value. JavaScript has a number of functions for controlling how a date variable is displayed. This is how you declare a date variable: var dateVariableName = new Date();

dateVariableName § Array. Another object variable, the array variable holds an ordered collection of data. Arrays are covered in more detail a little later in this chapter, but for now it’s enough to know that to declare an array you use this format: var arrVariableName = new Array(); Examples of an array include: [2,3,1], [Dan,Bill,Mary], and [document.links,document.images,document.forms]. arrVariableName § Function. Represents a JavaScript function that is defined elsewhere in the script or is predefined by JavaScript. Functions are covered in more depth later in this chapter. Here is how function variables are declared: var funVariableName = function(); alert(), blur(), and imageSwap() are all functions. funVariableName § Boolean. A Boolean value has only two possible values: true or false. Boolean values come in handy for conditional tests (if this variable is true do this function). boolVariableName Note It is possible that you may not know in advance what type a variable will be. This often occurs with Web forms, as user input is rarely predictable. In this case I recommend the following notation for unknown variable types: varVariableName. Operators

You need to be aware of four types of JavaScript operators when you’re dealing with the Web forms presented in this book: comparison, mathematical, assignment, and Boolean. Operators take two or more variables and perform an action, compare values, or assign a property. Comparison operators examine multiple variables and produce a result in a Boolean (true or false) value. Table 4.1 lists the comparison operators. Table 4.1: Comparison Operators Operator Name Example Result

== Equals 5 == 5 true

!= Does 4 != 2 true not equal

> Greater 3 > 1 true than

< Less 7 < 9 true than

>= Greater 5 >= 2 true than or equal to

<= Less 7 <= 7 true than or equal to Mathematical operators work about as you would expect. They take two or more variables and perform a simple mathematical calculation with them. The result is always a number variable. Table 4.2 lists the JavaScript mathematical operators. Table 4.2: Mathematical Operators Operator Name Example Result

+ Add 1 + 1 2

- Subtract 2 - 3 -1

u Multiply 4 * 2 8

/ Divide 9 / 3 3

++ Add 1 6++ 7

–– Subtract 2–– 1 1 Assignment operators perform a mathematical calculation on two variables and assign the result to the first variable. These operators can also be used instead of mathematical operators to shorten your JavaScript code. Table 4.3 lists the assignment operators. Table 4.3: Assignment Operators Operator Name Example Result

= Equals a = b a = b

+= Add the a += b a = a + b values Table 4.3: Assignment Operators Operator Name Example Result

-= Subtract a -= b a = a - b the second value from the first

*= Multiply a *= b a = a

the * b values

/= Divide a /= b a = a / b the second value by the first Boolean operators compare two Boolean variables and result in a Boolean value. Table 4.4 describes Boolean operators. Table 4.4: Boolean Opera tors Operator Name If Results a = tr u e a n d b = fa ls e

&& And a true && a a false && b b false && b b false && a

|| Or a true || a a true || b b false || b Table 4.4: Boolean Opera tors Operator Name If Results a = tr u e a n d b = fa ls e b true || a

! Not !a false !b true

Arrays, Strings, and Web Forms

Now that you know how to write JavaScript, you need to know a bit more about the elements that make up any JavaScript code. The two most common JavaScript elements are arrays and scripts, and these two objects support the majority of Web form manipulation. Scripts can hold the content entered into text input fields, text areas, password fields, and other form elements. Arrays are used when dealing with radio buttons, select boxes, and form elements as a group. Note JavaScript allows you to define the size of an array at the time of creation. Placing a number inside the parentheses defines a variable of a specific length. For instance, new Array(5) would create an array with 5 undefined entries. Presizing an array produces little benefit, however. The length of a presized array is not fixed; array entries can be entered in any order and may expand the array beyond the initial size without additional programming.

Array Handling

As you learned earlier, arrays are a special kind of variable object. An array is a group of variables, whether string, numerical, Boolean, or object type, arranged in a defined order. A JavaScript command can reference a specific entry in an array by number rather than by full name. This time- and code-saving technique should be familiar to most programmers.

Defining a JavaScript array is simple: var arrVariableName = new Array();

Array entries are numbered starting with 0. This can be a little confusing, as the last entry in an eight-entry array would be 7. When defining or referencing array entries in JavaScript use brackets: function funCreateArray() { var arrMyArray = new Array(); arrMyArray[0] = 5; arrMyArray[1] = 'seven'; arrMyArray[5] = true; arrMyArray[2] = new Date();

document.write(arrMyArray[5]); // results in value "true" document.write(arrMyArray[4]); // results as undefined document.write(arrMyArray.length); // results in value "6" }

It is also possible to populate an array at the same time it is defined: var arrMyArray = new Array(1, "two", true); document.write(arrMyArray[2]); // results in value "two" document.write(arrMyArray.length); // results in value "3"

While JavaScript arrays are one-dimensional, the freedom to populate an array entry with another array allows you to simulate the workings of a multidimensional array. This can be done using a double set of brackets: function funCreateMultiArray() { var arrMyArray = new Array(); arrMyArray[0] = new Array(); arrMyArray[0][0] = 1; arrMyArray[0][1] = 2; arrMyArray[0][2] = 3; arrMyArray[1] = new Array(); arrMyArray[1][0] = 4; arrMyArray[1][1] = 5; arrMyArray[1][2] = 6; arrMyArray[2] = new Array(); arrMyArray[2][0] = 7; arrMyArray[2][1] = 8; arrMyArray[2][2] = 9;

document.write(arrMyArray[1][1]); // results in value "5" document.write(arrMyArray[0][6]); // results as undefined document.write(arrMyArray[2].length); // results in value "3" }

Following are the most common array properties and methods: § length. This read-only property results in the number of entries in the array. Remember, entries can be of any value, including undefined. Example: var arrMyArray = new Array(); arrMyArray[0] = 5; arrMyArray[1] = 'seven'; document.write(arrMyArray.length); // results in value "2" § concat(). This method adds the values of one array onto another. The second array’s entries are simply added to the end of the first. This nondestructive process produces a third array. The entries from the original arrays are not changed, but neither are they linked. The new array is independent of its parents. Example: var arrMyFirstArray = new Array("apple", "banana", "carrot"); var arrMySecondArray = new Array("red", "yellow", "orange"); var arrMyBigArray = arrMyFirstArray.concat(arrMySecondArray); document.write(arrMyBigArray); // results in values "apple", "banana", "carrot", "red", "yellow", "orange" § join(). The join() method produces a string from the entries in an array and separates the values by a defined delimiter. This is useful when assigning an entire array to a form element value and is preferable to the old (and finicky) method toString(). Example: var arrMyArray = new Array("car", "boat", "plane"); document.formThisForm.formTextBox.value = arrMyArray.join("~"); // results in car~boat~plane § push(). This stack method adds an entry to the end of an array. Example: var arrMyArray = new Array("desk", "chair"); arrMyArray.push("door"); document.write(arrMyArray); // results in values "desk", "chair", "door" § pop(). Another stack method, pop() returns the value of the last entry in an array and removes that entry from the array stack. Example: var arrMyArray = new Array("mine", "yours", "ours"); var strLastEntry = arrMyArray.pop(); document.write(strLastEntry); // results in value "ours" document.write(arrMyArray); // results in values "mine", "yours" § unshift(). This method places an entry at the beginning of an array stack. Any entries already in the array are moved up. Example: var arrMyArray = new Array("trot", "prance", "gallop"); arrMyArray.unshift("canter"); document.write(arrMyArray); // results in values "canter", "trot", "prance", "gallop" § shift(). This stack method works just like the pop method except that the first entry is returned and removed from the array. Any remaining array entries are collapsed with index 1 becoming index 0 and so on. Example: var arrMyArray = new Array("mouse", "cat", "dog"); var strFirstEntry = arrMyArray.shift(); document.write(strFirstEntry); // results in value "mouse" document.write(arrMyArray); // results in values "cat", "dog" § reverse(). The reverse() method reorders an array back to front. It also results in a list of entries in this reverse order. Example: var arrMyArray = new Array("listen", "think", "speak"); var strWrongWay = arrMyArray.reverse(); document.write(strWrongWay); // results in values "speak", "think", "listen" document.write(arrMyArray); // results in values "speak", "think", "listen" § slice(). This method returns an array of contiguous values from the entries in the original array. The first parameter defines the starting point; the second defines the noninclusive ending point. The original array is not affected. Example: var arrMyBigArray = new Array("gold", "silver", "bronze", "tin"); var arrMySmallArray = arrMyBigArray .slice(1,3); document.write(arrMySmallArray); // results in values "silver", "bronze" document.write(arrMyBigArray); // results in values "gold", "silver", "bronze", "tin" § splice(). The splice() method also results in an array of contiguous values from the entries in the original array. There are a few significant differences between the slice and splice methods, however. The second parameter of the splice() method defines the total number of entries to affect, not the stopping point. The splice() method also removes the returned entries from the array. The remaining entries are collapsed with index 10 becoming index 9 and so on. Any additional parameters are used to replace the removed entries. Example: var arrMyBigArray = new Array("dollar", "pound", "peso", "yen", "mark"); var arrMySmallArray = arrMyBigArray .splice(1,3,"franc"); document.write(arrMySmallArray); // results in values "pound", "peso", "yen" document.write(arrMyBigArray); // results in values "dollar", "franc", "mark" String Handling

Strings are variables that hold a defined text value. This text value can be of any length and composition as long as it contains only text and special representative text characters. Strings are the most common type of variable used in form-based JavaScript.

Defining a string variable is a simple process: var strThisVariable = "This is a string." It is also possible to add special inline characters, such as a carriage return or tab, to a string. These are added using the backslash notation. Table 4.5 lists the more common special characters and their corresponding string notations. Table 4.5: Special String Characters

Notation Description

\t Tab

\n New line

\r Carriage return

\" Double quotation mark

\’ Apostrophe

\\ Backslash JavaScript strings have a number of properties and methods. These are the most common types: § length. This read-only property returns a count of the number of characters in a string; spaces, punctuation, and special characters each count as one character. Example: var strMyString = "This is a string"; document.write(strMyString.length); // results in value "16" § charAt(). This method returns the value of the character located at the defined position. As with arrays, JavaScript strings begin at position 0. Example: var strMyString = "What\'s Up?"; document.write(strMyString.charAt(7)); // results in value "U" § indexOf(). This method is used to determine where, if at all, the first occurrence of a search string can be found in a parent string. The first parameter is the search string. The optional second parameter determines the start position for the search. If the search string is not found the result is -1. Example: var strMyString = "How many Web designers does it take to screw in a lightbulb?"; document.write(strMyString.indexOf("any",4)); // results in value "5" (One to screw it in and four to screw it up.) § lastIndexOf(). LastIndexOf() works exactly the same as IndexOf(), except that the search runs from right to left. The optional second parameter still determines the start position for the search. Example: var strMyString = "How many programmers does it take to screw in a lightbulb?"; document.write(strMyString.lastIndexOf("How",4)); // results in value "0" (It's a hardware problem.) § slice(). The slice() method returns a string value from the interior of a parent string without modifying the original. The first parameter defines the starting point. The second parameter defines the end point. Using negative numbers, you can define this as an offset from the final character. Without the second parameter, the slice() method returns all the remaining characters. Example: var strMyBigString = "What does a thinker use most?"; var strMySmallString = strMyBigString.slice(14,-12); document.write(strMySmallString); // results in value "ink" § split(). This method produces an array of string entries based on a defined delimiter. The optional second parameter defines the maximum length of the new array. Example: var strMyBigString = "eeny,meeny,miney,moe"; var arrayStringArray = strMyBigString.split(",",3); document.write(arrayStringArray[1]); // results in value "meeny" document.write(arrayStringArray.length); // results in value "3" § substr(). Another way to return an interior string from a parent string, this method has two parameters. The first defines the starting point, the second the length of the substring. Example: var strMyBigString = "What interests programmers most?"; var strMySmallString = strMyBigString.substr(9,4); document.write(strMySmallString); // results in value "rest" § substring(). This methods works much like substr() except that the second parameter is the ending point, not the length. The returned string includes the characters up to but not including the ending point. Example: var strMyBigString = "What do most programmers need to work on?"; var strMySmallString = strMyBigString.substring(16,23); document.write(strMySmallString); // results in value "grammer" (Get it?) § toLowerCase(), toUpperCase(). These methods each result in a string with the characters converted to the corresponding upper or lowercase equivalents. The original string is left unaffected. As JavaScript and many server-side languages are case sensitive, it is often necessary to convert a string to a single case before comparing values. Example: var strMyString = "Connor MacLeod"; var strMyLowerString = strMyString.toLowerCase(); var strMyUpperString = strMyString.toUpperCase(); document.write(strMyLowerString); // results in value "connor macleod"; document.write(strMyUpperString); // results in value "CONNOR MACLEOD"; document.write(strMyString); // results in value "Connor MacLeod"; JavaScript, Web Forms and DOM JavaScript is an object-oriented scripting language that uses a dot notation to reference objects within the browser. The (DOM) defines the hierarchy of JavaScript objects. This hierarchy begins at the navigator level and moves down through such objects as the window, document, and form. Browser manufacturers have implemented the JavaScript DOM differently. For instance, Internet Explorer 4 and above has all, style, and iframe objects while Netscape (up till version 6) has the layer object.

The variation in the DOM for the various browsers is one reason why so many DHTML programmers do their scripting from mental health facilities. The good news is that most of the DOM that pertains to Web forms and form elements has been standardized—and for the rest, I’ll be sure to point out what you need to know.

Here’s how to reference a form element using the dot notation: window.document.forms[0].elements[0].property; This script references the first element of the first form on the page. You may have noticed the similarity between the form and elements designators (forms[0], elements[0]) and the array notation I described earlier. A browser’s JavaScript engine automatically creates a form array and an elements array when a form is encountered on a Web page. As with all arrays the first entry is numbered 0. If you have taken the time to assign name values to your form elements it is also possible to reference these elements by name. For example if the FORM tag included a "name=formForm" and a text box had "name=formTextBox" you could reference the value property of the text box like this: window.document.formForm.formTextBox.value;

In fact most browsers do not require the window declaration, so this would produce the same results: document.formForm.formTextBox.value; Caution You may notice that IE will produce results without the document notation as well. Be careful of this shortcut, though, as the majority of Netscape browsers won’t recognize a reference to a form element without the document declaration.

Radio buttons and select boxes present a few new wrinkles. Again the individual options are referenced from an array. For radio buttons this means simply adding the bracket notation, like this: document.formForm.formRadioGroup[0].value;

The select box, however, requires the addition of the option object: document.formForm.formSelectBox.options[0].value;

Like all JavaScript objects, form elements have defined properties and methods. These differ from element to element. The form object has a number of properties and methods that can be referenced using JavaScript. Here are some of the most common: § action. This property contains the value of the action attribute of the FORM tag. The action property is read/write. § elements[]. This is an array of the form elements within the form. This value is read-only. The elements object is covered in more detail later. § length. This read-only property holds the total number of form elements within a form. § method. This read/write property contains the value ("get" or "post") of the method attribute for the FORM tag. § name. This is an editable property that holds the value of the name attribute for the FORM tag. § target. This property contains the value of the target attribute of the FORM tag. The target property is read/write. § reset(). This method resets the form to the initial values. § submit(). This method posts the form to the file or application assigned to the action attribute as if the user had pressed a submit button. To assign any of the read/write properties simply use the equals (=) operator like so: document.formForm.method = "post";

Form elements also have properties and methods. The following properties are shared by all form elements: § form. This property references the parent form of the element. This is a great help when writing modular scripts. It often prevents having to hard code the form name into the script. § name. This read/write property contains the value of the name attribute assigned the form element. § type. The type property references the type value of a form element. This property is read-only for all elements except button, reset, and submit. § value. The value property contains the second part of the name- value pair that is posted to the server when the form is submitted. This property is read/write, but can be temperamental with button, reset, and submit elements.

Additionally, there are a few element-specific properties: § checked. Applicable to check boxes and radio buttons, the checked property is read-only. This property references whether a specific radio button or check box has been selected. § length. The length property works for radio buttons, select boxes, and option objects. In radio buttons and select boxes this property describes the number of selections possible within the group and is read-only. The length property of the option object, however, is read/write. § options[]. This is an array of the options within a select box. This value is read-only. Properties and methods specific to the option object are discussed a little later. § selectedIndex. This read/write property only applies to select boxes. The selectedIndex property holds the index of the currently selected option. If no option is selected the value is -1.

Text input, password, and textarea elements share three unique methods: § blur(). The blur() method causes the insertion point to move away from the text box. § focus(). This method does the opposite of blur(); the insertion point is moved into the text box. § select(). The select() method highlights the text inside a text box as if the user has selected the entire text with the mouse. This can be used to draw attention to faulty input. The select() method is best used in conjunction with the focus() method.

The option object has all the standard properties, including name and value. Additionally, it has a few of its own unique properties and methods: § selected. The selected property is a read/write Boolean value that indicates whether or not a specific option has been selected. § text. The text property is a read/write reference to the text (what the browser displays) of the option object. § remove(). This IE 4+ (and Netscape 6+) method permanently removes the referenced option from the select box. For example, to remove the second option (remember arrays begin at 0) you could use: document.formForm.formSelectBox.options.remove(1); Netscape 6 requires that the remove() method be assigned to the select element rather than the option object: document.formForm.formSelectBox.remove(1);

To produce the same effect in Netscape 3+ you must assign the option object to null like this (compatible up to IE 5.5): document.formForm.formSelectBox.options[1] = null; § add(). Another IE 4+ method, add() produces a new option for the select box. Before you can add an option using this method, however, you must first create the option using the createElement() function: var objNewOption = document.createElement("OPTION"); document.formForm.formSelectBox.options.add(objNewOption, 0); The first parameter of the add() method is a reference to the created option. The second parameter is optional and defines the index value of the new option in the option array. This example places the new option at the top of the select box. If the second parameter is omitted, the option is added to the end of the array. The add() method works similarly in Netscape 6+ with two exceptions. First, like remove(), the add() method is assigned to the select element itself. Second, the optional second parameter is a reference to the object before which the new option should appear: var objNewOption = document.createElement("OPTION"); document.formForm.formSelectBox.options.add(objNewOption, document.formForm.formSelectBox.options[0]); Netscape 3+ uses an entirely different method (compatible up to IE 5.5). Rather than creating the option using the createElement() function, the new Option declaration is used. The properties of the new option can be passed through as parameters of the new Option declaration. The parameters fall in this order and all are optional: text, value, name, defaultSelected (Boolean), and selected (Boolean). Here is an example of this process: document.formForm.formSelectBox.options[document.formForm. formSelectBox.length] = new Option('Premier Press', 'http:/\/www. premierpressbooks.com/', 0, 0);

JavaScript Control Structures

Without control structures, your code would be pretty boring. All you could do would be to perform a series of commands in a set order. You couldn’t compare values, respond to changes, or create modular functions. Control structures are what put the “programming” in JavaScript programming. They allow you to use the JavaScript engine to perform actions dependent upon unknowns, conditions, or user input. They are also of great benefit for shortening, simplifying, and organizing your code.

JavaScript control structures are broken down into three categories: conditional controls, loops, and functions. Conditional controls produce actions based on the value of an unknown. Loops also take an unknown value, but they repeat a series of actions until a specific condition has been met. Functions are organized sets of code that allow you to reference a series of commands from another part of the script. Conditional Controls

Conditional controls are the heart of any . They allow you to create customized effects based on data not available at the time of scripting. A good example would be a form validation script. The script would need to read the content entered into a form by the user and determine whether it is acceptable to post based on a number of predefined conditions. As a JavaScript programmer you can’t know ahead of time exactly what the user might enter into a Web form, so you have to prepare for a number of possible circumstances. The most common and simple of the conditional controls is the if statement. Common to all programming languages, the if statement basically says: "If a defined condition is met, then do this." JavaScript encloses the condition in parentheses and the commands to be executed if the condition is met inside curly brackets. If statements can be written as: if (condition1) { // do this } JavaScript evaluates the condition and performs the included commands if the result is true. Usually the condition involves one of the comparison operators described earlier. For instance, you might want to check if the value of a variable is greater than 0, so your if statement might look like this: if (intMyNumber > 0) { document.write('It's not negative'); } Caution Don’t confuse the “==” comparison operator with the “=” assignment operator. You might be tempted to think that the following code would call alert() only if it encountered a value of -1: if (intMyNumber = -1) { alert(); } But in fact, that command would actually assign the variable the value “-1”. In addition, the condition will always be true and the alert() function will be called every time. You’ve gotten your script to perform an action dependent on a condition, but now you need to perform a separate action only for those times when the condition is not met. The second part of the if statement is the else clause: else { // do something else } Combining the if statement with the else clause allows you to describe events based on unknowns, such as user input. The following example changes the content of a select box based on the country of origin: if (boolUSA) { funSelectChange('States'); } else { funselectChange('Provinces') } The else if clause allows you to add more than one conditional statement to the complete if command. The else if command looks much like you might expect: else if (condition2) { // do something else } Each else if condition is evaluated in the order it appears in the script. The first condition that is met ends the if statement. For instance, in this example only "Go 3" would appear: var intNumber = 5; if (intNumber > 3) { document.write('Go 3'); } else if (intNumber < 7) { document.write('Back 7'); } else if (intNumber == 5) { document.write('Right on 5'); } else if (!intNumber) { document.write('Not!'); } The number of else if clauses is not limited, but after five or so your script can become cumbersome. If the situation allows, you might consider using the switch statement. The switch statement takes an expression and executes different commands depending on what the expression evaluates. The advantage of the switch statement is that the browser only has to evaluate a single expression regardless of the number of possible outcomes. To use an analogy, the if statement is like walking down a hotel hallway checking your key on each door to find the one door your key will open. With the switch statement you just need to find the door with the same number that’s on the key. JavaScript switch statements are written like this: switch(expression) { case value1: // do something break; case value2: // do something else break; } Like the if statement, the switch statement ends when the first result evaluates to true. In this example only the case "Ca" would evaluate to true: var strState = "California" switch(strState.substring(0,1)) { case "Fl": document.write('Florida: Not just cancer anymore!'); break; case "Oh": document.write('Ohio: Please don't run away!'); break; case "Ca": document.write('California: We'll trade you cheese for power!'); break; case "Il": document.write('Illinois: Sorry about the Cubs!'); break; case "Lo": document.write('Louisiana: Great for family canoe trips!'); break; } The switch statement also has an optional default clause. The JavaScript commands defined in the default clause are only executed if none of the previous cases evaluates as true. The default clause is written as: default: // do something only if nothing else The disadvantage to the switch statement is that you can’t use conditional operators. In other words, you can’t use the switch statement to check if an integer variable is greater than zero. You have to know the exact value of the expression. This limits the switch statement to use mostly for comparing strings and object values. Loops

Another commonality among programming languages is the ability to code or script loops. Loops are commands that are repeated as long as certain conditions are in place. When those conditions change, the commands stop executing. Loops are used commonly as shortcuts in scripting, to compare entry values in arrays, and to confirm user input. The for statement is the most common type of JavaScript loop. The basic for statement takes three parameters: an initial expression, a conditional statement, and an expression assignment. Commands in a for statement are executed as long as the conditional statement evaluates to true. Each time the loop cycles though the commands the expression assignment alters the initial expression. The basic form of the for statement is for (expression; condition; expression assignment) { // do something } In this example of a for statement the name of each day of the week starting with today will appear until the last day, "Saturday," appears: var arrDateArray = new Array('Sunday','Monday','Tuesday','Wednesday','Thursday', 'Friday','Saturday'); var dateToday = new Date; for (var dateDay = dateToday.getDay(); dateDay !== 7; dateDay++) { document.write(arrDateArray[dateDay] + "
"); } Caution It is entirely possible to create a for loop that never ends. If you’re using a development environment that allows you to test your code on the fly, do yourself a favor and save the script before testing. An unending loop is a great way to lock up your browser. A variation of the for statement is the for-in statement. This statement loops through each element of an object and extracts the name and value of each object property. This can be helpful when debugging your script. You can simply create a script that prints the properties for the troublesome object and compare between browsers. If the property appears differently in one or doesn’t appear at all, then you will have to create a work- around. The name of each property can be accessed by referencing a statement variable. The value is found by using the bracket notation. The for-in loop is written like this: for (var varVariableName in objObject) { // varVariableName = property name, objObject[varVariableName] = property value; } For-in loops are also handy as shortcuts for looping through an array. The following example loops through one array and generates a weather forecast from another: var arrDateArray = new Array('Sunday','Monday','Tuesday','Wednesday','Thursday', 'Friday','Saturday'); var arrWeatherArray = new Array('Sun','Clouds','Wind','Rain','Snow','Fog','Hail'); for (var intCount in arrDateArray) { document.write(intCount + ". On " + arrDateArray[intCount] + " expect " + arrWeatherArray[intCount] + "
"); } The while loop shows another way of creating a conditional loop. Rather than requiring an initial expression and an assignment, the while loop only needs the conditional statement. The commands called by a while loop continue to execute until the condition is no longer true. It is up to you, the scripter, to create that situation. The while loop looks like: while (condition) { // do something until the condition is false } Many for loops can easily be converted to while loops. For example, here is the day loop from earlier converted to a while loop: var arrDateArray = new Array('Sunday','Monday','Tuesday','Wednesday', 'Thursday','Friday','Saturday'); var dateToday = new Date; var dateDay = dateToday.getDay(); while (dateDay !== 7) { document.write(arrDateArray[dateDay] + "
"); dateDay++; } The choice of using either the for loop or the while loop is up to you. Generally the for loop saves space, while the while loop is a little easier to read. The newest addition to the family of JavaScript loops is the do-while loop. This loop places the conditional statement at the end, meaning that the commands inside the loop are guaranteed to execute at least once, even if the condition is false. The do-while loop looks like this: do { // do something } while (condition) Functions

A JavaScript function is a set of commands that have been given a unique label. Functions can be called from any point in the script, so they’re especially useful for repeatable tasks. For example, the majority of JavaScript used in the projects for this book is in functions. This allows for modularity, organization, and flexibility.

Calling a function is quite easy in JavaScript; simply add the function name and parentheses to your script like so: f_FunctionName()

Defining a function is a simple process of declaring the name and writing the commands. A function can be defined anywhere in the code, even a separate .jss file. The format for defining a function is as follows: function f_FunctionName(arguments) { // do something }

Arguments are objects or variables that the function can act upon. It is not necessary to have arguments, but it certainly helps when creating truly useful functions. Imagine a function that popped up an alert box containing the value of a text input field. Without arguments you might code it like this: function f_AlertText() { alert(document.formForm.formTextBox.value); }

Now, what if you also wanted to use the same code on another text box? You’d have to create an entirely separate function. But by passing through the object reference to the text box in question you can reuse the same function. function f_AlertText(objTextBox) { alert(objTextBox.value); } Now, when you call the f_AlertText() function, you do it like this: f_AlertText(document.formForm.formTextBox);

Functions can have multiple arguments separated by commas. When referencing a function with multiple arguments you must include the arguments in the same order they appear in the function definition.

As you may have noticed by now, JavaScript has a number of predefined functions called methods. Some of these methods have parameters, required or optional, and some do not. There are too many methods to describe them all in this book, but I’ll take some time to describe a few as they appear in the projects. There is quite a lot more to JavaScript than what I’ve been able to give you in this chapter, but complete coverage would take far too long. For a list of JavaScript references, see Appendix C. As an experienced programmer, you should be able to pick it up quickly. As it stands already, from this chapter you have gained enough working knowledge to allow you to complete the projects in this book.

Chapter 5: Server-Side Scripting and Relational Databases

Overview

In the last two chapters you learned how to create and modify Web forms using HTML and JavaScript. Now it’s time to look at how to store the form data. Form data, as you have already seen, is transmitted in name-value pairs. The final step in making dynamic Web forms is to develop a way of storing and retrieving this information as needed. For this book information will be stored using a database.

You’re undoubtedly already familiar with the simplest form of database: the file system. A file system is a type of hierarchical database where files are stored within folders, which are themselves stored within other folders. Form data can be stored in simple text files within a file system. For example, a file named registered_ users.txt could store usernames and passwords such as the following: dransom:meat_puppet jwyatt:darkpup rbouknight:snookums jyounger:poogle

When the user enters login and password information on the Web form the server opens the registered_users.txt file and reads line by line until it finds a matching name-value pair. If there’s no match, it displays an “invalid login” page.

File system databases are very simple, but unfortunately most Web form applications require a more sophisticated database. Imagine trying to retrieve information from dozens of files located throughout a file system. Simply communicating where the information is in a file system can be cumbersome. Luckily, you have another alternative: the relational database. Relational databases have been around for decades. These systems store data in tables. Data in one table can be related—that is, linked—to data in another table. This increases the amount and complexity of data that can be stored without increasing the complexity of getting at the data. Most relational databases use a set of commands called structured query language (SQL) to store, modify, and delete information. SQL is a standardized format organized by the American National Standards Institute (ANSI). This means that, for the most part, SQL written for one relational database should work with another. The projects in this book use a specific relational database as the core information system. Enough information (along with some insights into the larger possibilities) is provided so that you can complete the projects, but relational database management and SQL are huge topics that can’t be covered completely in this book. For a list of relational databases resources, see Appendix C.

Server-Side Scripting

Getting information into and out of a database requires an intermediary process. The Web form gathers user-entered information and transmits it to the Web server. Next, the information must be passed to the database, but the Web server must be told where to find the database and which tables in the database to update. This can be accomplished using a server-side scripting language. Server-side scripting allows data from an information source such as a relational database, Web form, or file system to be passed to or from a Web server. All scripting languages also allow for dynamic control of the data. For example, a login request can be stamped with the current date and time, or access to specific data can be limited based on the origin of the request.

Choosing a Scripting Language

Server-side scripting and Web forms have been linked ever since they both appeared on the Internet. The Web began as little more than a complex file-sharing system. A person could gather files from the Web servers of others and in turn serve files from a local server. The process was one way; only a server could transfer information. The limitations of this system became apparent as more people got online. Web forms were added to the HTML standard to allow browsers to send data, and scripting languages were developed to allow Web servers to handle the incoming data.

W3C The World Wide Web Consortium (W3C) is an international committee of programmers, developers, information architects, and Internet leaders who develop standardized protocols for Internet-based technologies. By creating a standard code base they promote interoperability, expandability, and accessibility throughout the Web. Most famous for recommendations for HTML, the W3C also is the leader in standardizing XML, CSS, PNG, and DOM. For more information visit the W3C Web site at http://www.w3.org/.

While there is only one HTML Web form standard as defined by the World Wide Web Consortium (W3C), there are numerous server-side scripting languages. Choosing which one to use is often a matter of personal preference or resources. If you have (or your development team has) a strong background in one scripting language, then by all means use that. For those new to server-side scripting, this is a great time to pick up the basics. This book can be used by the novice as an intro to server-side scripting. If you’re already experienced in a scripting language you should use this chapter to familiarize yourself with the language and techniques used in the projects. This will allow for much easier conversion of the code when it comes time to create your own Web forms.

The following sections provide a breakdown of the most popular server-side scripting languages. CGI and Perl

CGI stands for Common Gateway Interface. Unlike the other scripting options presented here, CGI is not a single language—it is a set of specifications that allow the Web server to communicate with other applications or data sources. Any programming or scripting language that adheres to the CGI standard can be used to produce CGI applications. Common scripting languages used for CGI include tcl, AppleScript, and Perl. By far the most popular CGI language is Perl.

As the oldest server-side scripting language, Perl has the advantage of a large online community of scripters. Many types of applications have already been developed and are available for free online. This makes Perl one of the simplest languages to learn, yet it remains flexible enough for most needs.

The disadvantage of Perl (as with all CGI applications) is that the Web server is required to start a new process for each use of the CGI code. While CGI applications are typically small, the resources needed to run hundreds or thousands of processes can be overwhelming for some servers.

Perl predates the need for Web servers to use database communication, and as a result, has no built-in database functionality. Generally Perl scripts connect to databases using the database interface module or DBI, an add-on to Perl. Here’s an example: use DBI; my $dbhandle = DBI->connect( "dbi:mySQL:databasename", "username", "password" ); Note Not all CGI-compatible languages are scripting languages. It is entirely possible to create CGI programs using C++, Visual Basic, or Fortran. These types of CGI applications are compiled after coding. This produces faster processing at the cost of slower development. Because of the constantly changing nature of the Web most developers prefer the simplicity of scripted CGI to compiled applications.

ASP

ASP (Active Server Pages) is Microsoft’s entry into the server-side scripting community. As part of a larger Active Scripting environment, ASP runs as part of Microsoft’s Internet Information Server (IIS), Windows NT, or Windows 2000 Servers. ASP combines VBScript and ADO (Active Data Object) components to generate dynamic pages, connect to databases, and handle Web form data.

The support and technical documentation provided by Microsoft make ASP a good choice for those running Microsoft Web servers. As an integrated part of the Web services, ASP runs quickly, seamlessly, and universally. The rub is that few quality implementations of ASP are available for non-Microsoft servers. Those using Apache, Netscape, or Java Web servers cannot take advantage of ASP.

Typical ASP database communication involves using the ADO Data Interface. An ODBC connection is established using the operating system of the Web server and an ODBC driver specific to the database. Then the VBScript inside the ASP page can communicate with the database like this: Set oConn = Server.CreateObject("ADOBD.Connection") oConn.Open " DATABASE=databasename;DSN= DSNName;UID= username; password=password;" JSP

Java has been promoted as a universal programming language that allows a developer to “write once” for all operating environments. JSP (JavaServer Pages) is the attempt to leverage this advantage into the Web scripting community. Java isn’t a typical scripting language; it was developed based on the C programming language. Most developers will find that Java is the most difficult to learn of all the options presented here, but its complexity also allows for incredible flexibility.

JSP involves including Java code in Web pages. The JSP-enabled server then reads the code and executes the commands. Java code can also be stored as reusable components in objects called JavaBeans. JavaBeans allow for easier code modularity and can be shared between JSP applications.

With the ability to transfer code between vastly different Web servers and operating environments, JSP has attracted a great number of developers. One common complaint about JSP pages, however, is that they lack speed. Because all commands must run through a JRE (Java Runtime Environment) on the server before being processed, page load time can be slow.

JSP accesses a relational database through Java Database Connectivity (JDBC). Much like Microsoft’s ODBC, a JDBC connection must be established using the operating environment and an approved JDBC driver for the specific database. JSP code then references the JDBC connection. Here is a typical connection string: Class.thisClass("sun.jdbc.odbc.JdbcOdbcDriver"); java.sql.Co9nnection connection = java.sql.DriverManager.getConnection ("jdbc:odbc:DataBaseName","username","password"); ColdFusion

ColdFusion (CF) is less of a scripting language and more like an extension of HTML. ColdFusion uses special HTML-like tags that produce dynamic content, communicate with a database, and interact with file system objects. A special ColdFusion server processes the ColdFusion files, reads the CF tags, and performs the necessary actions. The requirement for this “middle-ware” server discourages some developers from using it. However, the similarity to HTML makes ColdFusion very easy for a non-programmer to learn. At the same time, experienced programmers often find CF simplistic and limiting.

Much like ASP, ColdFusion requires that an ODBC connection be established before connecting to a database. Typically this is done through the ColdFusion Administration program that comes with the ColdFusion server. Once the ODBC connection is established, CF code can be used to connect to the database like this:

PHP

PHP (PHP Hypertext Preprocessor) was developed in the mid-1990s by Rasmus Lerndorf. Lerndorf combined a scripting engine, a set of prepared Web tools, and the Form Interpreter to generate a quick, simple, yet robust and open-source scripting language. Today the latest version of PHP is version 4 (PHP4); it is based on the new, faster, more universal Zend scripting engine. PHP use is steadily growing as more developers are introduced to its advantages.

PHP runs on virtually any Web server with few variations in code. The Zend scripting engine is small, incredibly fast, and versatile enough for virtually any Web application. PHP allows for increased modularity, scalability, and control. Best of all, PHP is free! It doesn’t require a specific type of server the way ASP does, or expensive middle-ware like ColdFusion. It’s faster than JSP and easier to learn than Perl. For these reasons and more, PHP is my choice for the projects in this book. The next section will cover a few of the basics of PHP scripting. Of course, there isn’t nearly enough room to cover the entire topic, but I’ll provide enough to get you started. For a list of PHP resources see Appendix C. If you feel more comfortable with another scripting language, feel free to adapt these techniques. Later in the chapter I’ll cover an example of converting PHP to ASP. Note The original meaning of PHP was “Personal Home Page.” It began as a set of tools Lerndorf put together to track visitors to his online r…sum…. For information on installing PHP4 visit http://www.php.net/. For more information on the Zend engine visit http://www.zend.com/. PHP articles and support forums are located at http://www.phpbuilder.com/. PHP Standards and Conventions PHP is embedded into a page using the tags . Typically PHP pages are named with the .php or .php4 extensions, but this is not required. Depending on your server settings, .htm, .html, or any other extension can be processed for PHP. PHP has the standard control structures, variables, operators, functions, and objects that you may be familiar with from other programming and scripting languages. In fact, in Chapter 4 you’ll notice a number of similarities between JavaScript and PHP. Like JavaScript, PHP uses semicolons to mark the end of a line of code. PHP also uses a set of operators and variable handling routines similar to the ones in JavaScript. A variable in PHP begins with the dollar sign character ($) and can be assigned like this: $strBadMojo = "Dr. Evil"; Also like JavaScript, PHP does not require that the variable type be expressly declared. PHP uses the same // and /* */ notation to mark comments and the backslash (\) escape character.

A great way to modularize your PHP code is to group functions into categories. Each function group can then have its own PHP page. These subpages can then be called when needed. For example, all database connection commands can be stored in a file called i_phpdo.php. Any page on your site that requires connection to the database can reference this file. The advantage here is that if the database connection string needs to be changed then only one file has to be modified. The best way to include exterior files in PHP is to use the include command: include("./objects/i_phpdo.php"); Unlike other scripting languages, PHP lets you use the include command inside control structures such as an if command. This makes it possible to include different files for different circumstances. PHP also includes the exit() command. This allows you to programmatically dictate when the scripting engine should stop processing the page. This is great for error handling. For example, if a database connection fails, the exit() command can be used to prevent unnecessary script processing.

As you’ll discover in the coming chapters, PHP handles functions, arrays, and strings in a similar manner to JavaScript. The specifics of how this is done are covered on a case- by-case basis throughout this book, as needed. PHP and Web Forms As you learned earlier, the Form Interpreter was an integral part of PHP almost from the beginning. It goes without saying then that PHP was designed in part with Web forms in mind. To understand how PHP handles form data it is important to know a little about how data is transferred over the Web. Web pages as we know them are transferred using HTTP (Hypertext Transfer Protocol). HTTP involves one computer, the client, submitting a request for information to another computer, the server. The server then evaluates the request and sends back the appropriate information. HTTP is a stateless protocol, meaning that once the request and response have been processed there is no inherent system of remembering what just happened. This is one reason denial of service attacks have been successful in the past. The typical Web server can’t distinguish between a million requests from a single client and a single request from a million clients. This also means that if the Web server is to process form information it must do so on the very next page after a form is submitted. You saw in Chapter 3 that the action attribute is part of any FORM tag in HTML. When using a server-side scripting language, the action of any form must be set to the name of the page or program that will be using the information. For example, on a registration form residing on a page called register.html the action attribute of the form tag would point to thanks_for_registering.php.

It is entirely possible for a Web form located on a PHP page to point to itself. The Chapter 5 folder on the book’s Web site contains a file named hello.php. Copy this file to your Web root directory to see an example of a Web form that points to itself. For your convenience the text of the file is included in this chapter. Note You will need to have PHP installed for this file to work. For installation files and instructions visit http://www.php.net/. hello.php Hello <?php echo $strName ?>

I'd like to welcome you properly, what's your name:
The first part of this page declares an empty variable—$strName—and assigns it the value of the text field if the form has been submitted. The line that reads getenv(‘QUERY_STRING’) checks for the environmental variable $QUERY_STRING, which contains the value of the URL after the ? (question mark) character. Because there is no ? character the first time the page is loaded, the $strName variable is left blank. An if / else command is used to check this variable and change the output of the page accordingly. As you can see, PHP automatically assigns a variable when it encounters a name-value pair in a form request. The name of the form element is assigned as the name of the variable (with the added $ character) and the value of the form element is placed in the value of the variable. There is no need to specifically request a form variable. PHP creates the variable for you. This is another reason naming conventions are so important. Figure 5.1 shows what happens when a small error in naming occurs in the hello.php file, as in the next block of code.

Figure 5.1: What kind of name is “Submit” anyway?

hello_bad.php Hello <?php echo $strName ?>

I'd like to welcome you properly, what's your name:

PHP arrays are commonly used in conjunction with Web forms. Take, for example, a music catalog form that allowed the user to enter the title and personal ranking of a collection of favorite albums. The HTML for the form could look like this:

Album Name: Rating:
Album Name: Rating:
Album Name: Rating:
When this form is submitted PHP will generate a variable for each form element. There are only six here, but imagine what happens when the form gets really long. A simpler and shorter way of producing the same results is to use a for loop and an array to name the elements.
Album Name: Rating:
The code begins with a simple for loop to iterate three times. The bonus to using this technique is that when the form needs to expand to four, five, or even a dozen or more elements, only one part of the script needs to be changed. Next, rather than give each form element a unique name, arrays are designated using []. When this modified code is submitted to the server the PHP scripting engine will generate two arrays named album[] and rating[]. The values entered into the form can then be accessed like any other array element. For example, the name of the second album would be held in album[1]. As in JavaScript, PHP arrays begin at 0. Converting from PHP

PHP is not for everyone. Some developers have a favorite scripting language that they feel comfortable with and know inside out. Others don’t have the time or resources to spend on learning a new scripting language when another has worked for them in the past. If you fall into one of these groups, or for any other reason prefer not to use PHP, you’ll need to convert the sample codes in this book to your preferred scripting language.

While there is no magic formula for converting PHP, it shouldn’t be too difficult for an experienced programmer. PHP was designed to be readable and I’ve made a special effort to include comments, descriptions, or summaries whenever possible. The key is to recognize what the PHP script does rather than how it’s doing it.

Here’s an example of a typical PHP page: list_files.php

Directory Listing

Files in:

Let me break it down section by section. The first part of the code is very simple. It checks for a query string attached to the URL. If one is present the value is stored in a variable, otherwise it defaults to “C:/”. if (getenv('QUERY_STRING')) { $strDirName = $loc . "/"; } else { $strDirName = "C:/"; }

The next line opens the directory, based on the variable just set. $objDir = opendir($strDirName);

Two arrays are initiated: $arrFileList = array(); $arrFileLink = array(); A while loop is used to cycle through all the files in the open directory. The readdir() function returns the name of the next file in the directory. The while loop assigns this name to the variable $strFileName. while ($strFileName = readdir($objDir)) PHP produces dot file names to represent the current directory and the parent directory. An if statement checks to make sure that these are not included in the rest of the code. if (($strFileName != ".") && ($strFileName != ".."))

Next, the file name is appended to the end of one of the arrays initiated earlier: $arrFileList[] = "$strFileName"; An if statement then checks if the file is a directory or not and assigns the corresponding URL to the $arrFileLink array. if (is_dir($strDirName . $strFileName)) { $arrFileLink[] = "http://ransom/php/listfiles.php?loc=" . $strDirName . $strFileName; } else { $arrFileLink[] = $strDirName . $strFileName; }

Next, the directory is closed: closedir($objDir); The directory variable is output and a for loop is used to display the file names and links.

While you may not be a master of PHP yet, you can see how easy PHP is to read. Once you understand what the code does, it becomes a simple matter to convert it into another scripting language. Here’s an example of the same functionality done in ASP: <% dim strDirName, objFSO, objFolder, objFiles, objThisFile, objThisDir, objSubFolder, intArrayCount if Len(request.QueryString("loc"))>0 then strDirName = CStr(request.QueryString("loc")) + "/" else strDirName = "C:/" end if set objFSO = CreateObject("Scripting.FileSystemObject") set objFolder = objFSO.GetFolder(strDirName) set objFiles = objFolder.Files redim arrFileList(0) redim arrFileLink(0) redim arrDirList(0) redim arrDirLink(0) intArrayCount = 0 for each objThisFile in objFiles if ((objThisFile.name <> ".") AND (objThisFile.name <> "..")) then intArrayCount = CInt(intArrayCount + 1) redim preserve arrFileList(intArrayCount) redim preserve arrFileLink(intArrayCount) arrFileList(CInt(intArrayCount-1)) = objThisFile.name end if next intArrayCount = 0 set objSubFolder = objFolder.SubFolders for each objThisDir in objSubFolder intArrayCount = CInt(intArrayCount + 1) redim preserve arrDirList(intArrayCount) redim preserve arrDirLink(intArrayCount) arrDirList(CInt(intArrayCount-1)) = objThisDir.name next %> Directory Listing

Files in: <% response.write strDirName %>

Directories:

    <% for each strDir in arrDirList if (strDir<>"") then response.write "
  • " + strDir + "" end if next %>
Files:
    <% for each strElement in arrFileList if (strElement<>"") then response.write "
  • " + strElement + "" end if next %>

Database Connectivity

Earlier I called server-side scripting an intermediary between the Web server and a database. Here’s a closer look at how that process works.

First, a Web server receives a request for a page from a client. If the requested page is a PHP page, then the PHP scripting engine that resides on the Web server processes the page and performs any necessary actions. If any external data is called for the PHP engine contacts the data source. This external data is then added to the data stream in a format determined by the PHP page. Finally, the data stream is sent to the Web server to send to the client.

Most commonly, the external data required by PHP either reside in a file system or in a relational database. In the next section I’ll cover the basics of how PHP can be used to communicate with these data sources.

PHP and File Systems

PHP makes using file systems quite easy compared with other scripting languages. PHP can open, modify, and create simple text files, comma-delimited files, and other non- relational database files. In fact, using PHP you will be able to control any file on the file system—including whole directory trees!

A common use of PHP’s file-handling ability is to store simple data, such as e-mail addresses, in text files for quick storage and retrieval. This is just the tip of the iceberg for file handling with PHP. Using the file system controls and a bit of creative string manipulation, it is entirely possible to create an entire template system for a Web site where the PHP code is removed from raw HTML. This kind of template system would allow HTML coders to change layout, content, and appearance without having to understand PHP. The first project shows an example of a very simple template system.

PHP has a simple command set for manipulating file systems. This command set includes functions for opening, closing, reading from, writing to, and navigating within files. It also has a number of directory-specific functions that allow you to change the working directory, create new folders, and read the contents of a folder. The most common file system function you’ll encounter is fopen(). This function takes the file name and a mode argument as parameters. The fopen() function returns an integer that is used as a file handle for later commands involving the file. Table 5.1 lists the acceptable mode values. Table 5.1: PHP File Open Modes Mode Description

r Opens a file for reading only r+ Opens a file for reading and writing. Any data written to the file will be added at the beginning. a Opens a file for appending data only. If the file does not exist it will be created. Data is added to the end of the file. a+ Opens a file for reading and appending. If the file does not exist it will be created. Table 5.1: PHP File Open Modes Mode Description Data is added to the end of the file. w Opens a file for writing only. If the file does not exist it will be created. If the file does exist any contents will be deleted. w+ Opens a file for reading and writing. If the file does not exist it will be created. If the file does exist any contents will be deleted.

Additionally, the “b” option can be added to the mode. This tells PHP to treat the file as a binary object, such as an image or application file. Here’s how the binary mode could be used: $objImage = fopen("logo.gif", "rb"); fpassthru($objImage); The fpassthru() function in the preceding example sends the contents of a file directly to the output stream. The only parameter that is needed is the file handle generated by the fopen() function. Both fpassthru() and fopen() can be combined into a single command: readfile(). This function only requires the file name as a parameter. Both fpassthru() and readfile() cause the file to close after execution, but some other functions make it necessary to close the file manually. The fclose() function takes the file handle as a parameter and closes the file. Before the file is closed, however, you may want to modify the contents or select only portions of the file to display. PHP uses the fread() function to read contents of a file into a string. This string can then be manipulated or displayed like any other string. The first parameter is the file handle, the second is the number of characters to add to the returned string. In the preceding example only the first 50 characters are returned. This parameter is required, but you can use the filesize() function to get the size of a file. Keep in mind that invisible characters such as carriage returns, spaces, and tabs are all considered characters. Note The filesize() function doesn’t actually determine the number of characters in a file; it finds the size in bytes that the file takes up on the server. For text-based files such as HTML documents this should come out to just a little more than the total characters. Don’t worry about the excess—PHP always stops reading at the end of the file regardless of the parameters. Using the filesize() function to determine where to stop reading a file is just a quick shortcut when the size of the file is unknown. As you can see, filesize() takes the name of the file as a parameter, not the file identifier. Another function that uses the file name is file(). This function is unique in that it returns a numbered array filled with the contents of the file. Each line of the file produces an array element. The first line of the file will be array element 0. The file() function reads the content from the beginning of the file, but other file system commands, such as fread(), begin at the current position of the file pointer within the file. It is therefore necessary to have a way to move from place to place in a file. First, though, you have to know where the file pointer is. The ftell() function takes the file handle as a parameter and returns the current position within the file. $intFilePos = ftell($objFile); Moving through the file is accomplished using the fseek() function. This command uses an offset parameter to move within the file. A positive offset moves the file pointer toward the end of the file, and a negative value moves it toward the beginning. Besides the file handle and the offset, fseek() also has an optional third parameter, whence. The whence parameter has three options: SEEK_SET moves the file pointer from the beginning of the file. This is the option used if no whence parameter is specified. SEEK_CUR moves the file pointer from the current position. SEEK_END moves the file pointer from the end of file. The following example shows how to read the last three characters in a file: A simple function for navigating to the beginning of a file is rewind(). This function takes the file handle as a parameter. $objFile = fopen("testfile.txt", "a+"); rewind($objFile); Writing to a file is as simple as using the fwrite() function. This command accepts the file handle, the string to write to the file, and an optional length parameter. Using this function, writing will continue until either the end of the string is reached or the length parameter’s specified number of bytes has been written. Like the fread() command, fwrite() occurs at the current file pointer position within the file. $objFile = fopen("testfile.txt", "r+"); $strTextToAdd = "I'm a follower, put me at the back of the line"; fseek($objFile, 0, SEEK_END); fwrite($objFile, $strTextToAdd); Beyond simply reading and writing to files, PHP allows you to manipulate files themselves in a more fundamental way. But before you change anything about a file, it’s a good idea to check to see whether you actually have a file to play with. PHP provides the file_exists() function, which returns true if the file is encountered and false if it is not. $strFileName = "testfile.txt"; if (file_exists($strFileName)) { $objFile = fopen($strFileName, "r"); } else { echo "Can't find file"; } Now that you know the file exists, you can use the rename() function to change the file name or copy() to create a duplicate file. Both functions take the name of the original (source) file as the first parameter. The second parameter is the new name or the destination location, respectively. $strOrgFileName = "testfile.txt"; $strNewFileName = "testfile.bak"; $strDestination = "/usr/bak/tmp/"; if (file_exists($strOrgFileName)) { copy($strOrgFileName, $strDestination . $strOrgFileName); rename($strDestination . $strOrgFileName, $strDestination . $strNewFileName); } else { echo "Can't find file"; } To delete a file, use the unlink() function. The only parameter is the file name. unlink("testfile.txt"); You can also use PHP to work with directories. To set the current directory, use the chdir() function. The location of the directory is passed as the parameter and can be absolute (/use/local/) or relative (../../bin/). To open the directory, use opendir(). This function takes the path to the directory as a parameter and provides a directory handle that can be used by other directory functions. chdir("/usr/local/web/"); $objDir = opendir("."); $strFileName = readdir($objDir); The readdir() function is used to read the contents of a directory. This command moves to the next item in the directory and returns the name of the file or folder, if any. This allows for forward movement through the directory. To back up to the beginning, use rewinddir(). This function moves to the beginning of the directory. Like readdir(), rewinddir() takes the directory handle as a parameter. To close the directory, use the closedir() command. closesdir($objDir); The mkdir() function is used to create new directories. The first parameter is the path of the new directory. The second parameter is optional and sets the UNIX access permissions. mkdir("/usr/bak/tmp", 0777); If a directory is empty you can delete it with the command rmdir(). The sole parameter for this function is the name of the directory to be removed. $strFileName = rmdir("/usr/bak/tmp"); Find It Online For more information about these and other PHP file and directory functions visit the PHP online manual, available at http://www.php.net/docs.php. Not only is this a great place to see the official PHP documentation, it also gives you a chance to see the notes and advice of other PHP programmers on a function-by-function basis.

PHP and Relational Databases

If you’re familiar with PHP you’ll have noticed that it offers quite a large number of relational database functions. You might ask why—after all, other scripting languages use only a small set of generic database commands. The short answer is that the creators of PHP didn’t want to limit the functionality of PHP in that way. Generic database functions are great if you are planning on changing databases; in fact, PHP includes ODBC commands for just this purpose. However, generic commands can’t take advantage of the strengths of individual databases. For example, Oracle 8 databases allow a script to toggle internal debugging on and off. This is not a common feature among databases, and therefore couldn’t be included in a generic database command set.

PHP offers the best of both worlds: a generic ODBC function set that can handle any ODBC-compliant database, plus database-specific connectivity functions that can maximize PHP-to-database functionality. Choosing a Database Most Web site developers don’t have the luxury of choosing a database. Typically a company will have a legacy database system that represents a large amount of committed resources. However, sometimes you will have the chance to make the call. If so, the best advice I can give is to do a ton of research. Keep an eye out for issues such as reliability, speed, ease of use, technical support, and cost.

If you’ve never used a relational database before you should start with the free products, such as MySQL or PostgreSQL.

Once you’ve settled on a database, install the software (if necessary) and check your connection. PHP doesn’t require that an ODBC or JDBC connection be established with the database, so creating a DNS entry is optional. Please see your database documentation or administrator for information on establishing a connection between your Web server and the database. Many Web servers, such as Apache, require that an environmental variable be set to the location of the database at the time the server starts. For example, to connect Apache to an Oracle database the ORACLE_HOME variable must be set in the httpd.conf file.

What follows is a quick breakdown of the PHP functions for a few of the most popular databases. Optional parameters are in italics. Find It Online For a complete list of PHP database functions visit the PHP manual at http://www.php.net/manual/en/. Oracle PHP Oracle functions are divided between the standard (pre-7) functions and those specifically created for Oracle 7 and above. Even though these latter commands work with Oracle 7 they are called Oracle 8 functions and are designated with the OCI prefix. You will need to install the Oracle 8 client libraries, available at http://www.php.net/, to use OCI functions. Table 5.2 shows how PHP relates to Oracle. Table 5.2: PHP-Oracle Connectivity

Function Name Returns Description Type

ORA_Bind (cursor, variable, parameter, Boolean Binds a length, type) specific variable to anOracle parameter ORA_Close (cursor) Boolean Closes an open data cursor ORA_ColumnName (cursor, column) String Retrieves the name of a column ORA_ColumType (cursor, column) String Determines the data type of a column ORA_Commit (connection) Boolean Commits a transaction ORA_CommitOff (connection) Boolean Turns off automatic commit after ora_exec( ) ORA_CommitOn (connection) Boolean Turns on automatic commit ORA_Do (connection, query) Boolean Combines ora_parse (), ora_exec( ), and ora_fetch () Table 5.2: PHP-Oracle Connectivity Function Name Returns Description Type ORA_Error (cursor or connection) String Returns the last Oracle error message ORA_ErrorCode (cursor or connection) Integer Retrieves the specific Oracle error code for the last error ORA_Exec (cursor) Boolean Executes a parsed statement ORA_Fetch (cursor) Boolean Returns the next values in a row from the cursor ORA_Fetch_Into (cursor, array, Boolean Returns an options) entire row into an array ORA_GetColumn (cursor, column) Varies Fetches a column from the current cursor ORA_Logoff (connection) Boolean Logs out and disconnects from the database ORA_Logon (user, password) Integer Connects to or False an Oracle database using a username and password ORA_pLogon (user, password) Integer Creates a or False persistent connection to an Oracle database ORA_NumCols (cursor) Integer Returns the number of columns in a result ORA_NumRow (cursor) Integer Returns the number of rows Table 5.2: PHP-Oracle Connectivity Function Name Returns Description Type ORA_Open (connection) Integer Opens a or False cursor ORA_Parse (cursor, SQL, defer) 0 or -1 Validates an SQL statement with the Oracle database ORA_Rollback (connection) Boolean Undoes a transaction OCIDefineByName (statement, column Integer Returns a name, variable, type) column into a PHP variable OCIBindByName (statement, Integer Binds a placeholder, variable, length, type) PHP variable to an Oracle placeholder OCILogin (username, password, db) Integer Connects to an Oracle database OCIpLogin (username, password, db) Integer Creates a persistent connection to an Oracle database OCInLogin (username, password, db) Integer Connects to an Oracle database using a new connection OCILogoff (connection) Boolean Logs off and disconnects from the database OCIExecute (statement, options) Boolean Executes a parse statement OCICommit (connection) Boolean Commits all open transactions OCIRollback (connection) Boolean Undoes a transaction OCIRowCount (statement) Integer Returns the number of rows Table 5.2: PHP-Oracle Connectivity Function Name Returns Description Type returned by a statement OCINumCols (statement) Integer Returns the number of columns returned by a statement OCIResult (statement, column) Varies Retrieves the data for a column in the current row OCIFetch (statement) Integer Returns the next row from the statement OCIFetchInto (statement, array, mode) Integer Fetches the next row into the specified array OCIFetchStatement (statement, array) Integer Returns all rows into an array OCIColumnName column String Retrieves the name of the (statement, column number)

OCIColumnSize Integer Returns the size of the column (statement, column name or number) OCIColumnType (statement, column) String Returns the data type of the column OCIStatementType (statement) String Returns the type of the statement OCINewCursor (connection) Integer Creates a new cursor OCIFreeStatement (statement) Boolean Releases the resources Table 5.2: PHP-Oracle Connectivity Function Name Returns Description Type dedicated to the statement OCIFreeCursor (statement) Boolean Releases the resources dedicated to the cursor OCIParse (connection, query) Integer Parses a or False query OCIError (statement or connection) Array or Returns the False last Oracle error message OCIInternalDebug (Boolean) Void Activates or deactivates Oracle debugging

Informix Informix databases are a popular series of relational database products produced by IBM. Be sure you have the INFORMIXDIR and INFORMIXSERVER environmental variables set properly. Table 5.3 shows how PHP relates to Informix. Table 5.3: PHP-Informix Connectivity Function Name Returns Description Type IFX_Connect (db, username, password) Integer Connects to or False an Informix database IFX_pConnect (db, username, Integer Creates a password) or False persistent connection to an Informix database IFX_Close (connection) TRUE Closes the connection

IFX_Query Integer Submits a or False query (query, connection, type, blobidarray)

IFX_Prepare Integer Prepares a query for execution (query, connection, type, Table 5.3: PHP-Informix Connectivity Function Name Returns Description Type blobidarray) IFX_Do (result ID) Boolean Executes a prepared statement

IFX_Error() String Retrieves the latest Informix error code IFX_ErrorMSG (error code) String Retrieves the latest Informix error message IFX_Affected_Rows (result ID) Integer Returns the number of rows in the query IFX_Fetch_Row (result ID, position) Array Produces a numbered array from the current row IFX_HTMLTbl_Result (result ID, Integer Formats a options) or False query into an HTML table IFX_FieldTypes (result ID) Array or Returns an False array with the field names as key and the field types as data IFX_FieldProperties (result ID) Array or Returns an False array with the field names as key and the field properties as data IFX_Num_Fields (result ID) Integer Returns the or False number of columns in a query IFX_Num_Rows (result ID) Integer Returns the or False number of rows in a query Table 5.3: PHP-Informix Connectivity Function Name Returns Description Type IFX_Free_Result (result ID) Integer Releases or False the resources dedicated to the result IFX_TextAsVarChar (mode) Void Sets the default text mode for future select queries IFX_ByteAsVarChar (mode) Void Sets the default byte mode for future select queries

PostgreSQL PostgreSQL is a free database originally developed by the UC Berkeley Computer Science Department. PostgreSQL offers many of the features you might only expect to find in expensive commercial databases. Table 5.4 shows how PHP relates to PostgreSQL. Table 5.4: PHP-PostgreSQL Connectivity Function Name Returns Description Type PG_Close (connection) Boolean Closes the connection PG_Connect (host, port, options, tty, db) Integer Connects to a PostgreSQL database PG_DBName (connection) String Retrieves the name of the active database PG_ErrorMessage (connection) String Retrieves the latest PostgreSQL error message PG_Exec (connection, query) Integer Executes a query PG_Fetch_Array (result, row, type) Array Returns a row as an array PG_FieldIsNull (result ID, row, field) Boolean Determines if a field has a null value Table 5.4: PHP-PostgreSQL Connectivity Function Name Returns Description Type PG_FieldName (result ID, field number) String Retrieves the name of a field PG_FieldNUm (result ID, field name) Integer Returns the number of a field PG_FieldType (result ID, field number) String Determines the data type of the field PG_FreeResult (result ID) Integer Releases the resources dedicated to the result PG_GetLastOID (result ID) Integer Returns the identifier of the last object PG_Host (connection) String Returns the name of the host PG_NumFields (result ID) Integer Returns the total number of fields in the result PG_NumRows (result ID) Integer Returns the number of rows in the result PG_pConnect (host, port, options, tty, Integer Connects to db) a PostgreSQL database using a persistent connection PG_Port (connection) Integer Determines the port number of the connection

PG_Result Varies Returns the value of the result (result ID, row number, field name) Table 5.4: PHP-PostgreSQL Connectivity Function Name Returns Description Type PG_TTY (connection) String Returns the tty name of the connection

ODBC Connections ODBC (Open Database Connectivity) is a technology developed by Microsoft in an attempt to produce a widely accepted API (application-programming interface) for database access. Using a set of ODBC drivers, any client can communicate with a number of databases using the same set of commands. Most of the popular relational databases have made ODBC drivers available, either natively or through a third party. The disadvantage of ODBC is the lack of speed. Commands must be created on the client in proper ODBC vernacular, transmitted to the database server, and then translated into the native command set of the database. Still, the flexibility offered by ODBC makes it a popular database interface. Table 5.5 shows how PHP relates to ODBC. Table 5.5: PHP-ODBC Connectivity

Function Name Returns Description Type

ODBC_Autocommit (connection, toggle) Boolean Activates or deactivates autocommit ODBC_Close (connection) Void Closes the connection ODBC_Close_All () Void Closes all connections ODBC_Commit (connection) Boolean Commits the transaction

ODBC_Connect Integer Connects to an ODBC DSN (DSN, username, password, type) ODBC_Cursor (result) String Returns the name of the cursor ODBC_Do (connection, query) Integer Executes a query ODBC_Exec (connection, query) Integer Prepares and executes an SQL query ODBC_Error (connection) String Returns the last ODBC error code ODBC_ErrorMsg (connection) String Returns the Table 5.5: PHP-ODBC Connectivity Function Name Returns Description Type text of the last ODBC error message ODBC_Execute (result, parameters) Boolean Executes a prepared statement ODBC_Fetch_Into (result, row number, Integer Places one array) or False row of results into an array ODBC_Fetch_Row (result, row number) Boolean Fetches a row from a result ODBC_Field_Name (result, field number) String or Returns the False name of a column ODBC_Field_Num (result, field name) Integer Returns the or False number of a column ODBC_Field_Type (result, field number) String Returns the data type of a field ODBC_Field_Len (result, field number) Integer Returns the length of a field ODBC_Free_Result (result) True Releases the resources dedicated to the result ODBC_Num_Fields (result) Integer Returns the number of columns in a result ODBC_pConnect (DSN, username, Integer Establishes password, type) a persistent connection to an ODBC DSN ODBC_Prepare (connection, query) Integer Prepares a or False query for execution ODBC_Num_Rows (result) Integer Returns the number of rows in a result Table 5.5: PHP-ODBC Connectivity Function Name Returns Description Type ODBC_Result (result, options) String Returns the result data ODBC_Result_All (result, options) Integer Prints the or False result in an HTML table ODBC_Rollback (connection) Boolean Cancels a transaction ODBC_SetOption (ID, function, option, Boolean Allows for parameter) adjustments to the ODBC settings ODBC_Tables (connection, qualifier, Integer Fetches a owner, name, types) or False list of all the tables for the DSN ODBC_Columns (connection, qualifier, Integer Fetches a owner, table name, column name) or False list of all column names for a table ODBC_GetTypeInfo (connection, Integer Determines datatype) or False the data types supported by the data source ODBC_Procedures (connection, Integer Fetches a qualifier, owner, name) or False list of all procedures stored in the data source ODBC_Statistics (connection, Integer Fetches qualifier, or False statistics about a table owner, table name, unique, accuracy)

MySQL

MySQL claims to be the world’s most widely used open source database—and considering its many advantages, that’s hard to dispute. The developers of MySQL have made it extremely fast and easy to customize. The result is a relational database rarely matched in speed, compactness, stability, and ease of installation. Best of all, MySQL is available free under the GNU General Public License (GPL). These reasons made it the best choice as an example database for this book. If you choose to use a MySQL database, you can find information, documentation, and downloads at http://www.mysql.com/. If you prefer another database, you can use one of the preceding tables to convert the PHP as needed. Connection between PHP and MySQL is handled just as with other databases, using a custom set of connectivity commands. Table 5.6 shows how PHP relates to MySQL. Table 5.6: PHP-MySQL Connectivity

Function Name Returns Description Type

MySQL_Affected_Rows (link ID) Integer Returns the number of rows

MySQL_Change_User Integer Changes the current user (user, password, db, resource link ) MySQL_Close (resource link ) Boolean Closes the connection MySQL_pConnect (hostname:port:path, Integer Creates a username, password) or False persistent connection to a MySQL database MySQL_Query (query, resource link ) Boolean Submits a query to the database MySQL_Unbuffered_Query (query, Boolean Submits a resource link ) query without fetching the result rows MySQL_Result (result, row, field) Varies Returns the result data MySQL_Select_DB (db, resource link ) Boolean Selects a database MySQL_TableName (result, index) String Returns a table name MySQL_Connect (hostname:port:path, Integer Creates a username, password) or connection Boolean with a MySQL database MySQL_Create_DB (db name, resource Integer Creates a link ) new database MySQL_Data_Seek (result, row number) Boolean Moves the result pointer MySQL_DB_Name (result, row, field) String or Gets the False result data Table 5.6: PHP-MySQL Connectivity Function Name Returns Description Type MySQL_DB_Query (db, query, resource Integer Submits a link ) or False query MySQL_Drop_DB (db, resource link ) Boolean Removes a database MySQL_ErrNo (resource link ) Integer Produces the error number of the last MySQL procedure My_Error (resource link ) String Returns the text of the error message from the last MySQL procedure MySQL_Escape_String (string) String Escapes a string, preventing it from being recognized as a MySQL command MySQL_Fetch_Array (result, type) Array or Produces False either a numeric or associative array from the current result row MySQL_Fetch_Assoc (result) Array or Produces an False associative array from the current result row MySQL_Fetch_Field (result, offset) Object Returns an or False object containing field information MySQL_Fetch_Object (result, type) Object Returns an or False object containing information about the current result row MySQL_Fetch_Row (result) Array Produces a Table 5.6: PHP-MySQL Connectivity Function Name Returns Description Type numbered array from the result row MySQL_Field_Name (result, field index) String Get the name of a field MySQL_Field_Len (result, offset) Integer Returns the length of a field MySQL_Field_Seek (result, offset) Integer Moves the pointer to a field MySQL_Field_Type (result, offset) String Returns the data type of a field MySQL_Frre_Result (result) Integer Releases the resources dedicated to the result MySQL_List_DBS (resource link ) Integer Lists all databases available for the current connection MySQL_List_Fields (db, table, Integer Lists all the resource link ) fields for a table MySQL_List_Tables (db, resource link ) Integer Lists the tables in the database MySQL_Num_Fields (result) Integer Returns the number of fields in a result MySQL_Num_Rows (result) Integer Returns the number of rows in a result

Chapter 6: Modular Interface Design

Overview Modular design is the process of separating blocks of functionality into discrete packages or modules. Web form interfaces can have numerous modules, including design elements, style sheets, client-side functions, and server-side objects. Using modular code involves connecting these modules into a complete interface, and reusing the modules on a page-by-page basis to create the desired functionality.

This process can be compared to building a toy from a pile of Lego blocks. If you wanted to build a plane you might use long and flat blocks for the wings, tall blocks for the tail, and curved blocks for the fuselage. If you were to build a Lego house, however, you might use some of the same pieces, but you would also use a number of completely different blocks and many of the blocks used on the plane might go unused in the house. The goal of your modular interface design should be to give you all the pieces you need to build your interface and at the same time provide the flexibility to add new blocks as needed.

In this project you will create a template system that will allow you to add form elements, JavaScript, style sheets, and PHP functionality in any combination to create a dynamic, modular form interface.

Why Modularize?

Modularization might seem like a lot of extra work, but the long-term benefits far outweigh the short-term investment. Modular code allows the developer to create a “template-based” infrastructure. Think of a template system as a well-organized toolbox. When you need to get a job done, you can quickly and easily find the tools necessary to finish the task. As your template system grows in complexity you add more tools to your repertoire until projects that once might have taken months now take weeks—and those that took weeks now take only days.

Function versus Format

Modular code allows the developer to separate the various parts of the interface. The most important benefit of this is the separation of functional elements from format elements. Functional elements are the parts of the interface that determine its contents and the operation of any interactivity. Format elements determine how the content is to appear and may include such things as style sheets, table layouts, and dynamic layers.

Here’s an example of a typical PHP page that could benefit from additional modularization: <? echo $Title; ?>

The page seems straightforward enough for a developer. A query string is used to gather the page contents from a database. These values are assigned to PHP variables and output within the HTML. Additionally, separate navigation and footer files are included. The code has done a good job of separating out the code within the page, but modularization can take the separation even further by dividing the code into two files: a PHP page and an HTML template.

Here’s what that PHP page might look like:

Here’s how the HTML file would look: {{TITLE}}

{{NAVIGATION}} {{BODY_TEXT}}
{{FOOTER}}
The modular solution is a little more complicated for the developer, but look at it from the viewpoint of a designer or HTML editor. Nondevelopers can easily read and change the HTML file without having to work around confusing server-side scripts and include statements. This type of modular design also allows development teams to restrict access to the PHP code while still allowing the HTML to be globally available. Server- side script files could be secured through the file management system or an RCS.

Assembly Line Coding

Henry Ford, innovator that he was, realized that specialization was the key to mass production. He discovered that by allowing many individual workers to each focus on a small part of a larger project his company could substantially increase production without a significant drop in quality. As a result, the assembly line became standard throughout the industrialized world, with assembly lines used to produce everything from 747s to Childrens’ action figures.

All too often Web development teams forget that they too can take advantage of the assembly line method. Companies mistakenly believe that they can save money by hiring two or three team members with all-around skills rather than a half-dozen specialists. These stunted Web teams then take two to three times as much development time—only to produce an inferior product. For example, a true Web graphics designer might be able to come up with an acceptable design after two or three comps, whereas a combo designer-developer could easily go through eight to ten.

A common misconception is that just about anyone can create an effective Web interface. Modern desktop publishing tools and user-friendly development applications make producing simple Web interfaces easy, even for the beginner. It’s tempting to believe that someone who knows how to use Photoshop, Dreamweaver, or MS-SQL is qualified to be a designer, developer, or DBA, respectively. However, to stick with the assembly line metaphor, that’s like saying someone is qualified to assemble a car frame simply because they can use a welding torch without getting scorched.

What these “torch wielders” tend to forget is that Web professionals are more than the sum of their applications. Quality design, development, and architecture come only from talent, experience, and vision. While it is possible for a single person to be an ex pert in a few areas or good at most, it is impossible to be an expert in all areas of Web interface production. Therefore, if you want your project to be all that it can be, you’ll need to call in the specialists.

If you’re creating a Web site for your own use, feel free to do all the work yourself. Besides being a refreshing change of pace it can give you some insight into the demands and limitations of other Web-related fields. Professional projects, however, should involve a complete team of experts. What follows is a breakdown of the type of team members required for a typical Web interface project: § Team manager. This person should have a broad base of familiarity with the technologies involved in the project, but does not need to be an expert. The manager acts to coordinate the production, setting deadlines, resolving disputes, and communicating with the client. A common ineffective alternative: Outlook and a dartboard. § Interface architect. An expert in usability and accessibility, this person is responsible for creating an effective, organized, and understandable interface design plan. Part sociologist and part traffic cop, the interface architect (IA) must create an outline for the interface that meets the very different needs of the users, the production team, and the client. A common ineffective alternative: a Rorschach inkblot. § Web graphics designer. This person must create appealing graphics and page designs that work within the structure of the interface outline created by the IA. Don’t forget the importance of the “Web” part of this job. Creating effective and usable Web graphics involves any number of issues and considerations that not every print designer can appreciate. A common ineffective alternative: www.clipart.com. § HTML coder and optimizer. This person turns graphic designs into HTML. An HTML optimizer is responsible for creating flexible yet compact HTML page templates that will work on the majority of browsers and platforms. Don’t underestimate the significance of clean, easily readable HTML code: it not only saves time during the development process, it can also save precious seconds at the browser during rendering. A common ineffective alternative: FrontPage and a decoder wheel. § Web programmer and developer. This professional develops and manages the connection between the source content and the Web server. The Web developer could use server-side scripts, Flash, Java, Active X, or a custom-built API, but in all cases works to massage the content of a Web site into the interface produced by the IA and the Web designer. A common ineffective alternative: 10 million static HTML pages. § Database administrator. In the scope of a Web project the database administrator (DBA) designs, configures, and optimizes the database structure necessary for the interface. A properly developed database can save development time and reduce database downtime. A common ineffective alternative: Microsoft Access and tech support on speed dial.

Looking at these specialists, the advantages of using a modular interface become apparent, particularly for the Web developer. Here’s an example of a typical production cycle that doesn’t take advantage of modular code: 1. The interface architect creates the interface outline and coordinates with the team manager to assign tasks to the team members. 2. The designer creates a graphical design. 3. The programmer develops the back-end system for supplying the content. 4. The HTML optimizer creates an HTML template from the graphical design. 5. The programmer incorporates the HTML template into the server-side script. 6. The site is tested and reviewed. 7. The designer sends any changes to the HTML optimizer for coding. 8. The HTML coder changes the HTML template to incorporate the changes. 9. The programmer modifies the server-side script. 10. Repeat steps 6 through 9 as necessary.

Here’s the same production cycle with a modular code template system: 1. The interface architect creates the interface outline and coordinates with the team manager to assign tasks to the team members. 2. The designer creates a graphical design. 3. The programmer develops the back-end system for supplying the content and a modular template system for displaying the content. 4. The HTML optimizer creates an HTML template from the graphical design. 5. The site is tested and reviewed. 6. The designer sends any changes to the HTML optimizer for coding. 7. The HTML coder changes the HTML template to incorporate the changes. 8. Repeat steps 5 through 7 as necessary.

Flexibility in Design and Function

One of the great things about the Web is that it is constantly changing. Unlike walking down a city street that might remain unchanged for years, cruising the Information Superhighway results in a new vista each and every trip. It is the constantly changing nature of the Web, the ebb and flow of design and function, content and context, lol and lame that makes the Internet such a unique and fun experience for the user. It’s also what keeps near a million Web developers and designers employed.

Every time business plans, corporate images, and fashions change, the corresponding Web site must change to match. As the vast majority of professional Web sites employ some degree of programming or server-side scripting, this means that a Web developer must be involved. Some projects, such as a complete redesign, might require a Web developer. However, without a modular design even small changes, such as minor HTML fixes and template image changes, can necessitate calling in a Web developer, force a designer or HTML optimizer to dig through confusing scripts, or require buggy or complex workarounds.

Modular design, in effect, frees the Web developer from the shackles of the designer and HTML coder. With a modular design, modifications can be done directly in the HTML template, allowing the developer to pursue more important matters (debating on slashdot, reading User Friendly, playing games, and so on). Not convinced? Think about how big a hassle these sample scenarios would be to implement without modular design: § The bigwigs in marketing decide to change domain names from www.GreatStuff.com to www.WeReallyHaveNoConceptOfBranding Whatsoever.com. Every occurrence of the old name, logo, or URL must be changed. In addition, the image file structure enforced on the new server is different than from the old. § The Web site goes multilingual. All content must be available simultaneously in German, French, and Korean. Don’t forget the page titles, alt tags, and file names. § The client loses his favorite pair of glasses. All fonts must be changed to 24 pt Courier. § The database license is up and the company can’t afford to renew. Change to a freeware database product. § An Internet information company goes public and the new CEO wants to increase ad revenue. Split every article into multiple pages with “Next” and “Previous” links. § The client is nominated for a prestigious award. You have one week until the voting ends to implement a complete design change sitewide.

While nothing could make these tasks pleasant, a modular design at least makes them a lot simpler. Take the first scenario and think about how, in a typical scripting environment (without a template system), the changes might be accomplished.

First, every absolute link to the domain name and relative image link needs to be changed. The DBA could handle any links inside the database and the HTML coder could take care of the links within the anchor and image tags. Most server-side scripts, however, also include hard-coded elements that must be changed. Each dynamic file will have to be searched for file references. The updated image references will have to be entered manually, by you.

Next the new logo must be added to each page. The new logo is much wider than the old and requires a complete rework of the basic table structure; also the logo change requires a subtle shift in the background and text colors of each page. If you thought ahead you created an include file or two so that you don’t have to modify every page on the site, but you are still forced to copy the changes from the HTML template to the server-side scripts yourself.

Finally, any alt tags and text references within the content that mention the old name need to be changed. The HTML coder and the DBA can, for the most part, accomplish these, but it’s easy to forget the amount of hard-coded text within a Web interface. Error messages, alerts, and salutations are often hard-coded. You will need to check these for occurrences of the old name.

Think about how much easier this would be on you if you had a modular template system. First, there would be no hard-coded links or text to change. Next, the HTML coder could easily create a new HTML template without your assistance. Finally, the error messages, alerts, and salutations would be available in the template system for the HTML coder to modify. Isn’t that much better?

This advantage works both ways as well. What if you were expected to add or update the functionality of a Web interface? With a modular template you don’t have to be bothered with pages of extraneous HTML code or design elements; you simply code the new functionality in the script file and ask the designer or HTML coder to make the corresponding changes to the HTML template. With a modular template, a Web programmer is only asked to do one thing: program.

Modularization Considerations

Now that you understand all the benefits of modularization, you might be hopping up and down to get started on your first template project, but there are a few things you should keep in mind. First, modular design is not for everyone. Like usability design, modular design requires that you step into someone else’s shoes for a time. Some programmers just never get that knack; others prefer to keep site maintenance to themselves, either out of a sense of proprietorship or in hopes of increased job security.

Just as some developers are not cut out for modular design, some projects just don’t need it. A 10-page static brochure site without any user interaction, a Flash-based r…sum…, and a plain vanilla intranet solution are a few examples of Web interfaces that wouldn’t benefit from a modular design. Note When developing your modular template system try to keep in mind the HTML coders and designers who will be using the system. Remember, the more comfortable they are with the system the less they will need to come to you for assistance.

If you are unsure whether to create a modular design, ask yourself the following questions: § Will the site ever change? One of the biggest benefits of a modular interface is the flexibility to handle changes. If there won’t be any changes—for example, if you’re putting up an event-oriented site that will be taken down after the event—there is no need for a template system. § Are you the only one working on it? Modular design is expected to improve the organizational structure to facilitate teamwork. Without a team to take advantage of the modular system, there is far less reason to implement one. § How big is the site? A site with less than a dozen pages rarely warrants the extra work needed for a template system. § Can the project timeline handle the added hours? No time = no template. § Will you be working on similar projects in the future? While a small project may not benefit from a modular template system itself, if you do create a template the next time a similar project appears on your schedule you should be able to hash it out in no time flat.

The Template System Modular design is implemented using a system of templates. Rather than adding server- side script to an HTML page, functionality is created through special marker tags that represent dynamic content or sections. The template system looks for these marker tags and replaces them with content defined by the script file. The server-side script files that define content are called object files. The final element of a template system is a process of connecting the template files and the object files.

Designing a Template System

Creating a template system isn’t easy if you have never done it before. But each time you create one it becomes easier. A well-designed template system is something you can reuse over and over again with surprisingly little modification. A poorly designed template system will have you making fixes and adjustments whenever you need to implement it. Therefore, it is important to get it right the first time and avoid the hassle later. Global Functionality First, a template system should be controlled from a central global file. If you do need to make a change to the template system it becomes far easier to do so in a single file rather than dozens of object files. The easiest way to accomplish this is to place an include command in all object files that pulls a file containing the function, class, or object that defines the functionality of the template system.

Take another look at the template example given earlier:

Here’s what the file might look like after being converted to call a set of global functions:

$table_name = "web_content"; $sql = "SELECT * from $table_name WHERE PageKey = \"$id\""; $result = @mysql_query($sql,$connection) or die("Couldn't execute query."); while ($row = mysql_fetch_array($result)) { $Title = $row['title']; $BGImage = $row['image']; $BodyText = $row['body']; } if ($strText = m_LoadFile("template.html")) { $strText = m_AssignContent("TITLE",$Title,$strText); $strText = m_AssignContent("BG_IMAGE",$BGImage,$strText); $strText = m_AssignContent("BODY_TEXT",$BodyText,$strText); $strText = m_AddElements($strText); m_Output($strText); } else { echo "Couldn't open file"; } ?>

The database connection variables have been moved to a global include file called i_connect.php. Here’s what that file looks like: The m_Load(), m_AssignContent(), m_Output(), and m_AddElements() functions are located on the i_template.php file. Chapter 7 covers these functions in detail. Moving the functions to a global page saves typing, improves readability, and allows for easier maintenance and modification. Marker Tags

The key to the template system is the use of marker tags. Marker tags represent dynamic content. This can be a field from a database, a date stamp, the value of a variable, or any content that cannot be added to the template file using simple HTML. Throughout this project the marker tags will appear as text bordered by a pair of curly brackets on each side {{LIKE_THIS}}. You can replace the brackets with another type of notation if you prefer—pairs such as @@, %%, or || also work.

When designing your template system you’ll need to keep in mind the different ways that dynamic content will be displayed on your interface. Dynamic content typically shows up in one of six ways: § Simple Content. Simple content either appears or doesn’t, without regard for the content around it. The object file determines the value of the content and whether it should be displayed. This is the type of content handled by the m_AssignContent() function in the example. § Simple Sections. Sections are areas of HTML content that are bordered by section marker tags. The entire HTML contents of the section, including the marker tags, either appears or doesn’t, without regard for the content around the section. The object file determines whether it should be displayed. Sections in this project will appear like this: {{section Name}}Section Content{{/section Name}}. § Conditional Content. Conditional content appears only inside a section. If the parent section appears, then the content is displayed. In this example:

{{section MetaDescription}} description = {{KEYWORD}}{{/section MetaDescription}}

the conditional content “KEYWORD” would only appear if the section “MetaDescription” does as well. § Conditional Sections. These are sections that appear only if all of the content inside also appears. In the example, if “MetaDescription” were a conditional section then it would appear only if the content “KEYWORD” was also assigned. Otherwise neither the section tags, the content tag, nor “description = ” would appear on the final page. Conditional sections are defined in the object file. § Repeated Sections. These sections repeat the included content multiple times. The object file determines the number of repetitions. Repeated sections are often used for lists such as in:

{{section CatalogItemList}}

  • {{ITEM}}{{/section CatalogItemList}}.

    This would produce a bulleted list filled with all the catalog items. § Paired Sections. Paired sections are two related section blocks where the second section appears only if the first has already been assigned. The most common use of paired sections is with anchor tags or other HTML elements. In this example:

    {{paired Link}}{{/paired Link}}{{ITEM}}{{unpaired Link}}{{/unpaired Link}} The second section unpaired Link will appear only if the paired Link does as well. If the first section is conditional, then neither will appear if the content LINK is not assigned. In any case the content ITEM will always appear.

    Implementing the Template

    Following is what you’ll need to implement your template system. Template Files

    Template files are what the HTML coder and designer will be using in your template system to modify the pages. These files should be simple, easily accessible, and uncluttered. A simple template file may contain six to eight marker tags. The goal is to allow your nontechnical team members to modify these pages without encountering confusing server-side scripts or include statements.

    Here are a few guidelines for creating your template files: § Designate template files using a unique file extension. While any extension such as .html, .htm, .txt, or .php will work, you don’t want a designer to modify an object file by mistake. I typically label template files with the .tpl extension. § Use white space to separate marker tags from surrounding HTML if feasible. § Separate out common use elements such as headers, navigation, and footers. This content is thus made simpler to locate and modify. § Create a separate template for each type of page on the Web site. It is entirely possible to use a template system to create a single template file that includes the HTML content of a number of page types. This only confuses those who need to modify these files and needlessly increases the complexity of the system. § Match the file names and location within the file system with the URL as closely as possible. Make it easy to find files by using a standard naming convention and file system structure. Object Files

    Object files are the meat of any template system. They control what dynamic content is displayed and perform any number of modifications, calculations, or manipulations with the content before it is added to the template file contents for output. As a developer, you’ll find your biggest job when creating a template system is to create all the object files that will be required for the interface.

    Here are a few tips for when you are creating your object files: § Separate object files from other server-side script files. You may want to prefix object files with “i” or “o”, as in i_catalogitem.php. § Create a global object file that contains the common functional elements of the template system. The i_template.php example given earlier is an example of a global object file. § Don’t be tempted to stuff the global file with page-specific functionality. It will be an unnecessary drain on the scripting engine and will make the code harder to locate when making updates or fixes. § Each page type should have its own unique object file for the reasons described earlier. § Try not to hard-code text, links, or HTML in an object file. This only serves to undermine the original purpose of the template system. It is very easy to take shortcuts and hardcode HTML in your scripts, but don’t be tempted. § Don’t code yourself into a corner. Develop with flexibility in mind. You don’t want to have to rewrite the entire system after a simple design change. Connecting Objects and Templates

    So far you’ve learned how to create HTML template files and script object files. Now, all that is left is to make sure that the scripting engine uses the correct object file and then reads the correct template file. This is the simplest part of the process, but you do have a couple of options to consider.

    Here are four ways of connecting object and template files: § Querystring. It is a simple matter in most scripting languages to read a query string from a URL. For example, in the URL www.mydomain.com/ catalog.asp?Obj=i_item&Tpl=catalog_item, the catalog.asp file checks the values of Obj and Tpl and then determines that the correct object file is i_item.asp and the template file is catalog_item.tpl. Alternatively, the file names could be numerically coded such that Obj=14 tells the catalog.asp file to use i_item.asp as the object file. This option is simple and flexible, but can create ugly URLs that may be difficult to index with search engines. § Hard-coded. The template example shown earlier used this technique. The object file was called directly from the URL and the name of the template file was written directly into the code of the object file. This is a simple solution that doesn’t clutter the URL, but it can be difficult to maintain for complex sites (20+ page types). The projects in this book use this technique for simplicity. § Database. Developers of complex sites should probably consider using a database system to manage the object-template connections. A page key value is added to the beginning of each requested file. A global function is used to search the database based on this key value plus any number of additional criteria to determine the name and location of the object and template files. § Combination. Depending on your preferences and the needs of the project, it may be beneficial to combine two or more of these options. For example, you could hard-code the object file name but store the template file name in a database. Or you might include the page key value in the query string for retrieval from the database.

    Part II: Project 1: Creating Membership Forms

    Chapter List Chapter 7: Project 1 Preparation: Designing for Modularization Chapter 8: Creating the Forms Chapter 9: Completing the Membership Interface

    Project 1 Overview

    One of the most common uses of Web forms is to generate membership registrations. A Web site might restrict nonmembers’ access to specific areas or might provide registered users with additional perks such as contests, increased functionality, or technical support. Administrators of the Web site benefit by gathering information about the end users that would otherwise not be available. The data could be used to generate mailing lists, be sold to a third party, or be used as demographic information for advertisers.

    This project walks you through the creation of a dynamic membership interface. Many Web developers will have created similar forms already, but this project will go beyond the simple login and register pages that are sufficient to get the basic job done. You’ll discover the power of modular design, create a simple template system, and address a number of the most common usability issues.

    Chapter 7: Project 1 Preparation: Designing for Modularization

    Your first project is to develop an online membership interface. Before jumping into the code for these pages, you will need to come up with a development plan for the various elements needed in the project. In a large, professional project the development plan is often created by the IA or results from cooperation among many team members. Unless you’re reading this book as a team, however, you’ll have to create a development plan by yourself. This chapter walks you through that process for the first project. An important point to take away from this exercise is that you need to consider carefully how an interface will function before plunging into the coding process.

    Site Architecture

    The most important part of any development plan is the site architecture. The site architecture defines the visible and hidden pages of an interface and charts the expected or possible workflow of the end users. A site architecture outline should also include details about how the interactive elements of the interface will function and the technologies that will be used. It isn’t necessary to go into great detail; that can wait for the actual coding.

    As I’ve mentioned before, the site architecture is most often the purview of the interface architect, but not every project can afford to hire a specialist for this role. Often, in these situations, the task of creating the framework for a Web site falls to the Web developer. When creating a Web form–based interface, consider the types of pages that will be needed. Form Pages

    Form pages are the actual pages that will interact with the end user. These pages contain the form elements that will gather the necessary information from the user. For each form page you must define three parts. First, you must define the action page associated with the form page (action pages are discussed a little later). Second, you need to declare the information that the form will gather. Finally, you need to state how the form information will be validated and submitted. Note All form page validation is accomplished using client-side scripting. In this case that involves JavaScript. Action page validation is accomplished through server-side scripting, such as PHP.

    Here is a breakdown of the form pages you will be using for this project: § Login page. This page will allow users to access their membership information. The Login page will submit information to the check login action page. The Login page will ask for a username and password. For this project the username will be the e-mail address. The Login page will also pass a value representing the referring page, if any. This will be the page that the Check Login action page will display after processing the information. The username will be validated for an acceptable e-mail format and the password will be validated for length (3+ characters). A submit button will submit this form. § Registration page. This page allows new users to become members of the site. This page will be tied to the new user action page. The Registration page will require four pieces of information: the user’s name, e-mail address, password, and a Boolean check to verify that the user’s age is 17 or older. Additionally, the following information will be optional: postal code, gender, birthday, and newsletter preference. The name and password field will be validated for length (3+ characters), the e-mail address for format, the age check for a true value, and the birthday for format (mm-dd-yyyy). A submit button will submit this form. § Membership Update page. This page is intended to allow the user to update membership information. This page works exactly the same as the registration page except that the information is submitted to the Member Update User Action page. § Password Reminder page. This page provides a way for the user to retrieve a lost password. The user will submit their e-mail address to the Password Mailer Action page. The e-mail address will be validated for format. A submit button will submit this form.

    Action Pages Action pages are the server-side script pages that perform the actual functionality of the interface. Action pages are defined using the action attribute of the FORM tag. Action pages validate the submitted content and perform some functional process. This often involves appending, updating, or inserting the information into a database. Action pages are rarely visible to the end user. Often, after the information is processed, the action page will return the user to the form page or to a display page.

    Here are the action pages you will be creating for this project: § Check Login page. This page validates the e-mail value for format and the password value for length (3+ characters). The values are then compared to the database. If the values do not match, the login page is displayed with an error message. If the values are in the database, a session variable is set with the member key value and the referring page value is used to determine the page to display. If no referring page is given, the Membership Update page is displayed. § New User page. This page appends the gathered information to the membership tables in the database. The password value will be validated for length (3+ characters), the e-mail address for format, the age check for a true value, and the birthday for format (mm-dd-yyyy). Additionally, the name value will be verified to be unique and validated for length (3+ characters). If all the values are properly validated the account display page is shown. Otherwise the Registration page is displayed with the appropriate error messages. § Update User page. This page updates the membership tables in the database with the gathered information, based on the value of the member key value stored in the session variable. If the session variable does not exist (the user has not logged in) then the Login page is displayed. The password value will be validated for length (3+ characters), the e-mail address for format, the age check for a true value, and the birthday for format (mm-dd-yyyy). Additionally, the name value will be verified to be unique and validated for length (3+ characters). If all the values are properly validated, the Account Display page is shown. Otherwise the membership update page is displayed with the appropriate error messages. § Password Mailer page . This page validates the e-mail address value for format and checks the database for a match. If the value appears, an e- mail message containing the corresponding password is sent to that address and the password confirmation display page is shown. Otherwise the password reminder page is displayed with the appropriate error messages.

    Display Pages

    Display pages contain neither the form elements of a form page nor functionality of an action page. These pages complete the interface with the content, links, or messages that give information to the user.

    Here are the display pages you will need for this project: § Home page. This page contains the links to the login, membership update, and help pages. § Account Display page. This page contains the membership information available for the user. The membership key value stored in the session variable is used to determine the membership information to display. If the session variable does not exist (the user has not logged in) the Login page is displayed. A link to the membership update page is included. § Password Confirmation page. This page displays a message informing the user that an e-mail message containing their password has been sent to the e-mail address specified. § Help page. This page contains technical support and contact information for the interface. A link to the referring page is included.

    Database Architecture

    Creating a flexible yet stable database architecture is just as important as creating a quality site architecture. Typically a DBA is responsible for developing a database architecture. If you don’t have a DBA available, however, the following section should help you set up a simple SQL database. Later you will create the database tables used in the first project.

    Initial Setup with MySQL If a database has already been installed and configured, then you’re in luck; otherwise you’ll need to set one up yourself. MySQL is a free and simple database product that is available on a number of platforms. The following is a quick breakdown of how to configure MySQL for Windows 2000. If you’re using a different system or if you need further assistance, visit the online documentation available at http://www.mysql.com/doc/. If you’re using a different database system you can follow along—but you will need to consult your documentation for the specifics. § Download and install MySQL. The binaries and source code along with the installation files are available at http://www.mysql.com/. § Create a root password. As the default, MySQL allows root access without a password. For security, you’ll need to add one. Run mysql from the bin/ folder. Type the following: delete from user where host='localhost' and user='';

    Exit the program and reload the database permissions by typing: mysqladmin reload

    Then: mysqladmin -u password YOUR_NEW_PASSWORD § Create a global Web user profile. This profile will be used by the Web server to access the MySQL server. Later, you will assign permissions to this profile. Restart mysql using your new password like this: mysql —user=root —password=YOUR_NEW_PASSWORD

    Access the mysql database: use mysql;

    Then insert the new profile into the user table: insert into user (host, user, password) values ('YOUR_HOST_NAME ', 'GlobalWebUser', password('YOUR_WEB_USER_PASSWORD')); § Create the Web database . This database will hold all the data for your projects. In this example the database is called dwf. Type the following at the mysql prompt: create database dwf; § Grant access privileges to the Web user profile. MySQL uses the “db” table to assign access privileges to individual databases. At the mysql prompt, type the following: insert into db (host, user, db) values ('YOUR_HOST_NAME', 'GlobalWebUser', 'dwf');

    Then update the record to grant the user permission to modify the database: update db Set Select_priv='Y', Insert_priv="Y", Update_priv="Y", Delete_priv="Y", References_priv="Y", Index_priv="Y", Alter_priv="Y" Where user like 'GlobalWebUser';

    Activate the new privileges: flush privileges; Caution Any DBA worth his pocket protector will tell you that it is a big security risk to give global permissions to the Web user profile. Limit the Web user’s access to a specific database or a set of tables. Keep sensitive information in a separate database, or better yet, in a separate server altogether.

    Membership Table

    The membership and memberDemographics tables contain the user information that you will be gathering with your interface. It might be tempting to place all the information in one table rather than split it into two, but there is a very good reason not to: expandability. Imagine if you had all of the member information stored in a single table and the client suddenly wanted to start gathering marital status on the membership form. You would need to stop the interface, adjust the table (not always an easy task), and then restart the interface. By using a separate table for member demographics, you can make changes without updating the database table. Switch to the dwf database by entering the following at the mysql prompt: use dwf;

    The member table will be the core of your registration database. As such, it should be short, quick, and simple. For this project the membership table will need four columns. The names are up to you, but I’ve chosen memberID, memberName, eMail, and password. Enter the following text at the mysql prompt to create your membership table: create table membership (memberID int not null auto_increment primary key, memberName varchar(255) not null, eMail varchar(255) not null, password varchar(255) not null); Use membership as the table name. Make all of the fields varchar(255), except for the primary key field (memberID), which is an integer. You should also set memberID to auto increment, meaning that a new number (the next available integer) will automatically be assigned to the field when data is appended to the table. If your database won’t support auto increment you will need to add a few extra steps in any insert statements to create a new memberID.

    Before you can test the table you’ll need to insert some data into it. Enter the following at the mysql prompt: insert into membership (memberName, eMail, password) values ('default', '[email protected]', 'password'); Now you should be able to connect to the database and retrieve the information in the member table, shown in Figure 7.1. Below is a sample PHP script for accessing the table data. In your file you will need to change the host and password to match what you defined earlier.

    Figure 7.1: The contents of the membership table

    connect.php

    $connection = mysql_connect("RANSOM","GlobalWebUser","goober8") or die("Couldn't connect."); $db_name = "dwf"; $table_name = "membership"; $db = mysql_select_db($db_name, $connection) or die("Couldn't select database."); $sql = "SELECT * from $table_name"; $result = mysql_query($sql,$connection) or die("Couldn't execute query."); while ($row = mysql_fetch_array($result)) { echo $row['memberID'] . ", "; echo $row['memberName'] . ", "; echo $row['eMail'] . ", "; echo $row['password'] . "
    "; }

    ?> memberDemographics Table

    The memberDemographics table holds the optional data to be collected from the membership form. As the specific needs of the client regarding this type of information are likely to change over time, it is important to create a flexible table structure. The table needs four columns: memberDemoID, memberID, d_attribute, and d_value.

    The memberDemoID is an auto increment, primary key field. The memberID field allows you to connect any information in the memberDemographics table with the information in the membership table. The d_attribute field holds the name of an optional data type. For this project there will be four optional data fields: gender, birthday, postal code, and newsletter preference. The d_value field holds the corresponding value of the d_attribute field. For example, a record with a value of Gender in the d_attribute field would have either male or female as the value of the d_value field.

    In this way, each member could have any number of records in the member- Demographics table. As the members hip information grows, or more information is gathered about a specific member, the data can be added to the memberDemographics table without disrupting the interface functionality. To create the member-Demographics table using MySQL, type the following at the mysql prompt: create table memberDemographics (memberDemoID int not null auto_increment primary key, memberID int not null, d_attribute varchar(255) not null, d_value varchar(255) not null);

    To populate the table with some sample data, enter each of these commands, one at a time: insert into memberDemographics (memberID, d_attribute, d_value) values ('1', 'Gender', 'male'); insert into memberDemographics (memberID, d_attribute, d_value) values ('1', 'Postal Code', '95678'); insert into memberDemographics (memberID, d_attribute, d_value) values ('1', 'Birthday', '01-07-1976'); insert into memberDemographics (memberID, d_attribute, d_value) values ('1', 'Newsletter', '0'); This table structure may be more flexible than a single table, but it does require a little more work to display all the membership information for a user. The following example uses a pair of queries and a few while loops to append the membership data into an associative array. A foreach command then outputs the array to the page. The result is shown in Figure 7.2.

    Figure 7.2: The combined contents of the membership and memberDemographics tables

    connect-join.php

    $connection = mysql_connect("RANSOM","GlobalWebUser","goober8") or die("Couldn't connect."); $db_name = "dwf"; $table_name = "membership"; $db = mysql_select_db($db_name, $connection) or die("Couldn't select database."); $sql = "SELECT * from $table_name"; $result = mysql_query($sql,$connection) or die("Couldn't execute query.");

    while ($row = mysql_fetch_array($result, MYSQL_ASSOC)) { $arrResult = array(); $arrResult["memberID"] = $row['memberID']; $arrResult["memberName"] = $row['memberName']; $arrResult["eMail"] = $row['eMail']; $arrResult["password"] = $row['password'];

    $demo_table_name = "memberDemographics"; $demo_sql = "SELECT d_attribute, d_value from $demo_table_name WHERE memberID = \"" . $arrResult["memberID"] . "\""; $demo_result = mysql_query($demo_sql,$connection) or die("Couldn't execute query.");

    while ($demo_row = mysql_fetch_array($demo_result, MYSQL_ASSOC)) { $arrResult[$demo_row["d_attribute"]] = $demo_row["d_value"]; }

    foreach ($arrResult as $key => $value) { echo $key . ": " . $value . "
    "; } echo "
    "; unset($arrResult); }

    ?>

    File System Architecture The file system architecture outlines the directory structure and file conventions of the interface. For this project you will be creating a modular, template-based file system architecture. Chapter 6 explored the benefits of modular design, and now you’ll get to create your own template system.

    Your file system begins at the root folder. For Apache servers this is usually located at /usr/local/apache/htdocs, whereas for Microsoft servers this is located at C:\Inetpub\wwwroot. Most Web servers allow you to change the location of the root folder. As you create files and folders for your projects you will need to place them in the root folder.

    For the first project you will need to create three subfolders. The nonglobal object files will be loose in the root folder. Here is a description of each subfolder: § Images. This folder will hold the images for your interfaces. § Global. This folder holds all of the global files, such as the connection string file and the template file. § Templates. This is where you will place the HTML template files that you will be creating for your template system. Caution Be very careful about capitalization when it comes to files and folders. Some Web servers consider /images/car.jpg, Images/car.jpg, images/CAR.jpg, and iMaGes/CaR.jPg to be different files. Consult Chapter 2 for help with creating a file naming convention.

    Basic Methods The template system will begin with the i_template.php file. You will build off the basic principles you read about in Chapter 6. A big change from that chapter, however, is that this template will define a new PHP class rather than simply relying on global functions. Using a class system allows the various functions to share a common set of variables, called class properties. The global functions will become class methods. The primary template class will be called o_Template for simplicity. To create this class, create the i_template.php file with the following text: To declare the class properties use the var declaration. This template system will use three properties: § p_strOutputContents. This contains the text of the template file. § p_arrSections. This is an associative array that will hold the text of each template section. § p_arrPaired. This is a multidimensional array that will hold the content of each paired section. class o_Template { var $p_strOutputContents = ""; var $p_arrSections = array(); var $p_arrPaired = array(); …

    Now the class methods must be declared. In PHP, class methods are simply functions that appear inside a class declaration. This template has six basic methods: § m_LoadFile(). This method will gather the file contents of an HTML template file and send the data to the p_strOutputContents property. § m_AssignContent(). This method replaces content marker tags with the supplied text. § m_DefineSection(). Sections are repeatable areas of content. This method creates an entry in the p_arrSections array property for the named section. § m_AssignSection(). After a section is defined this method can be called to assign the content to the p_strOutputContents property. § m_AddElements(). This method adds the common elements, such as page title, navigation, and footer, to the p_strOutputContents property. § m_Output(). This property simply sends the value of the p_strOutputContents property to the page.: m_LoadFile() The first method, m_LoadFile(), takes two parameters: strFile and strTagName. The first is the name and location of the HTML template file to grab. The second parameter, strTagName, is set to null by default. If a value is supplied for strTagName, then the m_LoadFile() function will replace the named marker tag with the contents of the template file. To create the m_LoadFile() method, add the following code after the variable declarations: function m_LoadFile($strFile, $strTagName = "") { // the m_LoadFile() method commands go here. } The next three statements open the template file, add the contents to the strContents variable, and close the template file. $objFile = fopen($strFile, "r"); $strContents = fread($objFile, filesize($strFile)); fclose($objFile); Finally, an if statement checks whether the strTagName parameter has been passed to the function. If it has, then the p_strOutputContents property is modified with the contents of the template file replacing the marker tag that matches the name of the strTagName parameter. if (empty($strTagName)) { $this->p_strOutputContents = $strContents; } else { $this->p_strOutputContents = str_replace("{{" . $strTagName . "}}", $strContents, $this->p_strOutputContents); }; Here’s the complete text of the m_LoadFile() method: class o_Template { … function m_LoadFile($strFile, $strTagName = "") { $objFile = fopen($strFile, "r"); $strContents = fread($objFile, filesize($strFile)); fclose($objFile);

    if (empty($strTagName)) { $this->p_strOutputContents = $strContents; } else { $this->p_strOutputContents = str_replace("{{" . $strTagName . "}}", $strContents, $this->p_strOutputContents); };

    } … m_AssignContent() This method takes three parameters: strTagName, strReplace, and strContent. The first parameter, strTagName, is the name of the marker tag that will be affected. strReplace is the content that will replace this tag. The optional third parameter is the content to affect and is set to null by default. function m_AssignContent($strTagName, $strReplace, $strContent="") { // the m_AssignContent() method commands go here. } If the strContent parameter is not supplied, then the m_AssignContent() method adjusts the value of the p_strOutputContents property with the marker tag replaced with the value of strReplace. if (empty($strContent)) { $this->p_strOutputContents = str_replace("{{" . $strTagName . "}}", $strReplace, $this->p_strOutputContents); }; Otherwise, p_strOutputContents is left alone. Any occurrence of the named marker tag within strContent is replaced with the value of strReplace and output. else { return str_replace("{{" . $strTagName . "}}", $strReplace, $strContent); }; Here is the complete text of the m_AssignContent() method: class o_Template { … function m_AssignContent($strTagName, $strReplace, $strContent="") { if (empty($strContent)) { $this->p_strOutputContents = str_replace("{{" . $strTagName . "}}", $strReplace, $this->p_strOutputContents); };

    return str_replace("{{" . $strTagName . "}}", $strReplace, $strContent); } … m_DefineSection() This method takes a single parameter: strSectionName. This is the name of the section that the method should define. This method always returns the text of the named section. function m_DefineSection($strSectionName) { // the m_DefineSection() method commands go here. } The variables longSectionStart and longSectionEnd hold the numerical positions of the section marker tags within p_strOutputContents. $longSectionStart = strpos($this->p_strOutputContents, "{{section $strSectionName }}"); $longSectionEnd = strpos($this->p_strOutputContents, "{{/section $strSectionName }}"); An if command checks to be sure that the end section marker tag exists by comparing the longSectionEnd value. The length of the section is assigned to the longSectionLength variable and used to locate the text of the section from p_strOutputContents. if (($longSectionEnd > 0)) { $longSectionLength = $longSectionEnd - $longSectionStart + strlen("{{/section $strSectionName }}"); $strSection = substr($this->p_strOutputContents, $longSectionStart, $longSectionLength); … The section text is assigned to the p_arrSections array property. The name of the section is used as the index and the section text as the value. Last, the section text is returned. $this->p_arrSections[$strSectionName] = trim($strSection); return $this->p_arrSections[$strSectionName]; This is the full text of the m_DefineSection() method: class o_Template { …

    function m_DefineSection($strSectionName) { $longSectionStart = strpos($this->p_strOutputContents, "{{section $strSectionName }}"); $longSectionEnd = strpos($this->p_strOutputContents, "{{/section $strSectionName }}");

    if (($longSectionEnd > 0)) { $longSectionLength = $longSectionEnd - $longSectionStart + strlen("{{/section $strSectionName }}"); $strSection = substr($this->p_strOutputContents, $longSectionStart, $longSectionLength); $this->p_arrSections[$strSectionName] = trim($strSection); return $this->p_arrSections[$strSectionName]; };

    } … m_AssignSection() After defining a section you can replace the marker tags it contains by using the m_AssignContent() method. When the section is ready for output, call the m_AssignSection() method. This method takes three parameters: strSectionName, strReplace, and strContent. The name of the section marker tag is passed using strSectionName. The value of strReplace is the text that should replace the contents of the section. The optional parameter, strContent, is used with a set of nested sections to define the content of the daughter section. function m_AssignSection($strSectionName, $strReplace, $strContent="") { // the m_AssignSection() method commands go here. } The content of the named section is retrieved from the p_arrSections array property that was assigned in the m_DefineSection() method. The text is trimmed to remove any white space and then assigned to strSection. $strSection = trim($this->p_arrSections[$strSectionName]); An if statement is used to determine if the strContent parameter has been passed. If it has not (the default situation), then a nested if statement checks that the section text has been assigned to strSection. if (empty($strContent)) { if (!empty($strSection)) { … Next, the contents of the p_strOutputContents property are modified. If the text of the section is located within the current value of the p_strOutputContents property, it is replaced with the value of the strReplace parameter, a carriage return, and the text of the section (including the section marker tags). Because the section text is always re- added to the p_strOutputContents property, this method can be called multiple times. Each time the method is called, the section tags are replaced and another set of identical section marker tags is added after the replaced text. $this->p_strOutputContents = str_replace(trim($strSection), $strReplace . "\r\n" . $strSection, $this->p_strOutputContents); If the strContents parameter is passed to the m_AssignSection() method, the p_strOutputContents property is not affected. Instead, the strContent parameter is used as the base for locating the section text. The same process of re-adding the strSection variable is applied and the modified strContent value is returned. else { if (!empty($strSection)) { return str_replace($strSection, $strReplace . "\r\n" . $strSection, $strContent); };

    }; This is what the finished m_AssignSection() method should look like: class o_Template { …

    function m_AssignSection($strSectionName, $strReplace, $strContent="") { $strSection = trim($this->p_arrSections[$strSectionName]);

    if (empty($strContent)) { if (!empty($strSection)) { $this->p_strOutputContents = str_replace(trim($strSection), $strReplace . "\r\n" . $strSection, $this->p_strOutputContents); };

    } else { if (!empty($strSection)) { return str_replace($strSection, $strReplace . "\r\n" . $strSection, $strContent); };

    };

    } … m_AddElements() This simple method takes a single parameter: strPageTitle. This parameter will be used as the page title. function m_AddElements($strPageTitle) { // the m_AddElements () method commands go here. } First, the navigation and footer templates are added to the p_strOutputContents through the m_LoadFile() method. The strTagName parameters are used so that the original p_strOutputContents parameter is not overwritten. $this->m_LoadFile("templates/navigation.tpl","NAVIGATION"); $this->m_LoadFile("templates/footer.tpl","FOOTER"); Next, the TITLE marker tag is replaced with the strPageTitle parameter using the m_AssignContent() method. $this->m_AssignContent("TITLE","DWF - " . $strPageTitle); Here’s the complete m_AddElements() method: class o_Template { … function m_AddElements($strPageTitle) { $this->m_LoadFile("templates/navigation.tpl","NAVIGATION"); $this->m_LoadFile("templates/footer.tpl","FOOTER"); $this->m_AssignContent("TITLE","DWF - " . $strPageTitle); } … m_Output() The last of the basic methods is m_Output(). This method takes no parameters; it simply sends the value of the p_strOutputContents parameter to the page. As you could output the value of this property without defining a new method, it may seem that the whole thing is unnecessary. It is included for completeness. In the future you may want to perform additional processes with the data before it is output, and the m_Output() method is where these statements would appear. class o_Template { … function m_Output() { echo $this->p_strOutputContents; } …

    Advanced Methods The basic methods can accomplish most of the needed functionality for the template system. However, some advanced methods take up where the others leave off. Your template system will have three advanced methods: m_DefinePair(), m_AssignPair(), and m_PrepTemplate(). The first two declare and modify paired sections of the template. The m_PrepTemplate() method is used to remove the residual marker tags and section text from the p_strOutputContents before the m_Output() method is called. m_DefinePair() The m_DefinePair() method appends the contents of a paired section to the p_arrPaired array property. Functionally, it is similar to m_DefineSection(), except that each process must function twice. Handling paired sections requires a multidimensional array. Each paired section creates an associative array that is further divided into a numerical array that holds each section’s content. This method takes only two parameters: strPairName and boolUseBoth. The strPairName value represents the name of the paired section. The Boolean boolUseBoth parameter determines whether both sections of the pair or just the first will be modified. function m_DefinePair($strPairName, $boolUseBoth=false) { // the m_DefinePair() method commands go here. }

    First, four variables are assigned—two for each of the starting points, two for each of the ending points. $longFirstStart = strpos($this->p_strOutputContents, "{{paired $strPairName }}"); $longFirstEnd = strpos($this->p_strOutputContents, "{{/paired $strPairName }}"); $longSecondStart = strpos($this->p_strOutputContents, "{{unpaired $strPairName }}"); $longSecondEnd = strpos($this->p_strOutputContents, "{{/unpaired $strPairName }}"); Next, an if statement verifies that the first section exists. The length of each section is then calculated. if (($longFirstEnd > 0)) { $longFirstLength = $longFirstEnd - $longFirstStart + strlen("{{/paired $strPairName }}"); $longSecondLength = $longSecondEnd - $longSecondStart + strlen("{{/unpaired $strPairName }}"); … These lengths are then used to grab each section’s text from the p_strOutputContents property. $strFirstPairContents = substr($this->p_strOutputContents, $longFirstStart, $longFirstLength); $strSecondPairContents = substr($this->p_strOutputContents, $longSecondStart, $longSecondLength); Each of the two section content values are then assigned to the second dimension of the p_arrPaired array property. Each p_arrPaired element is indexed by the name passed into the m_DefinePair() method by strPairName. The first section of the pair is assigned to index 0, the second to index 1 only if the boolUseBoth parameter has been passed. $this->p_arrPaired[$strPairName][0] = trim($strFirstPairContents); if ($boolUseBoth) { $this->p_arrPaired[$strPairName][1] = trim($strSecondPairContents);

    }; Finally, the text of the first section is returned. Only one section is returned; as the first section is more likely to be needed for m_AssignContent() methods, it is the one chosen. return $this->p_arrPaired[$strPairName][0]; Here is the complete text of the m_DefinePair() method: class o_Template { …

    function m_DefinePair($strPairName, $boolUseBoth=false) { $longFirstStart = strpos($this->p_strOutputContents, "{{paired $strPairName }}"); $longFirstEnd = strpos($this->p_strOutputContents, "{{/paired $strPairName }}"); $longSecondStart = strpos($this->p_strOutputContents, "{{unpaired $strPairName }}"); $longSecondEnd = strpos($this->p_strOutputContents, "{{/unpaired $strPairName }}");

    if (($longFirstEnd > 0)) { $longFirstLength = $longFirstEnd - $longFirstStart + strlen("{{/paired $strPairName }}"); $longSecondLength = $longSecondEnd - $longSecondStart + strlen("{{/unpaired $strPairName }}"); $strFirstPairContents = substr($this->p_strOutputContents, $longFirstStart, $longFirstLength); $strSecondPairContents = substr($this->p_strOutputContents, $longSecondStart, $longSecondLength); $this->p_arrPaired[$strPairName][0] = trim($strFirstPairContents); if ($boolUseBoth) { $this->p_arrPaired[$strPairName][1] = trim($strSecondPairContents);}; return $this->p_arrPaired[$strPairName][0]; };

    } … m_AssignPair() This method cycles through the p_arrPaired array property and collects the paired sections’ text. Once the content is gathered, this function works similarly to the m_AssignSection() method. The m_AssignPair() method takes three parameters, though the last one is optional. The strPairName parameter defines the name of the paired sections that will be affected. The next two parameters, strFirstReplace and strSecondReplace, hold the replacement text for the first and second part of the paired sections, respectively. function m_AssignPair($strPairName, $strFirstReplace, $strSecondReplace="") { // the m_AssignPair() method commands go here. } First, each of the left half of each section is retrieved from the p_arrPaired array property using the name of the pair as passed by strPairName. $strFirstPair = $this->p_arrPaired[$strPairName][0]; Next, an if statement ensures that the first section text has been assigned to strFirstPair. if (!empty($strFirstPair)) { … The first section value is replaced in the p_strOutputContents property in a similar manner to the m_AssignSection() method. Any occurrences of the first section in the property are replaced with the srtFirstReplace value and a copy of the section. $this->p_strOutputContents = str_replace($strFirstPair, $strFirstReplace . "\r\n" . $strFirstPair, $this->p_strOutputContents); Finally, another if statement determines if the strSecondReplace parameter has been passed. If it has, then the text of the right section is assigned to a variable and replaced in the p_strOutputContents property in the same way as the first. if (!empty($strSecondReplace)) { $strSecondPair = $this->p_arrPaired[$strPairName][1]; $this->p_strOutputContents = str_replace($strSecondPair, $strSecondReplace . "\r\n" . $strSecondPair, $this->p_strOutputContents); }; Here is the complete m_AssignPair() method: class o_Template { … function m_AssignPair($strPairName, $strFirstReplace, $strSecondReplace="") { $strFirstPair = $this->p_arrPaired[$strPairName][0];

    if (!empty($strFirstPair)) { $this->p_strOutputContents = str_replace($strFirstPair, $strFirstReplace . "\r\n" . $strFirstPair, $this->p_strOutputContents);

    if (!empty($strSecondReplace)) { $strSecondPair = $this->p_arrPaired[$strPairName][1]; $this->p_strOutputContents = str_replace($strSecondPair, $strSecondReplace . "\r\n" . $strSecondPair, $this->p_strOutputContents); };

    };

    } … m_PrepTemplate() The m_PrepTemplate() is used to remove any of the sections and section marker tags that have not been replaced by an assign method. This method only takes one Boolean parameter, boolDeleteSection. If a value of false is passed for this parameter then the m_PrepTemplate() method does not remove the sections and markers. function m_PrepTemplate($boolDeleteSection = true) { \\ the m_PrepTemplate() method commands go here } First, an if statement ensures that a value of false has not been passed in the boolDeleteSection parameter. if ($boolDeleteSection) { … Next the p_arrSections property is assigned to the arrSections variable. A while loop, a list command, and an each function are used to cycle through the contents of this new variable array. $arrSections = $this->p_arrSections; while (list($strSectionName, $strSectionContent) = each($arrSections)) { … Any of the extra section texts that were added as a result of the m_AssignSection() method are removed. Then the section marker tags are eliminated. $this->p_strOutputContents = str_replace(trim($strSectionContent), "", $this- >p_strOutputContents); $this->p_strOutputContents = str_replace("{{section " . $strSectionName . " }}", "", $this- >p_strOutputContents); $this->p_strOutputContents = str_replace("{{/section " . $strSectionName . " }}", "", $this- >p_strOutputContents); Next, the p_arrPaired property is assigned to the arrPair variable. Again, a while loop, a list command, and an each function are used to cycle through the contents of this new variable array. $arrPair = $this->p_arrPaired; while (list($strPairName) = each($arrPair)) { Another while loop is used to rotate through each element in the second dimension of the p_arrPaired array property. while (list($intArrayVal, $strPairContent) = each($arrPair["$strPairName"])) { … Now any extra paired sections created by the m_AssignPair() method are removed. $this->p_strOutputContents = str_replace(trim($strPairContent), "", $this- >p_strOutputContents); Then the paired section marker tags are removed from the p_strOutputContents property. $this->p_strOutputContents = str_replace("{{paired " . $strPairName . " }}", "", $this- >p_strOutputContents); $this->p_strOutputContents = str_replace("{{/paired " . $strPairName . " }}", "", $this- >p_strOutputContents); $this->p_strOutputContents = str_replace("{{unpaired " . $strPairName . " }}", "", $this- >p_strOutputContents); $this->p_strOutputContents = str_replace("{{/unpaired " . $strPairName . " }}", "", $this- >p_strOutputContents); Finally, the contents of the p_strOutputContents property are returned. return $this->p_strOutputContents; This is the complete m_PrepTemplate() method: class o_Template { … function m_PrepTemplate($boolDeleteSection = true) { if ($boolDeleteSection) { $arrSections = $this->p_arrSections;

    while (list($strSectionName, $strSectionContent) = each($arrSections)) { $this->p_strOutputContents = str_replace(trim($strSectionContent), "", $this->p_strOutputContents); $this->p_strOutputContents = str_replace("{{section " . $strSectionName . " }}", "", $this->p_strOutputContents); $this->p_strOutputContents = str_replace("{{/section " . $strSectionName . " }}", "", $this->p_strOutputContents); };

    $arrPair = $this->p_arrPaired;

    while (list($strPairName) = each($arrPair)) { while (list($intArrayVal, $strPairContent) = each($arrPair["$strPairName"])) { $this->p_strOutputContents = str_replace(trim($strPairContent), "", $this->p_strOutputContents); }; $this->p_strOutputContents = str_replace("{{paired " . $strPairName . " }}", "", $this->p_strOutputContents); $this->p_strOutputContents = str_replace("{{/paired " . $strPairName . " }}", "", $this->p_strOutputContents); $this->p_strOutputContents = str_replace("{{unpaired " . $strPairName . " }}", "", $this->p_strOutputContents); $this->p_strOutputContents = str_replace("{{/unpaired " . $strPairName . " }}", "", $this->p_strOutputContents); };

    };

    return $this->p_strOutputContents; } …

    Template System Overview Now that you have created the o_Template class, you need to understand how to make it work. By combining the class methods with loops and conditional statements you can reproduce all of the different types of dynamic that were discussed in Chapter 6. As you put the o_Template class through its paces you might get a little lost at first. Just hang in there and keep referring back to the method descriptions from earlier. When you’ve got it mastered you’ll have a very powerful tool that can be applied to almost any Web application. Using the o_Template Class Your first step when using the template system is to instantiate the class. This assigns a variable to the class and allows you to access all the class properties and methods. Additionally, instantiation causes a special method, called a constructor, to execute. In PHP constructors are given the same name as the class, so that a constructor for a class called o_CookieHandler would be o_CookieHandler(). Currently, there is no constructor for the o_Template class.

    To instantiate a class in PHP use the new keyword: var objTemplate = new o_Template;

    instantiate To create an instance. An instance is a particular recognition of a construct or template such as a class or object. In this case instantiation involves creating an instance by defining one particular variable of a class; that is, giving a name to it.

    A special “pointer” operator, ->, is used to reference class properties and methods. For example, to reference the m_LoadFile() method you would use var objTemplate = new o_Template; objTemplate->m_LoadFile("i_template"); If you reference a property or any method that returns output, you can assign the value to a variable using the = assignment operator. $strCurrentOutput = objTemplate->p_OutputContents; The i_template.php file contains the complete text of the o_Template class. i_template.php

    function m_LoadFile($strFile, $strTagName = "") { $objFile = fopen($strFile, "r"); $strContents = fread($objFile, filesize($strFile)); fclose($objFile);

    if (empty($strTagName)) { $this->p_strOutputContents = $strContents; } else { $this->p_strOutputContents = str_replace("{{" . $strTagName . "}}", $strContents, $this->p_strOutputContents); };

    }

    function m_AssignContent($strTagName, $strReplace, $strContent="") { if (empty($strContent)) { $this->p_strOutputContents = str_replace("{{" . $strTagName . "}}", $strReplace, $this->p_strOutputContents); } else { return str_replace("{{" . $strTagName . "}}", $strReplace, $strContent); }; }

    function m_DefineSection($strSectionName) { $longSectionStart = strpos($this->p_strOutputContents, "{{section $strSectionName }}"); $longSectionEnd = strpos($this->p_strOutputContents, "{{/section $strSectionName }}");

    if (($longSectionEnd > 0)) { $longSectionLength = $longSectionEnd - $longSectionStart + strlen("{{/section $strSectionName }}"); $strSection = substr($this->p_strOutputContents, $longSectionStart, $longSectionLength); $this->p_arrSections[$strSectionName] = trim($strSection); return $this->p_arrSections[$strSectionName]; };

    }

    function m_AssignSection($strSectionName, $strReplace, $strContent="") { $strSection = trim($this->p_arrSections[$strSectionName]);

    if (empty($strContent)) { if (!empty($strSection)) { $this->p_strOutputContents = str_replace(trim($strSection), $strReplace . "\r\n" . $strSection, $this->p_strOutputContents); };

    } else { if (!empty($strSection)) { return str_replace($strSection, $strReplace . "\r\n" . $strSection, $strContent); };

    };

    }

    function m_DefinePair($strPairName) { $longFirstStart = strpos($this->p_strOutputContents, "{{paired $strPairName }}"); $longFirstEnd = strpos($this->p_strOutputContents, "{{/paired $strPairName }}"); $longSecondStart = strpos($this->p_strOutputContents, "{{unpaired $strPairName }}"); $longSecondEnd = strpos($this->p_strOutputContents, "{{/unpaired $strPairName }}");

    if (($longFirstEnd > 0)) { $longFirstLength = $longFirstEnd - $longFirstStart + strlen("{{/paired $strPairName }}"); $longSecondLength = $longSecondEnd - $longSecondStart + strlen("{{/unpaired $strPairName }}"); $strFirstPairContents = substr($this->p_strOutputContents, $longFirstStart, $longFirstLength); $strSecondPairContents = substr($this->p_strOutputContents, $longSecondStart, $longSecondLength); $this->p_arrPaired[$strPairName][0] = trim($strFirstPairContents); $this->p_arrPaired[$strPairName][1] = trim($strSecondPairContents); return $this->p_arrPaired[$strPairName][0]; };

    }

    function m_AssignPair($strPairName, $strFirstReplace, $strSecondReplace="", $strContent="") { $strFirstPair = $this->p_arrPaired[$strPairName][0]; $strSecondPair = $this->p_arrPaired[$strPairName][1];

    if (empty($strContent)) { if (!empty($strFirstPair)) { $this->p_strOutputContents = str_replace($strFirstPair, $strFirstReplace . "\r\n" . $strFirstPair, $this->p_strOutputContents);

    if (!empty($strSecondReplace)) { $this->p_strOutputContents = str_replace($strSecondPair, $strSecondReplace . "\r\n" . $strSecondPair, $this->p_strOutputContents); };

    };

    }

    else { if (!empty($strFirstPair)) { return str_replace($strFirstPair, $strReplace . "\r\n" . $strFirstPair, $strContent); if (!empty($strSecondReplace)) { return str_replace($strSecondPair, $strSecondReplace . "\r\n" . $strSecondPair, $this->p_strOutputContents); };

    };

    };

    }

    function m_AddElements($strPageTitle) { $this->m_LoadFile("templates/navigation.tpl","NAVIGATION"); $this->m_LoadFile("templates/footer.tpl","FOOTER"); $this->m_AssignContent("TITLE","DWF - " . $strPageTitle); }

    function m_PrepTemplate($boolDeleteSection = true) { if ($boolDeleteSection) { $arrSections = $this->p_arrSections;

    while (list($strSectionName, $strSectionContent) = each($arrSections)) { $this->p_strOutputContents = str_replace(trim($strSectionContent), "", $this->p_strOutputContents); $this->p_strOutputContents = str_replace("{{section " . $strSectionName . " }}", "", $this->p_strOutputContents); $this->p_strOutputContents = str_replace("{{/section " . $strSectionName . " }}", "", $this->p_strOutputContents); };

    $arrPair = $this->p_arrPaired;

    while (list($strPairName) = each($arrPair)) { $this->p_strOutputContents = str_replace("{{paired " . $strPairName . " }}", "", $this->p_strOutputContents); $this->p_strOutputContents = str_replace("{{/paired " . $strPairName . " }}", "", $this->p_strOutputContents); $this->p_strOutputContents = str_replace("{{unpaired " . $strPairName . " }}", "", $this->p_strOutputContents); $this->p_strOutputContents = str_replace("{{/unpaired " . $strPairName . " }}", "", $this->p_strOutputContents);

    while (list($intArrayVal, $strPairContent) = each($arrPair["$strPairName"])) { $this->p_strOutputContents = str_replace(trim($strPairContent), "", $this->p_strOutputContents); }; };

    };

    return $this->p_strOutputContents; }

    function m_Output() { echo $this->p_strOutputContents; } } ?> Content and Section Types In Chapter 6 you reviewed the different types of dynamic content that the template system would be able to handle. Here are some examples of how those contents types are handled using the o_Template class. Simple Content The HTML template file includes a marker tag for the main content of the page, which looks like this: {{BODY_TEXT}}. To replace this tag, first you would declare a variable and assign it a value of the text. This text could come from a separate file, the database, or a global variable. The m_LoadFile() method is used to define the p_strOutputContents property. Finally, the text is assigned using the m_AssignContent() method. $strText = "This is the main body text"; $objTemplate = new o_Template; $objTemplate->m_LoadFile("templates/template.tpl"); $objTemplate->m_AssignContent("BODY_TEXT",$srtTitle); Simple Sections The template.tpl file contains the section marker tag set, {{section Image }}{{/section Image }}. To replace this section with dynamic content, first define the section by assigning a variable to the m_DefineSection() method. This will give you the strReplace parameter that you can then pass to the m_AssignSection() method. $objTemplate = new o_Template; $objTemplate->m_LoadFile("templates/template.tpl"); $strImageSection = $objTemplate->m_DefineSection("Image"); $objTemplate->m_AssignSection("Image", $strImageSection); Conditional Content You might have noticed that the Image section contained two content marker tags: {{IMAGE}} and {{IMAGE_NAME}}. You can make this content appear only if the parent section appears by executing the m_AssignContent() method on the content of the section as defined by the m_DefineSection() method. $objTemplate = new o_Template; $objTemplate->m_LoadFile("templates/template.tpl"); $strImageSection = $objTemplate->m_DefineSection("Image"); $strImageInfo = $objTemplate->m_AssignContent("IMAGE", "images/logo.gif", $strImageSection); $strImageInfo = $objTemplate->m_AssignContent("IMAGE_NAME", "Our company logo", $strImageInfo); $objTemplate->m_AssignSection("Image", $strImageInfo); Conditional Sections All sections are conditional in that you must change the content of the section or the m_PrepTemplate() method will remove it. You can also make a section appear only if specific conditions are met. The most common way to accomplish this is to use an if conditional statement on the m_AssignSection() method. $objTemplate = new o_Template; $objTemplate->m_LoadFile("templates/template.tpl"); $strImageSection = $objTemplate->m_DefineSection("Image"); $strImageInfo = $objTemplate->m_AssignContent("IMAGE", "images/logo.gif", $strImageSection); if ($strImageInfo=="images/logo.gif") { $objTemplate->m_AssignSection("Image", $strImageInfo); };

    Repeated Sections To cause a section to repeat, call the m_AssignSection() method multiple times. Each execution of this method will produce a copy of the section. $objTemplate = new o_Template; $objTemplate->m_LoadFile("templates/template.tpl"); $strImageSection = $objTemplate->m_DefineSection("Image"); $strImageInfo = $objTemplate->m_AssignContent("IMAGE", "images/logo.gif", $strImageSection); $objTemplate->m_AssignSection("Image", $strImageInfo); $objTemplate->m_AssignSection("Image", $strImageInfo); $objTemplate->m_AssignSection("Image", $strImageInfo);

    Nested Sections The most important thing to keep in mind when nesting sections is the order of operations. First, define each section. Then assign any content of the parent section and daughter section, in that order. Next, assign the daughter section using the optional strContent parameter of the m_AssignSection() method. This will return a variable with the updated daughter section contents. Use this variable as the strReplace parameter for assigning the parent section. $strGroupSection = $objTemplate->m_DefineSection("Group"); $strItemSection = $objTemplate->m_DefineSection("Item"); $strGroup = $objTemplate->m_AssignContent("GROUP_NAME", "First Group", $strGroupSection); $strItem = $objTemplate->m_AssignContent("ITEM_NAME", "First Item, ", $strItemSection); $strItemText = $objTemplate->m_AssignSection("Item", $strItem, $strItemSection); $objTemplate->m_AssignSection("Group", $strItemText);

    It is also possible to repeat nested sections. $strGroupS ection = $objTemplate->m_DefineSection("Group"); $strItemSection = $objTemplate->m_DefineSection("Item"); $strGroup = $objTemplate->m_AssignContent("GROUP_NAME", "First Group", $strGroupSection); $strItem = $objTemplate->m_AssignContent("ITEM_NAME", "First Item, ", $strItemSection); $strItemText = $objTemplate->m_AssignSection("Item", $strItem, $strItemSection); $strItem = $objTemplate->m_AssignContent("ITEM_NAME", "Second Item", $strItemText); $strItemText = $objTemplate->m_AssignSection("Item", $strItem, $strGroup); $objTemplate->m_AssignSection("Group", $strItemText); $strGroup = $objTemplate->m_AssignContent("GROUP_NAME", "Second Group", $strGroupSection); $strItem = $objTemplate->m_AssignContent("ITEM_NAME", "First Item, ", $strItemSection); $strItemText = $objTemplate->m_AssignSection("Item", $strItem, $strItemSection); $strItem = $objTemplate->m_AssignContent("ITEM_NAME", "Second Item", $strItemText); $strItemText = $objTemplate->m_AssignSection("Item", $strItem, $strGroup); $objTemplate->m_AssignSection("Group", $strItemText);

    Paired Sections There are two ways of using the paired section ability of the o_Template class. By default, only the left section is changed and the right is untouched. Alternatively, both sections can be modified. To create a paired section that only modifies the left section, leave out the boolUseBoth and strSecondReplace parameters. $strLinkPair = $objTemplate->m_DefinePair("Link"); $strLink = $objTemplate->m_AssignContent("LINK","click.html", $strLinkPair); $objTemplate->m_AssignPair("Link", $strLink); To change both parts of a paired section, add these parameters to the method calls and reference the p_arrPaired array property in the m_AssignContent() method. $strLinkPair = $objTemplate->m_DefinePair("Link", true); $strLink = $objTemplate->m_AssignContent("LINK","click.html", $strLinkPair); $strLinkRight = $objTemplate->m_AssignContent("CAPTION","The link caption", $objTemplate->p_arrPaired["Link"][1]); $objTemplate->m_AssignPair("Link", $strLink, $strLinkRight;

    The Chapter 7 folder contains a copy of the template system and a file called test.php that demonstrates how each of these processes can be applied. test.php

    $objTemplate = new o_Template; $objTemplate->m_LoadFile("templates/template.tpl");

    $objTemplate->m_AddElements("Tester Page");

    $objTemplate->m_AssignContent("BODY_TEXT","This is the body text");

    $strImageSection = $objTemplate->m_DefineSection("Image"); $strImageInfo = $objTemplate- >m_AssignContent("IMAGE","images/log.gif",$strImageSection); $strImageInfo = $objTemplate->m_AssignContent("IMAGE_NAME","Our corporate logo",$strImageInfo); $objTemplate->m_AssignSection("Image", $strImageInfo); $objTemplate->m_AssignSection("Image", $strImageInfo);

    $strGroupSection = $objTemplate->m_DefineSection("Group"); $strItemSection = $objTemplate->m_DefineSection("Item"); $strGroup = $objTemplate->m_AssignContent("GROUP_NAME", "First Group", $strGroupSection); $strItem = $objTemplate->m_AssignContent("ITEM_NAME", "First Item, ", $strItemSection); $strItemText = $objTemplate->m_AssignSection("Item", $strItem, $strItemSection); $strItem = $objTemplate->m_AssignContent("ITEM_NAME", "Second Item", $strItemText); $strItemText = $objTemplate->m_AssignSection("Item", $strItem, $strGroup); $objTemplate->m_AssignSection("Group", $strItemText); $strGroup = $objTemplate->m_AssignContent("GROUP_NAME", "Second Group", $strGroupSection); $strItem = $objTemplate->m_AssignContent("ITEM_NAME", "First Item, ", $strItemSection); $strItemText = $objTemplate->m_AssignSection("Item", $strItem, $strItemSection); $strItem = $objTemplate->m_AssignContent("ITEM_NAME", "Second Item", $strItemText); $strItemText = $objTemplate->m_AssignSection("Item", $strItem, $strGroup); $objTemplate->m_AssignSection("Group", $strItemText);

    $strLinkPair = $objTemplate->m_DefinePair("Link", true); $strLink = $objTemplate->m_AssignContent("LINK","click.html", $strLinkPair); $strLinkRight = $objTemplate->m_AssignContent("CAPTION","The link caption", $objTemplate->p_arrPaired["Link"][1]); $objTemplate->m_AssignPair("Link", $strLink, $strLinkRight);

    $objTemplate->m_PrepTemplate(); $objTemplate->m_Output();

    ?>

    Chapter 8: Creating the Forms

    Now that you have created a development plan and a template infrastructure you can begin creating the actual pages. As you create the pages, try to keep in mind how you might be able to modularize your code, making it available throughout the interface. Part of this modularization is to create global files for the form validation, database connection, and JavaScript functions.

    Validation When it comes to creating a Web-based form interface, the first priority is always to ensure that the data submitted by the user is acceptable. Unacceptable data renders any work done on the rest of the interface useless. Chapter 2 discussed the principles of form validation in depth; in the first part of this project, you will put this knowledge to practical use. The validation processes created here will be used throughout the remaining projects.

    Client-Side Client -side validation has less to do with entering the data into a database than with giving the users feedback in a timely fashion. To this end, you will create a JavaScript object called o_ValidateClient() that will allow you to quickly and simply validate the different types of data required by your interface.

    Here is a rundown on the many types of client-side data validation that could be needed in a typical form interface. Each validation process takes a value entered into a form element and performs a process to determine if the data meets a set of criteria. § Does a value exist? Checks whether any data has been entered into this form element. In other words, is the value of the form element null or “”? § Is a value equal to x? Compares the value against a given and determines if they resolve to the same value. § Is a value an integer? Verifies that the data is a positive or negative whole number and contains only numerical characters. For example, 108, -6, and 0 are integers; 12.0, three, and 10*e^25 are not integer values. § Does a value contain unwanted characters? Checks whether the value contains any of a defined set of unacceptable characters or strings. Unwanted characters could include <, >, ~, |, or %. § Is a date properly formatted? Determines if a value meets the date formatting standards required by the form. § Is an e-mail address properly formatted? Checks that a value meets the requirements for an acceptable e-mail address. This just verifies the format of the value; it does not validate the actual existence of the e-mail address. § Is a U.S. Zip code properly formatted? Validates a value against the standards for a proper U.S. Zip code. § Is the length of a value within parameters x and y? Determines the number of characters in the value and compares it against the defined minimum and maximum values inclusive. § Is a number between x and y? Checks that the value is a number and determines if it lies between the defined minimum and maximum values inclusive. § Is a date between dates x and y? Takes a confirmed date value and determines whether it is before the end date and after the start date.

    Two additional functions are often useful when validating forms: § Remove the extra white space. This process eliminates spaces, tabs, and carriage returns from the beginning and ending of a value. § Send a warning to the user. This function sends a message to the browser that alerts the user that an unacceptable value has been detected. To create custom JavaScript objects such as o_ValidateClient(), define the function and name the methods using the this keyword: function o_ValidateClient() { this.m_Exists = m_Exists; this.m_isEquality = m_isEquality; this.m_isInteger = m_isInteger; this.m_isSpecChar = m_isSpecChar; this.m_isFormatDate = m_isFormatDate; this.m_isFormatEmail = m_isFormatEmail; this.m_isFormatZipUS = m_isFormatZipUS; this.m_isRangeLength = m_isRangeLength; this.m_isRangeNumber = m_isRangeNumber; this.m_isRangeDate = m_isRangeDate; this.m_modTrim = m_modTrim; this.m_Alert_x = m_Alert_x; } …

    Each method appears as a named function, like this: function m_modTrim() { //the m_modTrim() commands go here. } m_Exists() This method takes a single parameter: varVariant. A value of true is returned if an acceptable value exists for the parameter, otherwise a value of false is returned. function m_Exists(varVariant) { … Any of the following criteria will result in a value of false:

    The value contains only a blank value. varVariant == "" The varVariant value has not been defined. varVariant == null

    The value is set to 0. varVariant == 0

    The value is false. varVariant == false A separate if statement could be created for each condition, or they could be combined into one using parentheses and the or logical operator (||). function m_Exists(varVariant) { if ((varVariant == "") || (varVariant == null) || (varVariant == 0) || (varVariant == false)) { return false; } else { return true; } } m_isEquality() This method compares two parameters: varFirst and varSecond. If they are the same, a value of true is returned, otherwise m_isEquality() returns a value of false. function m_isEquality(varFirst,varSecond) { … Each parameter is calculated using the eval() function, and a simple comparison is made between the two resulting values. eval(varFirst) == eval(varSecond) A single if statement completes the method. function m_isEquality(varFirst,varSecond) { if (eval(varFirst) == eval(varSecond)) { return true; } else { return false; } } m_isInteger() Unfortunately, JavaScript doesn’t contain a built-in function to evaluate whether a value is an integer. The m_isInteger() method is a solution to this deficiency. The parameter, varVariant, will return a value of true only if it is an integer. function m_isInteger(varVariant) { …

    First, a starting point is defined. var intStart = 0; Then, the varVariant parameter is converted to a string. The charAt() function used in the next step will only work on a string value. varVariant = varVariant + ""; An if statement checks the first character of the value. If it is determined to be a negative sign (-), then the starting point is moved over to the right by one character. if (varVariant.charAt(0) == "-") { intStart++; } A for loop runs through each character of the varVariant parameter. Each character is assigned to the intCurrentPos variable. for (l = intStart; l < varVariant.length; l++) { var intCurrentPos = varVariant.charAt(l); … A comparison is made to determine if the character is between 0 and 9 inclusive. The not operator (!) is used to reverse the comparison. Therefore, alphabetical and other nonnumeric characters will cause this comparison to resolve as true and the parent if statement returns a value of false. if (!((intCurrentPos >= "0") && (intCurrentPos <= "9"))) { return false; }

    Here’s the entire method. function m_isInteger(varVariant) { var intStart = 0; varVariant = varVariant + "";

    if (varVariant.charAt(0) == "-") { intStart++; }

    for (l = intStart; l < varVariant.length; l++) { var intCurrentPos = varVariant.charAt(l);

    if (!((intCurrentPos >= "0") && (intCurrentPos <= "9"))) { return false; } }

    return true; } m_isSpecChar() The m_isSpecChar() is designed to determine whether the parameter strText contains a specific substring. The second parameter, strBadString, provides the text of the string for which to search. If strBadString is not supplied, then a predefined set of values is used instead. function m_isSpecChar(strText, strBadString) { … First the strText parameter is converted to a string and a new array is initialized. This new array will hold the search values. strText = strText + ""; var arrBadString = new Array(); If the strBadString parameter is not passed, the array is populated with a set of values. The escape character (\) is used to cause the character to be treated as a literal, preventing potential breaks in the code. This example uses ten possible unwanted strings, but your list could have any number of possible values. if (!strBadString) { arrBadString[0] = "\|"; arrBadString[1] = "\`"; arrBadString[2] = "\\"; arrBadString[3] = "\<"; arrBadString[4] = "\>"; arrBadString[5] = "\&"; arrBadString[6] = "\^"; arrBadString[7] = "\~"; arrBadString[8] = "\@"; arrBadString[9] = "\%"; arrBadString[10] = "\#"; } If the strBadText parameter has been defined, then it is assigned to the first position of the arrBadString array. else { arrBadString[0] = strBadString; } Next, a for loop cycles through the elements of the array. for (l = 0; l < arrBadString.length; l++) { … Finally, each element is checked to determine if it can be located within the strText parameter. If it does appear, then a value of false is returned. if (strText.indexOf(arrBadString[l]) >= 0) { return false; } Following is the complete m_isSpecChar() method: function m_isSpecChar(strText, strBadString) { strText = strText + "";

    var arrBadString = new Array(); if (!strBadString) { arrBadString[0] = "\|"; arrBadString[1] = "\`"; arrBadString[2] = "\\"; arrBadString[3] = "\<"; arrBadString[4] = "\>"; arrBadString[5] = "\&"; arrBadString[6] = "\^"; arrBadString[7] = "\~"; arrBadString[8] = "\@"; arrBadString[9] = "\%"; arrBadString[10] = "\#"; } else { arrBadString[0] = strBadString; }

    for (l = 0; l < arrBadString.length; l++) { if (strText.indexOf(arrBadString[l]) >= 0) { return false; } }

    return true; } m_isFormatDate() The first of the format validation methods is m_isFormatDate(). This method takes four parameters: strDate, charDelim, intYearDigits, and strType. The first parameter defines the value to check. charDelim is the date delimiter that is used to separate the sections of the date. Often this value is "\", "-", or ".". The number of digits expected in the year is assigned to the intYearDigits parameter. The optional fourth parameter determines whether to use the European convention of placing the days before the months or the default with the month preceding the days. If the strText value matches the proper date format a value of true is returned. function m_isFormatDate(strDate, charDelim, intYearDigits, strType) { … First, two variables are defined: an integer that will hold the maximum days possible for the month and an array populated with the value of the strDate parameter broken down by the charDelim parameter. var intMaxDays; var arrDateSet = strDate.split(charDelim); Two variables are used to determine the placement of the month and day values in the arrDateSet array. If the strType parameter is "EU", intMonth is set to 1 and intDay to 0. Otherwise the values are reversed. if (strType == "EU") { var intMonth = 1 var intDay = 0; } else { var intMonth = 0; var intDay = 1; } Next, an if statement returns a value of false if any of these conditions are met:

    The array contains no elements. arrDateSet.length <= 0

    The array contains more than three elements. arrDateSet.length > 3 The length of the year element does not match the intYearDigits parameter. arrDateSet[2].length != intYearDigits

    The year is not an integer. !m_isInteger(arrDateSet[2]) A switch condition statement determines the value of the month element in order to define the maximum days allowed in the month. switch(parseInt(arrDateSet[intMonth])) { … The default value of intMaxDays is set to 31. Each of the 30-day months assigns this variable a value of 30. February (case 2) requires special consideration. First the year element is divided by 4 and checked to see whether the result is an integer. Then it is divided by 1000. If the year is divisible evenly by 4 but not by 1000, then it is a leap year. An if statement assigns the value of intMaxDays to either 28 or 29. case 2: var boolLeap = m_isInteger(eval(arrDateSet[2] / 4)); var boolMill = m_isInteger(eval(arrDateSet[2] / 1000)); if ((boolLeap) && (!boolMill)) { intMaxDays = 29; } else { intMaxDays = 28; } break Finally, an if statement returns a value of false if any of the following conditions are met: § The month element is larger than 12. arrDateSet[intMonth] > 12 § The month element is less than 1. arrDateSet[intMonth] < 1 § The month element is not an integer. !m_isInteger(arrDateSet[intMonth]) § The day element is not more than the maximum number of days for the month. arrDateSet[intDay] > intMaxDays § The day element is not less than 1. arrDateSet[intDay] < 1 § The day element is not an integer. !m_isInteger(arrDateSet[intDay])

    The complete method should look like this: function m_isFormatDate(strDate, charDelim, intYearDigits, strType) { var intMaxDays; var arrDateSet = strDate.split(charDelim);

    if (strType == "EU") { var intMonth = 1 var intDay = 0; } else { var intMonth = 0; var intDay = 1; }

    if ((arrDateSet.length <= 0) || (arrDateSet.length > 3) || (arrDateSet[2].length != intYearDigits) || (!m_isInteger(arrDateSet[2]))) { return false; }

    switch(parseInt(arrDateSet[intMonth])) { case 2: var boolLeap = m_isInteger(eval(arrDateSet[2] / 4)); var boolMill = m_isInteger(eval(arrDateSet[2] / 1000)); if ((boolLeap) && (!boolMill)) { intMaxDays = 29; } else { intMaxDays = 28; } break

    case 4: intMaxDays = 30; break

    case 6: intMaxDays = 30; break

    case 9: intMaxDays = 30; break

    case 11: intMaxDays = 30; break

    default: intMaxDays = 31 break }

    if ((arrDateSet[intMonth] > 12) || (arrDateSet[intMonth] < 1) || (!m_isInteger(arrDateSet[intMonth])) || (arrDateSet[intDay] > intMaxDays) || (arrDateSet[intDay] < 1) || (!m_isInteger(arrDateSet[intDay]))) { return false; } return true; } m_isFormatEmail() The m_isFormatEmail() method checks the content of the parameter strEmail. If the parameter matches the format of a valid e-mail address, a value of true is returned. function m_isFormatEmail(strEmail) { … First the parameter is converted to an array slip by the @ character. In a valid e-mail address this produces exactly two elements. var arrEmail = strEmail.split("@"); An if statement returns a value of false if any of these conditions are met: § The arrEmail array does not have exactly two elements. arrEmail.length != 2 § The first array element does not contain any special characters. !m_isSpecChar(arrEmail[0]) § The second array element does not contain at least one period or has a period as the first character. arrEmail[1].indexOf(".") <= 0 § The last character of the second element is not a period. arrEmail[1].lastIndexOf(".") == arrEmail[1].length - 1 § The second element does not contain any special character. !m_isSpecChar(arrEmail[1]) Here is how the complete m_isFormatEmail() method should appear: function m_isFormatEmail(strEmail) { var arrEmail = strEmail.split("@");

    if ((arrEmail.length != 2) || (!m_isSpecChar(arrEmail[0])) || (arrEmail[1].indexOf(".") <= 0) || (arrEmail[1].lastIndexOf(".") == arrEmail[1].length - 1) || (!m_isSpecChar(arrEmail[1]))) { return false; } else { return true; } } m_isFormatZipUS() The standard format for a U.S. Zip code is #####-####. The extra four digits at the end are optional. The m_isFormatZipUS() method returns a value of true if the parameter strZip matches this format. function m_isFormatZipUS(strZip) { … First, the parameter is converted to a string. An array is populated with the strZip parameter divided by the hyphen character (-). strZip = strZip + ""; arrZip = strZip.split("-"); The following criteria cause the method to return a value of false: § The array has more than two elements. arrZip.length > 2 § The first element of the array is not an integer. !m_isInteger(arrZip[0]) § The first element of the array is not exactly five characters long. arrZip[0].length != 5

    Finally, the method checks whether the second array element exists. m_Exists(arrZip[1])

    If it does exist, the second element is verified to be an integer and to be exactly four characters long. arrZip[1].length != 4 !m_isInteger(arrZip[1]) The m_isFormatZipUS() method should look like this: function m_isFormatZipUS(strZip) { strZip = strZip + ""; arrZip = strZip.split("-");

    if ((arrZip.length > 2) || (!m_isInteger(arrZip[0])) || (arrZip[0].length != 5)) { return false; }

    if ((m_Exists(arrZip[1]) ) && ((arrZip[1].length != 4) || (!m_isInteger(arrZip[1])))) { return false; }

    return true; } m_isRangeLength() The first range method compares the character length of the strText parameter against the intMax and intMin parameters. Without the intMin parameter the m_isRangeLength() method returns a value of true if the length of strText parameter is less than intMax. function m_isRangeLength(strText, intMax, intMin) { … First the strText parameter is converted to a string. strText = strText + ""; An if statement checks whether the intMin parameter has been passed to the method. If it has not been passed, the length of the strText parameter is compared to the intMax parameter. If intMax is larger, then a value of true is returned. if (!m_Exists(intMin)) { if (strText.length <= intMax) { return true; } } If intMin is supplied to the method, two conditions must be met to result in a value of true being returned. strText.length <= intMax strText.length >= intMin Here is the complete text of the m_isRangeLength() method: function m_isRangeLength(strText, intMax, intMin) { strText = strText + "";

    if (!m_Exists(intMin)) { if (strText.length <= intMax) { return true; } } else { if ((strText.length <= intMax) && (strText.length >= intMin)) { return true; } } return false; } m_isRangeNumber() The m_isRangeNumber() method works similarly to the m_isRangeLength() method. Instead of comparing character lengths, however, this method compares actual values. The intNumber parameter is the value to which to apply the comparisons. The other parameters, intMax and intMin, work the same as with m_isRangeLength(). function m_isRangeNumber(intNumber, intMax, intMin) { … The isNAN JavaScript function is used to determine if the value passed in the intNumber parameter is really a number (NAN stands for Not A Number). Consequently, any value, such as "dog", "4H", or "three", resolves as true and the method returns a value of false. if (isNAN(intNumber)) { return false; } Next, the m_Exists() method checks whether the intMin parameter has been passed. else if (!m_Exists(intMin)) { … Finally, similar comparisons are made to the intNumber parameter as were made in the m_isRangeLength() method. Instead of comparing the length of the parameter, the parameter itself is compared.

    Here is how it should look: function m_isRangeNumber(intNumber, intMax, intMin) { if (isNAN(intNumber)) { return false; } else if (!m_Exists(intMin)) { if (intNumber <= intMax) { return true; } } else { if ((intNumber <= intMax) && (intNumber >= intMin)) { return true; } } return false; } m_isRangeDate() This is probably the most complex of the o_ValidateClient() methods. This method should be used after the date has been verified using the m_isFormatDate() method. The strDate parameter is the date value to check. The next two parameters, charDelim and strType, are the same as with the m_isFormatDate() method. The function will return a value of false if the value of strDate is chronologically before the strBefore parameter. If the strBefore value is not passed, the current date minus 18 years is used. The inclusion of the fourth parameter, strAfter, will cause the method to return a value of true if the value of strDate is chronologically after strAfter. Think of it this way: if you wanted to check whether someone is older than 18 and younger than 21, you would compare their date of birth (strDate) to 18 years ago (strBefore) and 21 years ago (strAfter). If the date is not before strBefore and not after strAfter the person is not between 18 and 21 years old. Note It is possible to add a call to the m_isFormatDate() method within the m_isRangeDate() method. If you do this, however, you won’t be able to tell if the false return was a result of a bad date format or a bad date range. function m_isRangeDate(strDate, charDelim, strType, strBefore, strAfter) { … First, two arrays are created. arrDateSet contains the value of strDate split by the charDelim parameter. The other array, arrBeforeCheck, is currently empty. var arrDateSet = strDate.split(charDelim); var arrBeforeCheck = new Array(); As with m_isFormatDate(), two variables are used to determine the placement of the month and day values in the arrDateSet array. If the strType parameter is "EU", then intMonth is set to 1 and intDay to 0. Otherwise the values are reversed. if (strType == "EU") { var intMonth = 1 var intDay = 0; } else { var intMonth = 0; var intDay = 1; } If the strBefore value has not been passed, then a new date variable is defined. The current month and day are assigned to the arrBeforeCheck array. Verifying age (adulthood) is the most common use of date range validation, therefore the year is set to 18 years ago. if (!m_Exists(strBefore)) { var dateToday = new Date(); arrBeforeCheck[0] = dateToday.getMonth() + 1; arrBeforeCheck[1] = dateToday.getDate(); arrBeforeCheck[2] = dateToday.getYear() - 18; } If the strBefore parameter is passed, then a new array is created by splitting the value of strBefore using the charDelim parameter. The elements of this new array are then assigned to arrBeforeCheck using the intMonth and intDay variables defined earlier. else { var arrBeforeSet = strBefore.split(charDelim); arrBeforeCheck[0] = parseInt(arrBeforeSet[intMonth]); arrBeforeCheck[1] = parseInt(arrB eforeSet[intDay]); arrBeforeCheck[2] = parseInt(arrBeforeSet[2]); } A series of if-else if commands are then used to compare the arrDateSet array against the arrBeforeCheck array. First, if the strDate parameter year is confirmed to be more than the arrBeforeCheck[2] value, the method returns a value of false. The arrBeforeCheck[2] element was assigned the value of the year from strBefore in the step above. if (arrDateSet[2] > arrBeforeCheck[2]) { return false; } … The next set of comparisons is only checked if the strDate parameter year is exactly the same as the year from strBefore. If the strDate month is greater than the strBefore month, then a value of false is returned. else if (arrDateSet[2] == arrBeforeCheck[2]) { if (arrDateSet[intMonth] > arrBeforeCheck[0]) { return false; } … If the months are the same, then the days are compared. An strDate day that is larger than the strBefore day will return a value of false. else if (arrDateSet[intMonth] == arrBeforeCheck[0]) { if (arrDateSet[intDay] > arrBeforeCheck[1]) { return false; } } … If the strAfter parameter is passed, another set of calculations are made. A new array is created and populated by splitting the strAfter parameter. if (m_Exists(strAfter)) { var arrAfterCheck = new Array(); var arrAfterSet = strAfter.split(charDelim); arrAfterCheck[0] = parseInt(arrAfterSet[intMonth]); arrAfterCheck[1] = parseInt(arrAfterSet[intDay]); arrAfterCheck[2] = parseInt(arrAfterSet[2]); … The earlier comparisons are repeated with arrAfterCheck rather than arrBeforeCheck. Of course, the comparison check for lower values rather than higher ones. if (arrDateSet[2] < arrAfterCheck[2]) { return false; } else if (arrDateSet[2] == arrAfterCheck[2]) { if (arrDateSet[intMonth] < arrAfterCheck[0]) { return false; } else if (arrDateSet[intMonth] == arrAfterCheck[0]) { if (arrDateSet[intDay] < arrAfterCheck[1]) { return false; } } }

    Here is the full method: function m_isRangeDate(strDate, charDelim, strType, strBefore, strAfter) { var arrDateSet = strDate.split(charDelim); var arrBeforeCheck = new Array();

    if (strType == "EU") { var intMonth = 1 var intDay = 0; } else { var intMonth = 0; var intDay = 1; }

    if (!m_Exists(strBefore)) { var dateToday = new Date(); arrBeforeCheck[0] = dateToday.getMonth() + 1; arrBeforeCheck[1] = dateToday.getDate(); arrBeforeCheck[2] = dateToday.getYear() - 18; } else { var arrBeforeSet = strBefore.split(charDelim); arrBeforeCheck[0] = parseInt(arrBeforeSet[intMonth]); arrBeforeCheck[1] = parseInt(arrBeforeSet[intDay]); arrBeforeCheck[2] = parseInt(arrBeforeSet[2]); } if (arrDateSet[2] > arrBeforeCheck[2]) { return false; } else if (arrDateSet[2] == arrBeforeCheck[2]) { if (arrDateSet[intMonth] > arrBeforeCheck[0]) { return false; } else if (arrDateSet[intMonth] == arrBeforeCheck[0]) { if (arrDateSet[intDay] > arrBeforeCheck[1]) { return false; } } } if (m_Exists(strAfter)) { var arrAfterCheck = new Array(); var arrAfterSet = strAfter.split(charDelim); arrAfterCheck[0] = parseInt(arrAfterSet[intMonth]); arrAfterCheck[1] = parseInt(arrAfterSet[intDay]); arrAfterCheck[2] = parseInt(arrAfterSet[2]);

    if (arrDateSet[2] < arrAfterCheck[2]) { return false; } else if (arrDateSet[2] == arrAfterCheck[2]) { if (arrDateSet[intMonth] < arrAfterCheck[0]) { return false; } else if (arrDateSet[intMonth] == arrAfterCheck[0]) { if (arrDateSet[intDay] < arrAfterCheck[1]) { return false; } } } } return true; } m_modTrim() Unlike the earlier methods, m_modTrim() doesn’t directly validate a value. Rather, it changes a value so as to make it easier to validate. For example, if a user entered 95678_ —that is, five numbers followed by a space—as a Zip code, the m_isFormat- ZipUS() method would return a value of false. By first processing the value with m_modTrim() you can avoid these problems. The m_modTrim() method takes a single parameter: strText. After the white space is removed, this value is returned by the method. function m_modTrim(strText) { …

    First the white space characters are defined in a single variable. var strWhiteSpace = " \r\t\n"; A while loop is used to check the contents of the first character of the strText parameter. If that character is found in the strWhiteSpace variable, then the while loop continues. while (strWhiteSpace.indexOf(strText.charAt(0)) != -1) { … Every time the loop runs, a substring is gathered, including every character except the first, and assigned to the strText parameter. strText = strText.substr(1, strText.length); Another while loop performs a similar process, checking and replacing the last character rather than the first. while (strWhiteSpace.indexOf(strText.charAt(strText.length-1)) != -1) { strText = strText.substr(0, strText.length -1); } Then the value of strText is returned. return strText; This is the complete m_modTrim() method: function m_modTrim(strText) { var strWhiteSpace = " \r\t\n";

    while (strWhiteSpace.indexOf(strText.charAt(0)) != -1) { strText = strText.substr(1, strText.length); }

    while (strWhiteSpace.indexOf(strText.charAt(strText.length-1)) != -1) { strText = strText.substr(0, strText.length -1); } return strText; } m_Alert_x() Currently, the m_Alert_x() method is simply an expanded way of calling the JavaScript alert() property of the window object. This function, however, could be modified to output the text in any number of ways. You could send the warning text to a form element, change the current URL, output directly to the page or a frame, or create a custom alert system. Currently, the m_Alert_x() method uses two parameters: strAlert and objFormElement. The first is the text to output, the second is the form element, if any, to which to move the insertion point.

    This method is straightforward: function m_Alert_x(strAlert, objFormElement) { window.alert(strAlert);

    if (m_Exists(objFormElement)) { objFormElement.focus(); } } Using o_ValidateClient() Now that the o_ValidateClient() is complete, you can begin to apply the methods to your forms. Like PHP, JavaScript uses the new keyword to instantiate an object. var objVal = new o_ValidateClient();

    JavaScript uses the dot notation rather than the pointer operator to call object methods and properties. objVal.m_Alert_x("test"); As you can see, the passing parameter is handled in the same way as PHP. To validate a form you would create a function that combines the necessary o_ValidateClient methods. You could call this function from any of the form event handlers, such as onSubmit(), onChange(), or onClick().

    Here is a quick example:

    Name:
    Password:
    The i_validate.jss file contains the complete text of the o_ValidateClient() object. i_validate.jss function o_ValidateClient() { this.m_Exists = m_Exists; this.m_isEquality = m_isEquality; this.m_isInteger = m_isInteger; this.m_isSpecChar = m_isSpecChar; this.m_isFormatDate = m_isFormatDate; this.m_isFormatEmail = m_isFormatEmail; this.m_isFormatZipUS = m_isFormatZipUS; this.m_isRangeLength = m_isRangeLength; this.m_isRangeNumber = m_isRangeNumber; this.m_isRangeDate = m_isRangeDate; this.m_modTrim = m_modTrim; this.m_Alert_x = m_Alert_x; } function m_Exists(varVariant) { if ((varVariant == "") || (varVariant == null) || (varVariant == 0) || (varVariant == false)) { return false; } else { return true; } } function m_isEquality(varFirst,varSecond) { if (eval(varFirst) == eval(varSecond)) { return true; } else { return false; } } function m_isInteger(varVariant) { var intStart = 0; varVariant = varVariant + "";

    if (varVariant.charAt(0) == "-") { intStart++; }

    for (l = intStart; l < varVariant.length; l++) { var intCurrentPos = varVariant.charAt(l);

    if (!((intCurrentPos >= "0") && (intCurrentPos <= "9"))) { return false; } }

    return true; } function m_isSpecChar(strText, strBadString) { strText = strText + "";

    var arrBadString = new Array(); if (!strBadString) { arrBadString[0] = "\|"; arrBadString[1] = "\`"; arrBadString[2] = "\\"; arrBadString[3] = "\<"; arrBadString[4] = "\>"; arrBadString[5] = "\&"; arrBadString[6] = "\^"; arrBadString[7] = "\~"; arrBadString[8] = "\@"; arrBadString[9] = "\%"; arrBadString[10] = "\#"; } else { arrBadString[0] = strBadString; }

    for (l = 0; l < arrBadString.length; l++) { if (strText.indexOf(arrBadString[l]) >= 0) { return false; } }

    return true; } function m_isFormatDate(strDate, charDelim, intYearDigits, strType) { var intMaxDays; var arrDateSet = strDate.split(charDelim);

    if (strType == "EU") { var intMonth = 1 var intDay = 0; } else { var intMonth = 0; var intDay = 1; }

    if ((arrDateSet.length <= 0) || (arrDateSet.length > 3) || (arrDateSet[2].length != intYearDigits) || (!m_isInteger(arrDateSet[2]))) { return false; }

    switch(parseInt(arrDateSet[intMonth])) { case 2: var boolLeap = m_isInteger(eval(arrDateSet[2] / 4)); var boolMill = m_isInteger(eval(arrDateSet[2] / 1000)); if ((boolLeap) && (!boolMill)) { intMaxDays = 29; } else { intMaxDays = 28; } break

    case 4: intMaxDays = 30; break

    case 6: intMaxDays = 30; break

    case 9: intMaxDays = 30; break

    case 11: intMaxDays = 30; break

    default: intMaxDays = 31 break }

    if ((arrDateSet[intMonth] > 12) || (arrDateSet[intMonth] < 1) || (!m_isInteger (arrDateSet[intMonth])) || (arrDateSet[intDay] > intMaxDays) || (arrDateSet[intDay] < 1) || (!m_isInteger(arrDateSet[intDay]))) { return false; } return true; } function m_isFormatEmail(strEmail) { var arrEmail = strEmail.split("@");

    if ((arrEmail.length != 2) || (!m_isSpecChar(arrEmail[0])) || (arrEmail[1].indexOf(".") <= 0) || (arrEmail[1].lastIndexOf(".") == arrEmail[1].length - 1) || (!m_isSpecChar(arrEmail[1]))) { return false; } else { return true; } } function m_isFormatZipUS(strZip) { strZip = strZip + ""; arrZip = strZip.split("-");

    if ((arrZip.length > 2) || (!m_isInteger(arrZip[0])) || (arrZip[0].length != 5)) { return false; }

    if ((m_Exists(arrZip[1])) && ((arrZip[1].length != 4) || (!m_isInteger(arrZip[1])))) { return false; }

    return true; } function m_isRangeLength(strText, intMax, intMin) { strText = strText + "";

    if (!m_Exists(intMin)) { if (strText.length <= intMax) { return true; } } else { if ((strText.length <= intMax) && (strText.length >= intMin)) { return true; } } return false; } function m_isRangeNumber(intNumber, intMax, intMin) { if (isNAN(intNumber)) { return false; } else if (!m_Exists(intMin)) { if (intNumber <= intMax) { return true; } } else { if ((intNumber <= intMax) && (intNumber >= intMin)) { return true; } } return false; } function m_isRangeDate(strDate, charDelim, strType, strBefore, strAfter) { var arrDateSet = strDate.split(charDelim); var arrBeforeCheck = new Array();

    if (strType == "EU") { var intMonth = 1 var intDay = 0; } else { var intMonth = 0; var intDay = 1; }

    if (!m_Exists(strBefore)) { var dateToday = new Date(); arrBeforeCheck[0] = dateToday.getMonth() + 1; arrBeforeCheck[1] = dateToday.getDate(); arrBeforeCheck[2] = dateToday.getYear() - 18; } else { var arrBeforeSet = strBefore.split(charDelim); arrBeforeCheck[0] = parseInt(arrBeforeSet[intMonth]); arrBeforeCheck[1] = parseInt(arrBeforeSet[intDay]); arrBeforeCheck[2] = parseInt(arrBeforeSet[2]); }

    if (arrDateSet[2] > arrBeforeCheck[2]) { return false; } else if (arrDateSet[2] == arrBeforeCheck[2]) { if (arrDateSet[intMonth] > arrBeforeCheck[0]) { return false; } else if (arrDateSet[intMonth] == arrBeforeCheck[0]) { if (arrDateSet[intDay] > arrBeforeCheck[1]) { return false; } } } if (m_Exists(strAfter)) { var arrAfterCheck = new Array(); var arrAfterSet = strAfter.split(charDelim); arrAfterCheck[0] = parseInt(arrAfterSet[intMonth]); arrAfterCheck[1] = parseInt(arrAfterSet[intDay]); arrAfterCheck[2] = parseInt(arrAfterSet[2]);

    if (arrDateSet[2] < arrAfterCheck[2]) { return false; } else if (arrDateSet[2] == arrAfterCheck[2]) { if (arrDateSet[intMonth] < arrAfterCheck[0]) { return false; } else if (arrDateSet[intMonth] == arrAfterCheck[0]) { if (arrDateSet[intDay] < arrAfterCheck[1]) { return false; } } } } return true; } function m_modTrim(strText) { var strWhiteSpace = " \r\t\n";

    while (strWhiteSpace.indexOf(strText.charAt(0)) != -1) { strText = strText.substr(1, strText.length); }

    while (strWhiteSpace.indexOf(strText.charAt(strText.length-1)) != -1) { strText = strText.substr(0, strText.length -1); } return strText; } function m_Alert_x(strAlert, objFormElement) { window.alert(strAlert);

    if (m_Exists(objFormElement)) { objFormElement.focus(); } } Server-Side As you learned in Chapter 2, client-side validation can fail in a number of ways. Whether through malicious user activity or a faulty JavaScript engine, it is entirely possible for improper data to be submitted. Server-side validation is the only way to ensure valid content. Luckily, you won’t have to start from scratch. PHP is very similar in format to JavaScript, and you will be able to recycle parts of the o_ValidateClient() object into the server-side o_Validate class. Additionally, PHP includes a number of built-in pattern matching and validation functions that can greatly simplify your code. Pattern Matching and Regular Expressions Form validation usually involves comparing a user-defined value to a set of acceptable values. Usually these values can be defined using a pattern of characters. For example, everyone knows what an e-mail address looks like. It begins with a name followed by an @ symbol. After that there is a domain name, a period, and a top-level domain extension, in that order. All e-mail addresses fit this pattern. Scripting languages such as PHP use a protocol called regular expressions to define patterns. The flexibility and power of regular expressions in PHP will allow you to create a much simplified validation class compared to o_ValidateClient().

    Regular expressions are very powerful, but can be confusing to the uninitiated. Here is an example of a regular expression: ^([0-9]{4}\-]){3}[0-9]{4}$

    I’ll come back to this expression later. What you should know now is that regular expressions define a pattern one character at a time. For example the expression a looks for a pattern that includes the single character “a.” It would be found in all day abba but not dog Andrew @home

    Any letters after “a” in the expression would produce a pattern where “a” was immediately followed by the next character in the expression. For example the expression in would be found within mint Illinois but not knife Indiana Square brackets ([]) are used to define a group of possible values. The hyphen is used to define a range. [A-F] // This expression matches any capital letter A through F. [aei] // This expression matches a or e or i. [3-7][xyz] // This expression matches any digit between 3 and 7 followed by either x, y, or z. Curly brackets ({}) are used to define a multiple occurrence of a character. A comma is used to separate the minimum and maximum number of occurrences in the pattern. A blank maximum value indicates that any number of characters is allowed (above the minimum). a{3} // This expression matches aaa. [abc]{2} // This expression matches aa, bb, or cc. p{1,2}g{3} // This expression matches pggg, ppggg. l{0,2}o // This expression matches o, lo, llo. j{1, } // This expression matches j, jj, jjj, etc. Parentheses (()) can be used to group sequential characters. The bar (|) is used as a logical or operator for the expression. (he){1, } // This expression matches he, hehe, hehehe, etc. (Dan)|(dork) // This expression matches Dan or dork. ([12]3){2} // This expression matches 1313 or 2323.

    Additionally, you can use a number of special characters and functions. ^ // The expression must be found at the beginning of a string. $ // The expression must be found at the end of a string. \ // The escape character, which causes a special character to be considered a literal. . // Represents any non-newline character. \t // This expression matches the tab character. \n // This expression matches the new line character. \r // This expression matches the carriage return character. \s // This expression matches a single whitespace character. \$ // This expression matches $. \i // Make the patter case insensitive. * // This is a shortcut for {0, }. + // This is a shortcut for {1, }. ? // This is a shortcut for {0,1}. [^] // Matches any except those included in the brackets. [[:alpha:]] // This expression matches any letter, any case. [[:alnum:]] // This expression matches any alphanumeric character. [[:blank:]] // This expression matches the space and tab characters. [[:digit:]] // This expression matches any single numerical digit. [[:lower:]] // This expression matches any lowercase letter. [[:punct:]] // This expression matches any punctuation character. [[:space:]] // This expression matches any whitespace character. [[:upper:]] // This expression matches any uppercase letter.

    // Examples: g$ // This expression matches walking and talking, but not gum. ^n // This expression matches now and nice, but not unknown. .{3} // This expression matches lol, brb, or any 3 character string without a newline. a\i // This expression matches a and A. [^a -zA-Z] // This expression matches any non-alphabetical character.

    Here are a few regular expressions that you may find useful: [a-zA-Z_] // Any letter or underscore. [0-9]+ // Any positive integer \-?[0-9]+ // Any integer \-?[0-9]*\.?[0-9]* // Any double number .+@.+\..+ // E-mail address. [0-9]{5}(\-[0-9]{4})? // U.S. Zip code 5 or 9 digit. ([0-9]{1,2})-([0-9]{1,2})-([0-9]{4}) // Date in dd-mm-yyyy format. (\(?[0-9]{3}\)?[ \-]?)?[0-9]{3}\-?[0-9]{4} // U.S. phone number with or without area code. ([0-9]{4}\-){3}[0-9]{4} // Credit card number (such as 5555-5555-5555-5555). PHP Validation Functions JavaScript developers will be happy to know that PHP comes far more equipped to handle form validation. PHP includes many built-in functions that make the back end of interface development smoother than the front end on the client. Other server-side scripting environments include similar functionality. For example, if you were developing in ASP you could use CInt() rather than intVal(). Before developing your validation class familiarize yourself with the language’s most common validation functions. ereg(), eregi() Earlier you learned about regular expressions. ereg() and eregi() are the most common ways for these expressions to be used in PHP. The two functions are exactly the same except that eregi() ignores case distinctions. These functions take a regular expression as the first parameter and compare it to a string, the second parameter. If the pattern is found within the string then a value of true is returned. Otherwise the function returns a value of false.

    The optional third parameter is a name of a variable array. If the pattern is found, this array will be populated with the string, then sequentially, with each section of the string. Sections are defined by parentheses. Here is an example: $strExpression = "(\(?([0-9]{3})\)?[ \-]?)?([0-9]{3})\-?([0-9]{4})"; $strPhoneNumber = "(800) 555-5555"; ereg($strExpression, $strPhoneNumber, $arrNumberParts); echo $arrNumberParts[0]; // displays (800) 555-5555 echo $arrNumberParts[1]; // displays (800) echo $arrNumberParts[2]; // displays 800 echo $arrNumberParts[3]; // displays 555 echo $arrNumberParts[4]; // displays 5555 Typically the two functions are used in if statements. By using the ^ and $ special characters you can find an exact match: $strExpression = "^([0-9]{4}\-?){3}[0-9]{4}$"; $strPhoneNumber = "5555-5555-5555-5555"; if (ereg($strExpression, $strPhoneNumber)) { echo "acceptable card number"; } else { echo "please, reenter your card number."; }; ereg_replace(), eregi_replace() These functions use regular expressions to replace patterns of characters within a string. Just as with ereg() and eregi(), ereg_replace() and eregi_replace() work exactly the same except that eregi_replace() ignores case distinctions. Each function takes three parameters: the patterns to match, the string to use as a replacement, and the string to search. $strExpression = "[ador]{4}"; $strReplace = "farmer"; $strText = "Why did the chicken cross the road?"; $strNewText = ereg_replace($strExpression, $strReplace, $strText); echo $strText; // displays "Why did the chicken cross the road?" echo $strNewText; // displays "Why did the chicken cross the farmer?"

    As you can see, these functions do not change the original string value, they simply output the modified string. split(), spliti() The last regular expression functions in PHP are split() and spliti(). These functions work similarly to explode in that they take a string and create an array from the parts as defined by a delimiter. In the case of split() and spliti(), the first parameter is the delimiter, a regular expression. The second parameter is the string from which to gather the array elements. The optional third parameter defines the maximum number of array elements that will be created. spliti() ignores case distinctions. $strExpression = "[[:blank:]]"; $strText = "Would you like fries with that?"; $arrText = split($strExpression, $strText, 3); echo $arrText[0]; // displays "Would" echo $arrText[1]; // displays "you" echo $arrText[2]; // displays "like fries with that?" empty(), isset() These two functions can be used to determine whether a variable has been defined. Both functions take a single parameter, the variable to check, and return a Boolean value. The difference is that isset() will return a value of true if the variable has been defined to any value except null, while the empty() function returns a value of true if the variable is set to zero, false, null, or not set at all. $varNull = null; $intZero = 0; $strEmtpy = ""; $boolFalse = false; empty($varNull); // returns true empty($intZero); // returns true empty($strEmtpy); // returns true empty($boolFalse); // returns true empty($varNone); // returns true isset($varNull); // returns false isset($intZero); // returns true isset($strEmtpy); // returns true isset($boolFalse); // returns true isset($varNone); // returns false Caution Using isset() to check a variable that has not been defined can produce a PHP error. To avoid this error, you can change the error_reporting bit value in your php.ini file to include ~E_NOTICE. This will, however, prevent PHP from giving you a number of helpful notices that you might need during development or troubleshooting. Alternatively, you could always check empty() first, then check isset() only if the result is true. is_int(), is_numeric(), is_string()

    PHP makes it easy to verify that a variable is of a specific type. The “is” functions take a single parameter, the variable to compare, and return a Boolean value if it meets the criterion for the variable type. $intInteger = 15; $strString = "Hello"; $dblNumber = 9.65; echo is_int($intInteger); // returns true echo is_int($strString); // returns false echo is_numeric($dblNumber); // returns true echo is_numeric($intInteger); // returns true echo is_numeric($strString); // returns false echo is_string($strString); // returns true echo is_string($dblNumber); // returns false settype(), intval(), strval()

    Most variable type changes happen automatically. For example if you append the string “th” to the integer 4 you end up with a string “4th”. Sometimes, however, variables can appear as one type but be another. Numbers are notorious for masquerading as strings. Luckily, PHP provides an easy way to force variables to a specific type. The simple functions intval() and strval() take a variable parameter and output an integer or string equivalent, respectively. Both return a value of false if the conversion is not possible. In addition, intval() takes an optional second parameter that defines the base value for the conversion. The default is 10. $varVar1 = "5th"; $varVar2 = 5; $varVar3 = 5; $varVar4 = "15"; echo intval($varVar1); // returns 5 echo strval($varVar2); // returns "5" echo (strval($varVar2) === $varVar3); // returns false echo (intval($varVar1) === $varVar3); // returns true echo intval($varVar4, 8); // returns 13 The settype() function changes a variable into a specific type. The first parameter is the variable to convert; the second is the variable type to which to convert. This function returns a value of true if the conversion is successful, and a value of false otherwise. $varVar = 0; echo settype($varVar, "boolean"); // returns true echo settype($varVar, "integer"); // returns true echo settype($varVar, "double"); // returns true echo settype($varVar, "string"); // returns true echo settype($varVar, "array"); // returns true echo settype($varVar, "object"); // returns true i_validate.php Server-side validation will be handled by a custom class: o_Validate. When building this class you will find many similarities to the o_ValidateClient() object created earlier. One of the primary differences, however, is that o_Validate has two properties: p_intWarnCounter and p_arrWarnings. Rather than simply returning a value of true or false, this class will use the properties to store error messages as they accumulate. When the validation process has been completed the error messages can be output all at once. Every time a method returns a value of false it runs the following code: $this->p_arrWarnings[intval($this->p_intWarnCounter)] = $strWarnText; $this->p_intWarnCounter++; The first line appends a new element to the p_arrWarnings array property. The p_intWarnCounter is used as the index for the element. The text of the element is strWarnText. This value is the last parameter of the method referenced. The second line increments the p_intWarnCounter property by one. In this way, the next element won’t overwrite the one just written. o_Validate() This constructor method runs each time the o_Validate class is called. In this case the only function called is session_start(). This function checks for an active session and registers all the session variables. If no session is active, it creates one. function o_Validate() { session_start(); }

    session A sequence of connections between an individual user and the server within a defined period of time. Session variables are variables that are globally accessible for the life of the session on any page on the server. This project uses session variables to pass values between pages. Check your Web server documentation for how to activate sessions.

    m_Exists() Other than the standard differences mentioned earlier, the m_Exists() method is almost the same as the JavaScript version. The server-side implementation, however, can condense the entire if condition to a single empty() function. function m_Exists($varVariant, $strWarnText) { if (empty($varVariant)) { $this->p_arrWarnings[intval($this->p_intWarnCounter)] = $strWarnText; $this->p_intWarnCounter++; return false; } else { return true; }; } m_isEquality()

    This method is essentially the same as the JavaScript version. function m_isEquality($varVariant, $strWarnText) { if ($varFirst == $varSecond) { return true; } else { $this->p_arrWarnings[intval($this->p_intWarnCounter)] = $strWarnText; $this->p_intWarnCounter++; return false; } } m_isFormatZip() For the m_isFormatZip() method a regular expression replaces the if condition. function m_isFormatZip($strText, $strWarnText) { if (!ereg("^[0-9]{5}(\-[0-9]{4})?$",$strText)) { $this->p_arrWarnings[$this->p_intWarnCounter] = $strWarnText; $this->p_intWarnCounter++; return false; } else { return true; }; } m_isFormatEmail() The m_isFormatEmail() method also replaces the if condition with a regular expression. function m_isFormatEmail($strText, $strWarnText) { if (!ereg("^.+@.+\..+$", $strText)) { $this->p_arrWarnings[$this->p_intWarnCounter] = $strWarnText; $this->p_intWarnCounter++; return false; } else { return true; }; } m_isFormatDate() The m_isFormatDate() method is significantly different from its JavaScript equivalent. The server-side version takes a single parameter, strFormat, rather than separate parameters for type and the number of digits in the year. function m_isFormatDate($strDate, $strFormat, $charDelim, $strWarnText) { … First, both the original date value and the format value are inserted into arrays based on the value of the charDelim parameter. $arrDateSet = explode($charDelim,$strDate); $arrFormatSet = explode($charDelim,$strFormat); Next, the strFormat parameter is modified by replacing the date notation with the requisite regular expression. $strFormat = str_replace("dd", "([0-9]{1,2})", $strFormat); $strFormat = str_replace("mm", "([0-9]{1,2})", $strFormat); $strFormat = str_replace("yyyy", "([0-9]{4})", $strFormat); $strFormat = str_replace("yy", "([0-9]{2})", $strFormat); An error message is generated if this pattern is not found in strDate. if (!ereg($strFormat, $strDate)) { $this->p_arrWarnings[$this->p_intWarnCounter] = $strWarnText; $this->p_intWarnCounter++; return false; } After the intMonth and intDay values are defined, the checkdate() function is used to determine whether the date is acceptable. checkdate() takes a month, day, and year, in that order, and determines if the date is valid. It automatically takes the number of days in the month and leap year into account. if ($arrFormatSet[0] == "dd") { $intMonth = 1; $intDay = 0; } else { $intMonth = 0; $intDay = 1; } if (!checkdate($arrDateSet[$intMonth],$arrDateSet[$intDay],$arrDateSet[2])) { …

    Here is the complete method: function m_isFormatDate($strDate, $strFormat, $charDelim, $strWarnText) { $arrDateSet = explode($charDelim,$strDate); $arrFormatSet = explode($charDelim,$strFormat);

    $strFormat = str_replace("dd", "([0-9]{1,2})", $strFormat); $strFormat = str_replace("mm", "([0-9]{1,2})", $strFormat); $strFormat = str_replace("yyyy", "([0-9]{4})", $strFormat); $strFormat = str_replace("yy", "([0-9]{2})", $strFormat);

    if (!ereg($strFormat, $strDate)) { $this->p_arrWarnings[$this->p_intWarnCounter] = $strWarnText; $this->p_intWarnCounter++; return false; }

    if ($arrFormatSet[0] == "dd") { $intMonth = 1; $intDay = 0; } else { $intMonth = 0; $intDay = 1; }

    if (!checkdate($arrDateSet[$intMonth],$arrDateSet[$intDay],$arrDateSet[2])) { $this->p_arrWarnings[$this->p_intWarnCounter] = $strWarnText; $this->p_intWarnCounter++; return false; }

    return true;

    } m_isRangeLength() The server-side version of this method uses strlen() rather than string.length. function m_isRangeLength($strText, $intMax, $intMin="", $strWarnText) { if ($intMin == "") { if (strlen($strText) <= $intMax) { return true; } } else { if ((strlen($strText) <= $intMax) && (strlen($strText) >= $intMin)) { return true; } }

    $this->p_arrWarnings[$this->p_intWarnCounter] = $strWarnText; $this->p_intWarnCounter++; return false; } m_isRangeNumber() This method uses is_numeric() rather than isNAN(). function m_isRangeNumber($intNumber, $intMax, $intMin="", $strWarnText) { if (is_numeric($intNumber)) { if ($intMin == "") { if ($intNumber <= $intMax) { return true; } } else { if (($intNumber <= $intMax) && ($intNumber >= $intMin)) { return true; } } } $this->p_arrWarnings[$this->p_intWarnCounter] = $strWarnText; $this->p_intWarnCounter++; return false; } m_isRangeDate() The PHP version of the m_isRangeDate() method uses the same strFormat parameter as the m_isFormatDate() method. Like the JavaScript version, this method uses two parameters for the before and after dates. function m_isRangeDate($strDate, $strFormat, $charDelim, $strBefore="", $strAfter="", $strWarnText) { … First, the date and format arrays are created and the intMonth and intDay variables are set in the same way as m_isFormatDate(). Then the elements of the date array are used to create a timestamp. The strtotime() function takes a string value representing a date and converts it to a timestamp. $intDateTS = strtotime($arrDateSet[$intMonth] . "/" . $arrDateSet[$intDay] . "/" . $arrDateSet[2]); An if statement creates a timestamp for the before date. As with the client-side version, this method uses the current day minus 18 years as the default value if no strBefore parameter is supplied. mktime() produces a timestamp using the current system date. The number 567648000 approximates the number of seconds in 18 years.

    timestamp

    An integer number that represents a specific time or date. In PHP, UNIX timestamps are used. These integers are the number of seconds since the arbitrary date for the beginning of the UNIX epoch: January 1, 1970. Timestamps for dates before this day are negative.

    if ($strBefore == "")

    { $intBeforeTS = mktime() - 567648000;

    }

    else

    { $arrBeforeSet = explode($charDelim, $strBefore);

    $intBeforeTS = strtotime($arrBeforeSet[$intMonth] . "/" . $arrBeforeSet[$intDay] . "/" . $arrBeforeSet[2]);

    }

    After creating the timestamps, the method is essentially the same as the JavaScript version. function m_isRangeDate($strDate, $strFormat, $charDelim, $strBefore="", $strAfter="", $strWarnText) { $arrDateSet = explode($charDelim,$strDate); $arrFormatSet = explode($charDelim,$strFormat);

    if ($arrFormatSet[0] == "dd") { $intMonth = 1; $intDay = 0; } else { $intMonth = 0; $intDay = 1; }

    $intDateTS = strtotime($arrDateSet[$intMonth] . "/" . $arrDateSet[$intDay] . "/" . $arrDateSet[2]); if ($strBefore == "") { $intBeforeTS = mktime() - 567648000; } else { $arrBeforeSet = explode($charDelim, $strBefore); $intBeforeTS = strtotime($arrBeforeSet[$intMonth] . "/" . $arrBeforeSet[$intDay] . "/" . $arrBeforeSet[2]); }

    if ($intDateTS > $intBeforeTS) { $this->p_arrWarnings[$this->p_intWarnCounter] = $strWarnText; $this->p_intWarnCounter++; return false; }

    if ($strAfter != "") { $arrAfterSet = explode($charDelim,$strAfter); $intAfterTS = strtotime($arrAfterSet[$intMonth] . "/" . $arrAfterSet[$intDay] . "/" . $arrAfterSet[2]);

    if ($intDateTS < $intAfterTS) { $this->p_arrWarnings[$this->p_intWarnCounter] = $strWarnText; $this->p_intWarnCounter++; return false; }

    } return true;

    } m_modDate() The m_modDate() is unique to the server-side o_Validate class. This method takes a date string such as "12/25/2002" and creates a formatted date string such as "Wednesday, the 25th day of December in 2002". Similar to m_isFormatDate(), this method takes the input date string, the format string, and the delimiter as parameters. However, rather than the error message as the final parameter, the m_modDate() method takes a string that represents the format of the output date string. function m_modDate($strDate, $strFormatIn, $charDelim, $strFormatOut) { … The method mimics the code of m_isFormatDate() until the timestamp is created. At that point the date() function is used to convert the timestamp to a date string. It is this string that is returned. $dateDate = date($strFormatOut,$intDateTS); return $dateDate;

    The date function takes an options string and a timestamp as parameters. The characters in the option string are read by the function to determine the format of the output. There are a number of recognized characters: § s. The two-digit seconds, with leading zeros. § i. The two-digit minutes, with leading zeros. § g. The hour of the day in standard format. § G. The hour of the day in 24-hour format. § h. The two-digit hour of the day with leading zeros. § H. The two-digit hour of the day with leading zeros in 24-hour format. § a. “am” or “pm” § A. “AM” or “PM” § w. The numeric day of the week. § l. The name of the day of the week. § D. The abbreviation of the day of the week. § j. The day of the month. § d. The two-digit day of the month, with leading zeros. § S. The ordinal suffix for the day, such as “th” or “st”. § n. The month of the year. § m. The two-digit month, with leading zeros. § F. The name of the month. § M. The abbreviation for the name of the month. § I. Evaluates to “1” if Daylight Savings Time is active, “0” otherwise. § L. Evaluates to “1” if the year is a leap year, “0” otherwise. § Y. The four-digit year. § y. The two-digit year. § z. The day of the year. Using these values and the escape character (\), you can get the output date format used earlier, "Wednesday, the 25th day of December in 2002", by passing the value "l, \\t\h\e dS \d\\a\y \o\f F \i\\n Y". m_Alert() The server-side version of the m_Alert() method gathers any error messages that have been generated and returns a formatted string. The only parameter of this method is the delimiter that should be used to separate the error messages. This could be a simple carriage return, "\n\r", or HTML code, "

  • ". A while loop is used to cycle through the error messages stored in the p_arrWarnings array property. while (list($intWanV al, $strWarnText) = each($this->p_arrWarnings)) { $strWarningOutput = $strWarningOutput . $strWarnText . $strDelim . "\r\n"; } A session_register() function creates a new session variable. This variable is then populated with the text of the warning messages, making it available to other pages. Finally, the error message is returned. session_register("sessErrors"); $GLOBALS["sessErrors"] = $strWarningOutput; return $strWarningOutput; Here is the complete m_Alert() method: function m_Alert($strDelim) { $strWarningOutput = "";

    while (list($intWanVal, $strWarnText) = each($this->p_arrWarnings)) { $strWarningOutput = $strWarningOutput . $strWarnText . $strDelim . "\r\n"; } session_register("sessErrors"); $GLOBALS["sessErrors"] = $strWarningOutput; return $strWarningOutput; } The i_validate.php file contains the complete text of the o_Validate class. i_validate.php

    function o_Validate() { session_start(); }

    function m_Exists($varVariant, $strWarnText) { if (empty($varVariant)) { $this->p_arrWarnings[intval($this->p_intWarnCounter)] = $strWarnText; $this->p_intWarnCounter++; return false; } else { return true; }; } function m_isEquality($varVariant, $strWarnText) { if ($varFirst == $varSecond) { return true; } else { $this->p_arrWarnings[intval($this->p_intWarnCounter)] = $strWarnText; $this->p_intWarnCounter++; return false; } } function m_isFormatZip($strText, $strWarnText) { if (!ereg("^[0-9]{5}(\-[0-9]{4})?$",$strText)) { $this->p_arrWarnings[$this->p_intWarnCounter] = $strWarnText; $this->p_intWarnCounter++; return false; } else { return true; }; } function m_isFormatEmail($strText, $strWarnText) { if (!ereg("^.+@.+\..+$", $strText)) { $this->p_arrWarnings[$this->p_intWarnCounter] = $strWarnText; $this->p_intWarnCounter++; return false; } else { return true; }; } function m_isFormatDate($strDate, $strFormat, $charDelim, $strWarnText) { $arrDateSet = explode($charDelim,$strDate); $arrFormatSet = explode($charDelim,$strFormat);

    $strFormat = str_replace("dd", "([0-9]{1,2})", $strFormat); $strFormat = str_replace("mm", "([0-9]{1,2})", $strFormat); $strFormat = str_replace("yyyy", "([0-9]{4})", $strFormat); $strFormat = str_replace("yy", "([0-9]{2})", $strFormat);

    if (!ereg($strFormat, $strDate)) { $this->p_arrWarnings[$this->p_intWarnCounter] = $strWarnText; $this->p_intWarnCounter++; return false; }

    if ($arrFormatSet[0] == "dd") { $intMonth = 1; $intDay = 0; } else { $intMonth = 0; $intDay = 1; }

    if (!checkdate($arrDateSet[$intMonth],$arrDateSet[$intDay],$arrDateSet[2])) { $this->p_arrWarnings[$this->p_intWarnCounter] = $strWarnText; $this->p_intWarnCounter++; return false; }

    return true;

    } function m_isRangeLength($strText, $intMax, $intMin="", $strWarnText) { if ($intMin == "") { if (strlen($strText) <= $intMax) { return true; } } else { if ((strlen($strText) <= $intMax) && (strlen($strText) >= $intMin)) { return true; } }

    $this->p_arrWarnings[$this->p_intWarnCounter] = $strWarnText; $this->p_intWarnCounter++; return false; }

    function m_isRangeNumber($intNumber, $intMax, $intMin="", $strWarnText) { if (is_numeric($intNumber)) { if ($intMin == "") { if ($intNumber <= $intMax) { return true; } } else { if (($intNumber <= $intMax) && ($intNumber >= $intMin)) { return true; } } } $this->p_arrWarnings[$this->p_intWarnCounter] = $strWarnText; $this->p_intWarnCounter++; return false; }

    function m_isRangeDate($strDate, $strFormat, $charDelim, $strBefore="", $strAfter="", $strWarnText) { $arrDateSet = explode($charDelim,$strDate); $arrFormatSet = explode($charDelim,$strFormat);

    if ($arrFormatSet[0] == "dd") { $intMonth = 1; $intDay = 0; } else { $intMonth = 0; $intDay = 1; }

    $intDateTS = strtotime($arrDateSet[$intMonth] . "/" . $arrDateSet[$intDay] . "/" . $arrDateSet[2]); if ($strBefore == "") { $intBeforeTS = mktime() - 567648000; } else { $arrBeforeSet = explode($charDelim, $strBefore); $intBeforeTS = strtotime($arrBeforeSet[$intMonth] . "/" . $arrBeforeSet[$intDay] . "/" . $arrBeforeSet[2]); }

    if ($intDateTS > $intBeforeTS) { $this->p_arrWarnings[$this->p_intWarnCounter] = $strWarnText; $this->p_intWarnCounter++; return false; }

    if ($strAfter != "") { $arrAfterSet = explode($charDelim,$strAfter); $intAfterTS = strtotime($arrAfterSet[$intMonth] . "/" . $arrAfterSet[$intDay] . "/" . $arrAfterSet[2]);

    if ($intDateTS < $intAfterTS) { $this->p_arrWarnings[$this->p_intWarnCounter] = $strWarnText; $this->p_intWarnCounter++; return false; }

    } return true;

    }

    function m_modDate($strDate, $strFormatIn, $charDelim, $strFormatOut) { $arrDateSet = explode($charDelim,$strDate); $arrFormatSet = explode($charDelim,$strFormatIn);

    if ($arrFormatSet[0] == "dd") { $intMonth = 1; $intDay = 0; } else { $intMonth = 0; $intDay = 1; }

    $intDateTS = strtotime($arrDateSet[$intMonth] . "/" . $arrDateSet[$intDay] . "/" . $arrDateSet[2]); $dateDate = date($strFormatOut,$intDateTS);

    return $dateDate; }

    function m_Alert($strDelim) { $strWarningOutput = "";

    while (list($intWanVal, $strWarnText) = each($this->p_arrWarnings)) { $strWarningOutput = $strWarningOutput . $strWarnText . $strDelim . "\r\n"; } session_register("sessErrors"); $GLOBALS["sessErrors"] = $strWarningOutput; return $strWarningOutput; }

    }

    ?>

    Form Pages

    In terms of format, the form pages can be as simple or as complex as you need them to be. In the next sections I will describe each of the required elements for these pages. Whether you put these elements on a white background or arrange them inside a complex table, structure is a matter of preference. Each of the form pages contains three types of elements: the error text marker tag ({{ERRORS}}), the HTML form elements, and the validation JavaScript. The directions here assume that you are using a JavaScript file (i_member.jss) to store the JavaScript functions. However, if you want, you can include the code directly on the HTML page. The i_validate.jss file should be included on all form pages.

    Many of the form pages do not require any dynamic content. If you are using the template system you might find it easier to create a composite object page. This page simply locates the template file based on a query string passed through the URL and appends the standard page elements. main.php is an example of this type of composite page. main.php

    $strFileName = "templates/" . strtolower($Page) . ".tpl"; $objTemplate = new o_Template; $objTemplate->m_LoadFile($strFileName); $objTemplate->m_AddElements($Page); if ($sessErrors) { $objTemplate->m_AssignContent("ERRORS", $sessErrors); } else { $objTemplate->m_AssignContent("ERRORS", ""); }; session_destroy(); $objTemplate->m_Output(); ?> To use this page for a specific template you would add a link to main.php?Page= Pagename. Table 8.1 describes the files that should be created for each form page. Table 8.1: Form Pages

    Page Object Template Page Page

    Login main.php login.tpl Register main.php register.tpl Update Member update.php update.tpl Password Reminder main.php password.tpl

    Login Page

    The following form elements are required for the Login page:

    Here is the JavaScript that should be added to the i_member.jss page: var objValidate = new o_ValidateClient(); function f_valEmail(objElement) { if (!objValidate.m_isFormatEmail(objElement.value)) { objValidate.m_Alert_x("Please enter a valid e-mail address.", objElement); return false; } else { return true; } } function f_valPass(objElement) { if (!objValidate.m_isRangeLength(objElement.value, 50, 3)) { objValidate.m_Alert_x("Passwords must be at least three characters and not more than 30.", objElement); return false; } else { return true; } } Figure 8.1 shows an example of how the Login page might look after formatting.

    Figure 8.1: The Login page

    Registration Page

    The Registration page requires the following form elements:

    The following lines should be added to the i_member.jss file. If you are not using a separate file, then you will also need to include the script from the Login page. function f_valName(objElement) { if (!objValidate.m_isRangeLength(objElement.value, 50, 3)) { objValidate.m_Alert_x("Your name must be at least three characters long and not more than 30.", objElement); return false; } else { return true; } } function f_valPassEq(objElement, objElementComp) { if (!objValidate.m_isEquality(objElement.value, objElementComp.value)) { objValidate.m_Alert_x("Your passwords do not match.", objElement); return false; } else { return true; } } function f_valDate(objElement) { if (!objValidate.m_isFormatDate(objElement.value, "-", 4)) { objValidate.m_Alert_x("Your birthdate should be in the format 12-31-2001.", objElement); return false; } else { return true; } } function f_valZip(objElement) { if (!objValidate.m_isFormatZipUS(objElement.value)) { objValidate.m_Alert_x("Your zip code is invalid.", objElement); return false; } else { return true; } }

    function f_ValidateRegister(objForm) { if ((!f_valEmail(objForm.formEmail)) || (!f_valName(objForm.formName)) || (!f_valPass(objForm.formPassword)) || (!f_valPassEq(objForm.formPassword, objForm.formPassword2))) { return false; } else if (!objForm.formAgeCheck.checked) { objValidate.m_Alert_x("You must be over 18 to register.", objForm.formAgeCheck); return false; } else if (objForm.formZip.value != "") { return f_valZip(objForm.formZip); } else if (objForm.Birthday.value != "") { return f_valDate(objForm.formBirthday); } else { return true; } } Figure 8.2 shows an example of how the Registration page might look with formatting elements included.

    Figure 8.2: The Registration page

    Membership Update Page

    The Membership Update page is different from the other form pages in that it includes dynamic content that must be pulled from a database. Additionally, only users who have successfully logged in should be able to access this page. After including the i_template.php and i_connect.php files, the object page for this form begins with a check for the sessMemberID session variable. If the variable is not found, then the sessRefer session variable is set to the Update page and the user is redirected to the Login page. session_start(); if (!$sessMemberID) { session_register("sessRefer"); $GLOBALS["sessRefer"] = "update.php"; header("Location: main.php?Page=login"); } If the sessMemberID variable is found, then the major page elements are added to the page through the template system similarly to the main.php file. else { $strFileName = "templates/update.tpl"; $objTemplate = new o_Template; $objTemplate->m_LoadFile($strFileName); $objTemplate->m_AddElements("update profile");

    if ($sessErrors) { $objTemplate->m_AssignContent("ERRORS", $sessErrors); } else { $objTemplate->m_AssignContent("ERRORS", ""); };

    session_unregister("sessErrors"); … Next, a database query uses the sessMemberID session variable to gather the member information from the membership table. The information is then used to populate an array using the column names as the index of the array. $strTable = "membership"; $strSQL = "SELECT memberName, eMail FROM $strTable WHERE memberID=\"$sessMemberID\""; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't execute query."); while ($objRow = mysql_fetch_array($objResult, MYSQL_ASSOC)) { $arrResult = array(); $arrResult["memberName"] = $objRow['memberName']; $arrResult["eMail"] = $objRow['eMail']; …

    Another database query then gathers the user information from the memberDemographics table and further populates the array. $strDemoTable = "memberDemographics"; $strDemoSQL = "SELECT d_attribute, d_value from $strDemoTable WHERE memberID = \"$sessMemberID\""; $objDemoResult = mysql_query($strDemoSQL,$CONNECTION) or die("Couldn't execute query."); while ($objDemoRow = mysql_fetch_array($objDemoResult, MYSQL_ASSOC)) { $arrResult[$objDemoRow["d_attribute"]] = $objDemoRow["d_value"]; } Finally, the marker tags are replaced by the content of the array and output to the page. $objTemplate->m_AssignContent("NAME", $arrResult["memberName"]); $objTemplate->m_AssignContent("EMAIL", $arrResult["eMail"]); if ($arrResult["Gender"] == "male") { $objTemplate->m_AssignContent("CHECKED_MALE", "CHECKED"); } else if ($arrResult["Gender"] == "female") { $objTemplate->m_AssignContent("CHECKED_FEMALE", "CHECKED"); } else { $objTemplate->m_AssignContent("CHECKED_MALE", ""); $objTemplate->m_AssignContent("CHECKED_FEMALE", ""); }; if ($arrResult["Postal Code"]) { $objTemplate->m_AssignContent("ZIP", $arrResult["Postal Code"]); } else { $objTemplate->m_AssignContent("ZIP", ""); }; if ($arrResult["Birthday"]) { $objTemplate->m_AssignContent("BIRTH", $arrResult["Birthday"]); } else { $objTemplate->m_AssignContent("BIRTH", ""); }; if ($arrResult["Newsletter"]) { $objTemplate->m_AssignContent("CHECKED_NEWS", "CHECKED"); } else { $objTemplate->m_AssignContent("CHECKED_NEWS", ""); }; session_unregister("sessErrors"); $objTemplate->m_Output();

    The following form elements should be included in the member update template file:

    The f_ValidateUpdate() function should be added to the i_member.jss file. function f_ValidateUpdate(objForm) { if ((!f_valEmail(objForm.formEmail)) || (!f_valName(objForm.formName)) || (!f_valPassEq(objForm.formPassword, objForm.formPassword2))) { return false; } else if (objForm.formZip.value != "") { return f_valZip(objForm.formZip); } else if (objForm.Birthday.value != "") { return f_valDate(objForm.formBirthday); } else { return true; } } Figure 8.3 shows an example of how the Member Update page might look with formatting elements included.

    Figure 8.3: The Member Update page

    Passw ord Reminder Page

    The Password Reminder page requires the following form elements:

    The i_member.jss file should already include the necessary JavaScript. If you are including the script on the page you will need the f_valEmail() function from the Login page. Figure 8.4 shows an example of how the Password Reminder page might appear with formatting elements included.

    Figure 8.4: The Password Reminder page

    Action Pages

    Because the action pages are used to process form input and not to display data, these pages do not require HTML form or template files. However, each action page should include a call to the i_connect.php and i_validate.php files. include_once("global/i_connect.php"); include_once("global/i_validate.php"); Table 8.2 lists the file names that should be assigned to the action pages. Table 8.2: Action Pages Page Object File

    Check Login a_login.php New User a_register.php Update User a_update.php Password Mailer a_password.php Note You will need to create your own i_connect.php file. The contents of this file are unique to each server, but each file must define three constants: declare CONNECTION as your connection string, DB_NAME as the name of the database, and DB as the database selection command.

    Check Login Page The Check Login page begins with a declaration of the error variables. These variables hold the text that will be displayed if form input is invalid. This particular page requires three error variables: strPassError, strEmailError, and strBadMatch. The text for these variables can be anything you wish if you don’t care for the samples given here. $strPassError = "The password you entered is not valid."; $strEmailError = "The e-mail address you entered is not valid."; $strBadMatch = "We could not locate your membership information. Are you sure that you have entered the information correctly? Remember the password is case sensitive. If you are still having trouble try using our password reminder."; Next, the o_Validate class is initialized and the user input is validated. If an error is returned, then the user is directed back to the Login page. $objValidate = new o_Validate; $objValidate->m_isFormatEmail($formEmail,$strPassError); $objValidate->m_isRangeLength($formPassword, 255, 3, $strEmailError); $strErrors = $objValidate->m_Alert("
    "); if ($strErrors != "") { header("Location: main.php?Page=login"); } If the user data is validated, then a database query checks the values against the database. If no results are found, then the sessErrors session variable is assigned the text of the error message and the user is redirected back to the Login form. $strTable = "membership"; $strSQL = "SELECT * FROM $strTable WHERE eMail=\"$formEmail\" AND password=\"$formPassword\""; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't execute query."); if (mysql_num_rows($objResult) != 1) { $GLOBALS["sessErrors"] = $strBadMatch; header("Location: main.php?Page=login"); } If the database does confirm the member information, then the sessMemberID session variable is set. session_register("sessMemberID"); $GLOBALS["sessMemberID"] = mysql_result($objResult, "memberID"); Finally, the sessRefer variable is checked to determine the page to display to the user. This session variable is only set by pages that require a login, such as the Member Update page. if ($sessRefer) { $strLocation = $sessRefer; session_unregister("sessRefer"); header("Location: $strLocation"); } else { header("Location: main.php?Page=home"); }

    New User Page

    The New User page also begins with the declaration of the error variables, although this page has many more of them than the Check Login page. $strPassError = "Your password must be at least 3 characters long."; $strEmailError = "The e-mail address you entered is not valid."; $strNameError = "Your name must be at least 3 characters long."; $strZipError = "Your zip code is invalid."; $strBirthError = "Your birthday should be in the format 12-31-2002."; $strPassMatchError = "Your passwords do not match. Remember, passwords are case sensitive."; $strAgeError = "You must be at least 18 years old to register on our site.";

    $strBadMatch = "Someone has already registered that e-mail address. If you have forgotten your password use the password reminder.";

    Next, the validation class is used to process the various user-submitted values. While this occurs, an options array is declared and populated with the contents of the optional form elements. This array will be used later to append the data to the memberDemographics table. $arrOptions = array();

    $objValidate = new o_Validate; $objValidate->m_isFormatEmail($formEmail,$strEmailError); $objValidate->m_isRangeLength($formName,10000,3,$strNameError); $objValidate->m_isRangeLength($formPassword,10000,3,$strPassError); $objValidate->m_isEquality($formPassword,$formPassword2,$strPassMatchError); $objValidate->m_Exists($formAgeCheck,$strAgeError); if (!empty($formZip)) { $objValidate->m_isFormatZip($formZip,$strZipError); $arrOptions["Postal Code"] = $formZip; }; if (!empty($formBirthday)) { $objValidate->m_isFormatDate($formBirthday,"mm-dd-yyyy","-",$strBirthError); $objValidate->m_isRangeDate($formBirthday,"mm-dd-yyyy","-","","",$strAgeError); $arrOptions["Birthday"] = $formBirthday; }; if (!empty($formGender)) { $arrOptions["Gender"] = $formGender; }; if (!empty($formNewsletter)) { $arrOptions["Newsletter"] = $formNewsletter; };

    $strErrors = $objValidate->m_Alert("
    ");

    If the validation produces an error, then the user is returned to the Registration page. Otherwise a database query confirms that the e-mail address is unique. if ($strErrors != "") { header("Location: main.php?Page=register"); } else { $strTable = "membership"; $strSQL = "SELECT memberID FROM $strTable WHERE eMail=\"$formEmail\""; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't execute query.");

    if (mysql_num_rows($objResult) > 0) { $GLOBALS["sessErrors"] = $strBadMatch; header("Location: main.php?Page=register"); } The data is appended to the database tables only if all of these initial processes complete without resulting in an error. First, the e-mail address and password go into the membership table, then the memberID value is selected and the optional data from the array is added to the memberDemographics table. The memberID is also assigned to the sessMemberID session variable. $strSQL = "INSERT INTO $strTable (memberName,eMail,password) VALUES (\"$formName\",\ "$formEmail\",\"$formPassword\")"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't execute query."); $strSQL = "SELECT memberID FROM $strTable WHERE eMail=\"$formEmail\""; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't execute query."); session_register("sessMemberID"); $GLOBALS["sessMemberID"] = mysql_result($objResult, "memberID");

    $strDemoTable = "memberDemographics"; while (list($strIndex, $strValue) = each($arrOptions)) { $strSQL = "INSERT INTO $strDemoTable (memberID,d_attribute,d_value) VALUES ($sessMemberID, \"$strIndex\", \"$strValue\")"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't execute query."); };

    Finally, the user is redirected to the Account Display page. header("Location: profile.php");

    Update User Page The Update User action page begins with the same error variable declarations as the New User action page. The form data validation, however, is significantly different. First, the password is only checked if a value has been submitted. Also, the arrOptions array is populated even if the form data is empty. This allows users to delete optional information. Additionally, this page requires that the user be logged in. $objValidate = new o_Validate; $objValidate->m_isFormatEmail($formEmail,$strEmailError); $objValidate->m_isRangeLength($formName,10000,3,$strNameError); if (!$sessMemberID) { session_register("sessRefer"); $GLOBALS["sessRefer"] = "update.php"; header("Location: main.php?Page=login"); }; if (!empty($formPassword)) { $objValidate->m_isRangeLength($formPassword,10000,3,$strPassError); $objValidate->m_isEquality($formPassword,$formPassword2,$strPassMatchError); $strPassword = $formPassword; } if (!empty($formZip)) { $objValidate->m_isFormatZip($formZip,$strZipError); $arrOptions["Postal Code"] = $formZip; } else { $arrOptions["Postal Code"] = ""; }; if (!empty($formBirthday)) { $objValidate->m_isFormatDate($formBirthday,"mm-dd-yyyy","-",$strBirthError); $objValidate->m_isRangeDate($formBirthday,"mm-dd-yyyy","-","","",$strAgeError); $arrOptions["Birthday"] = $formBirthday; } else { $arrOptions["Birthday"] = ""; }; if (!empty($formGender)) { $arrOptions["Gender"] = $formGender; } else { $arrOptions["Gender"] = ""; }; if (!empty($formNewsletter)) { $arrOptions["Newsletter"] = $formNewsletter; } else { $arrOptions["Newsletter"] = ""; };

    $strErrors = $objValidate->m_Alert("
    ");

    Next, the unique e-mail check is copied from a_register.php with a small change. The “AND memberID<>$sessMemberID” prevents the user’s own e-mail address from coming back as already in use. if ($strErrors != "") { header("Location: update.php"); } else { $strTable = "membership"; $strSQL = "SELECT memberID FROM $strTable WHERE eMail=\"$formEmail\" AND memberID<>$sessMemberID"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't execute query.");

    if (mysql_num_rows($objResult) > 0) { $GLOBALS["sessErrors"] = $strBadMatch; header("Location: update.php"); } Next, an if statement determines whether to update the password, or just the name and e-mail address. if (!empty($strPassword)) { $strSQL = "UPDATE $strTable SET memberName=\"$formName\", eMail=\"$formEmail\", password=\ "$strPassword\" WHERE memberID=$sessMemberID"; } else { $strSQL = "UPDATE $strTable SET memberName=\"$formName\", eMail=\"$formEmail\" WHERE memberID=$sessMemberID"; }

    $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't execute query."); The next step uses a two-dimensional array to hold the results of an SQL query. The d_attribute value is used as the first index of the array. The second index is numeric with the 0 element assigned the d_value value and the 1 element assigned the memberDemoID value. For example, say the following results were returned from the database: Gender, male, 10; Newsletter, true, 11; Postal Code, 55555, 12. The following values would be defined: arrDemo["Gender"][0] = male, arrDemo["Gender"][1] = 10, arrDemo["Newsletter"][0] = true, arrDemo["Newsletter"][1] = 11, arrDemo ["Postal Code"][0] = 55555, arrDemo["Postal Code"][1] = 10. $strDemoTable = "memberDemographics"; $strSQL = "SELECT memberDemoID, d_attribute, d_value FROM $strDemoTable WHERE memberID=$sessMemberID"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't execute query."); $arrDemo = array(); while ($objRow = mysql_fetch_array($objResult, MYSQL_ASSOC)) { $arrDemo[$objRow["d_attribute"]] = array(); $arrDemo[$objRow["d_attribute"]][0] = $objRow["d_value"]; $arrDemo[$objRow["d_attribute"]][1] = $objRow["memberDemoID"]; }; A while loop is then used to move through the arrOptions array. Inside the loop, if statements check the data against the values from the database. If the values are the same, nothing is done. If any value exists in the database and the user value differs, then an update statement is generated using the arrDemo array. If a blank value is submitted for something that does currently exist in the database, then a delete statement removes the value. If a value doesn’t exist in the database and a new, non- blank value is submitted, then an insert statement appends the data to the table. while (list($strIndex, $strValue) = each($arrOptions)) { if (!empty($arrDemo[$strIndex][0])) { if ($strValue=="") { $strSQL = "DELETE FROM $strDemoTable WHERE memberDemoID=\"" . $arrDemo[$strIndex][1] . "\""; } else if ($arrDemo[$strIndex] != $strValue) { $strSQL = "UPDATE $strDemoTable SET d_attribute=\"$strIndex\",d_value=\"$strValue\" WHERE memberDemoID=\"" . $arrDemo[$strIndex][1] . "\""; };

    $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't execute query."); } else if ($strValue != "") { $strSQL = "INSERT INTO $strDemoTable (memberID,d_attribute,d_value) VALUES ($sessMemberID, \"$strIndex\", \"$strValue\")"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't execute query."); };

    };

    Password Mailer Page The Password Mailer is the simplest of the action pages. The page first assigns the error variable, then validates the e-mail address against the database and for proper formatting. $strEmailError = "The e-mail address you entered is not valid."; $strBadMatch = "We could not locate your membership information. Are you sure that you have entered the information correctly?";

    $objValidate = new o_Validate; $objValidate->m_isFormatEmail($formEmail,$strPassError); $strErrors = $objValidate->m_Alert("
    "); if ($strErrors != "") { header("Location: main.php?Page=password"); } else { $strTable = "membership"; $strSQL = "SELECT eMail, password FROM $strTable WHERE eMail=\"$formEmail\""; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't execute query.");

    if (mysql_num_rows($objResult) != 1) { $GLOBALS["sessErrors"] = $strBadMatch; header("Location: main.php?Page=password"); } If the validation is successful, then the password is sent using the mail() function. This function takes three or four parameters: the e-mail address, the subject line, the content of the message, and any additional headers. Because the mail() function works with your local mail system to send e-mail, it is important to configure your php.ini with the correct SMTP settings. Contact your mail server administrator for assistance. $strEmailAddress = mysql_result($objResult, "memberID"); $strPassword = mysql_result($objResult, "password"); if (mail("[email protected]", "Password reminder - My Membership Interface", "Your password is:" . $strPassword, "Content-Type: text/txt\r\nFrom: [email protected]\r\nReply-To: [email protected]\r\nX-Mailer: PHP/" . phpversion())) { …

    Finally, if the mail function was successful the user is redirected to the Password Confirmation page. Otherwise the user is returned to the Password Reminder page. header("Location: main.php?Page=confirmation"); } else { $GLOBALS["sessErrors"] = $strBadMatch; header("Location: main.php?Page=password"); };

    Display Pages

    The display pages don’t involve any form elements or validation. These pages simply tie together the rest of the pages into a complete interface. Most of these pages don’t even have any dynamic content, allowing you to reuse the main.php file. Table 8.3 lists the files that should be created for each display page. Table 8.3: Display Pages Page Object Template Page Page Home main.php home.tpl Account Display profile.php profile.tpl Password Confirmation main.php confirmation.tpl Help main.php help.tpl

    Home Page

    The Home page should simply contain a welcome message and links to the important pages of the interface. In addition to the home.tpl file, you should also create a navigation.tpl and a footer.tpl for your interface. Figure 8.5 shows how the Home page might look. Here is the content of my home.tpl file: {{NAVIGATION}}

    Welcome to: My Membership Interface!

    Feel free to explore the interface. if you like it, maybe you'll even decide to sign up. Just click on the "Register" link to the left.

    If you have any questions or need assistance click on "Help".

    Thanks for visiting, come back soon.

    {{FOOTER}}

    Figure 8.5: The Home page

    Account Display Page

    This page is the most complicated of the display pages. Much of the content is dynamic and must be pulled from the database. In addition, this page requires that the user be logged in.

    The following elements should be in the profile.tpl file: {{NAVIGATION}} Name: {{NAME}} E-mail Address: {{EMAIL}} {{section Gender }}Gender: {{GENDER}}{{/section Gender }} {{section Zip }}Zip Code: {{ZIP}}{/section Zip }} {{section Birthday }}Birthday: {{BIRTH}}{{/section Birthday }} Newsletter? {{NEWS}} {{FOOTER}} The profile.php page is very similar to update.php. Both pages begin with a check for the sessMemberID session variable and then instantiate the o_Template class. include_once("global/i_connect.php"); include_once("global/i_template.php"); session_start(); if (!$sessMemberID) { session_register("sessRefer"); $GLOBALS["sessRefer"] = "profile.php"; header("Location: main.php?Page=login"); } else { $strFileName = "templates/profile.tpl"; $objTemplate = new o_Template; $objTemplate->m_LoadFile($strFileName); $objTemplate->m_AddElements("membership information");

    Then the three dynamic sections should be defined. $strGenderSection = $objTemplate->m_DefineSection("Gender"); $strZipSection = $objTemplate->m_DefineSection("Zip"); $strBirthdaySection = $objTemplate->m_DefineSection("Birthday"); Database requests and while loops are used to populate the arrResult array variable from the user’s information in the database. $strTable = "membership"; $strSQL = "SELECT memberName, eMail FROM $strTable WHERE memberID=\"$sessMemberID\""; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't execute query."); while ($objRow = mysql_fetch_array($objResult, MYSQL_ASSOC)) { $arrResult = array(); $arrResult["memberName"] = $objRow['memberName']; $arrResult["eMail"] = $objRow['eMail'];

    $strDemoTable = "memberDemographics"; $strDemoSQL = "SELECT d_attribute, d_value from $strDemoTable WHERE memberID = \"$sessMemberID\""; $objDemoResult = mysql_query($strDemoSQL,$CONNECTION) or die("Couldn't execute query.");

    while ($objDemoRow = mysql_fetch_array($objDemoResult, MYSQL_ASSOC)) { $arrResult[$objDemoRow["d_attribute"]] = $objDemoRow["d_value"]; } }

    Then a series of condition statements assign the content to the marker tags in the profile.tpl file. $objTemplate->m_AssignContent("NAME", $arrResult["memberName"]); $objTemplate->m_AssignContent("EMAIL", $arrResult["eMail"]); if (!empty($arrResult["Gender"])) { $strGender = $objTemplate->m_AssignContent("GENDER", $arrResult["Gender"], $strGenderSection); };

    if (!empty($arrResult["Postal Code"])) { $strZip = $objTemplate->m_AssignContent("ZIP", $arrResult["Postal Code"], $strZipSection); };

    if (!empty($arrResult["Birthday"])) { $strBirthday = $objTemplate->m_AssignContent("BIRTH", $arrResult["Birthday"], $strBirthdaySection); };

    if ($arrResult["Newsletter"] == true) { $objTemplate->m_AssignContent("NEWS", "Yes"); } else { $objTemplate->m_AssignContent("NEWS", "No"); };

    The last steps are to assign the sections, prepare the template, and output the content. $objTemplate->m_AssignSection("Zip",$strZip); $objTemplate->m_AssignSection("Birthday",$strBirthday); $objTemplate->m_AssignSection("Gender",$strGender); $objTemplate->m_PrepTemplate(); $objTemplate->m_Output(); Figure 8.6 shows how the Account Display page should look.

    Figure 8.6: The Account Display page

    Password Confirmation Page

    The Password Confirmation page is a simple page that tells users that their password has, in fact, been mailed to them. Figure 8.7 shows how the Password Confirmation page might look. Here is the content of my confirmation.tpl file: {{NAVIGATION}}

    Check your inbox, your password has been sent!

    {{FOOTER}}

    Figure 8.7: The Password Confirmation page

    Help Page

    The Help page should include frequently asked questions, technical contact information, and privacy reassurance. Your page can be as detailed or as simple as you wish. Figure 8.8 shows how the Help page might look. Here is the content of my help.tpl file: {{NAVIGATION}}

    We here at Dynamic Web Forms Inc. are always striving for perfection, but sometimes errors do occur and we apologize.

    Rest assured that we will never share your user information with anyone. In fact, we won't even use it ourselves. It is our sincere hope that no one, ever, will even contemplate the possibility of actually looking at the information you supply. We encourage you to keep your personal information to yourself.

    For assistance or questions please contact:

    My Membership Interface
    c/o Dan Ransom
    19 Bogus Address
    Nocity, CA 95678

    (555)555-5555

    [email protected]
    {{FOOTER}}

    Figure 8.8: The Help page

    Chapter 9: Completing the Membership Interface

    You now have a working membership interface, but it is not quite ready for prime time. You still have to take a few steps before unveiling your product. First and most important, you must make sure that it works. Second, you must make sure that it actually meets its objectives and the needs of the users. Only after you have performed these final checks can you invite your clients, coworkers, friends, and cousin Jim to partake of your programming excellence.

    Testing There’s an old saying: “If it ain’t broke, don’t fix it.” Here’s a new one: “If it is broke, you better fix it before anyone finds out.” As a programmer, nothing can hurt your pride, reputation, and sanity more than a buggy application. Don’t let this happen to you. Chapter 2 contains all sorts of information about usability and proper testing procedures. Use this information to your advantage. For the membership interface, test each form page and the display pages on multiple browsers and systems. Try the system without accepting cookies, or without JavaScript enabled. In other words, go beyond just trying to anticipate what the users will do—try to break the system. If you can’t break it, great! But if it does break, consider your options and ask yourself the following questions: § How badly did the system break? Is it terminal? § How likely is the user to replicate the break? § How difficult would it be to fix the problem? § Is it worth rewriting the code? § Is the break acceptable to the client? § How is the break presented to the user?

    The following section contains some useful information on how you might try to break the membership interface. Even if you decide not to fix the errors you will, at least, be aware of the problems so as not to be caught off guard when they are reported to you. Note The membership interface presented in this project probably will break during some of these tests. That is to be expected. This interface is a starting point for your own application and is not intended as a final product. Many of the issues raised during the testing of the interface will be addressed in later projects. The Login Page

    Here are just a few of the scenarios that you should consider for the Login page. For each browser, try to enter the following information and click submit, then turn off JavaScript and try it again. § A correct e-mail address and a correct password. § A properly formatted e-mail address that is not in the database and a correct password. § An improperly formatted e-mail address (include special characters) not in the database and a correct password. § An improperly formatted e-mail address (include special characters) that is in the database and a correct password. § An e-mail address more than 255 characters long. § A correct e-mail address and an incorrect password. § An incorrect e-mail address and correct password. § A correct e-mail address and a short password. § A correct password and no e-mail address. § An incorrect password and an improperly formatted e-mail address. § A password that includes special characters and capitalization and a correct e-mail address.

    Did you get the results that you expected? Were the error messages descriptive enough for the user to solve the problem? If not, then you should consider reworking a portion of the code to resolve the issue.

    The Registration Page

    Below are some suggestions for testing the Registration page. For each browser try to enter the following information and click submit, then turn off JavaScript and try it again. § All information complete and properly formatted. § Only the required information, properly formatted. § Only the optional information, properly formatted. § The optional information and some of the required information. § All the required information and some of the optional information. § Properly formatted required information and improper optional information. § Properly formatted optional information and improper required information. § Some proper and some improper required and optional information § No information at all. § Offensive or improper language. § All information improperly formatted.

    The Membership Update Page

    Here are some scenarios you can use to test the Membership Update page. For each browser, try to enter the following information and click submit, then turn off JavaScript and try it again. § No changes. § All information is changed. § All the required information is changed. § All the optional information is changed. § All the required information and some optional information is changed. § Some of the required information is changed. § Some of the optional information is changed. § All the optional and some of the required information is changed. § The same values as those used in the registration page testing are entered. § Information is changed on a browser that does not accept cookies. The Password Reminder Page

    Here are a few suggestions that you should consider for the Password Reminder page. For each browser try to enter the following information and click submit, then turn off JavaScript and try it again. § The e-mail address is correct. § The e-mail address is improperly formatted. § The e-mail address is formatted correctly, but it is not in the database. § The e-mail address is correct, but the actual e-mail box does not exist. § The e-mail address is correct, but the user uses a program to submit the form 10,000 times in one day. § The e-mail address is correct, but the database does not have a corresponding password. § The e-mail address is correct, but the password contains text that might be filtered by a “net -nanny” type application.

    The Display Pages

    These pages do not need to be tested for functionality, but you should still check the pages for readability, uniformity, and discrepancies. As with the form pages, you should check these pages on as many browsers as possible. Here are a few considerations to take into account: § Do all the pages look similar enough to be recognized as part of a single application? § How does increasing or decreasing the font size change the appearance of the pages? § Are display elements such as tables, layers, or style sheets uniform across browsers? § How do the pages look with images turned off? § Are the navigation elements easy to use? § How will other people react to the interface?:

    Personalizing the Interface

    Now that you know whether the interface works, you should determine if it meets the requirements of the client, the interface architect, and the users. The next few pages present some possibilities and ask a few questions that you might consider when attempting to personalize or expand the interface to meet your project’s specific needs. You can attempt to develop your own solutions or you can continue on to the next projects, where many of these issues are discussed in detail.

    The Login Page

    With the current interface users must log in every time they visit the site. Consider the changes necessary to allow the client computer to remember the login information. Most likely, this would involve the use of semipermanent cookies rather than sessions. Think about the advantages and disadvantages of such a system. Would you need a Log out page? Should you automatically fill in the e-mail address on the Login form? Should you give the user the option of a permanent login? How would you handle both systems simultaneously?

    The Registration Page

    The current membership interface asks for four pieces of required information and four pieces of optional information. Consider what you would need to do if the information collected had to be changed. Maybe additional optional information would be needed, or maybe a unique username would be used rather than the e-mail address for validation. Can your form handle the addition of many new form elements? What changes are required if some elements go away? What if you wanted to generate and e-mail passwords to users rather than letting them choose their own? What if you wanted to confirm the actual existence of an e-mail box before you accepted the registration information?

    The Membership Update Page

    Your interface allows the user to update any of the member information. Consider the possibility that some information shouldn’t be changed. For the Registration page you considered adding a unique username field, and you may not want to let users change this value.

    How would your interface work without cookies? Could you submit the memberID through a hidden field? Would you want to? Even with session cookies is it possible for a malicious user to update another user’s information?

    If you prevented users from updating e-mail addresses in this form as a security measure, what would happen to users whose e-mail address changes?

    The Password Reminder Page

    Of all the form pages, the Password Reminder page seems most ripe for abuse. Consider the options you have for preventing malicious use of this interface element. What if you used a system for password reminders rather than e-mailing the password? What if you didn’t have passwords at all and used a cookie system exclusively? What challenges to usability would this present? Could you give users the option of securing their passwords so that passwords would never be revealed, even to their owners, thus preventing unwanted reminders?

    Project 1 Summary

    In this project you learned the basics of modularization by creating the most common types of online forms: membership forms. Modularization involves separating out the various components of an interface into distinct elements. These elements can then be reused over and over, thereby greatly increasing the efficiency and organization of any Web-based interface. The concepts and techniques used in this chapter will be applied to the remaining projects.

    Part III: Project 2: Creating Catalog Forms

    Chapter List Chapter 10: User Interface Design Chapter 11: Designing a User Interface Chapter 12: Creating the Online Catalog Interface Chapter 13: Completing the Catalog Interface

    Project 2 Overview One of the most useful applications of Web forms is generating an online catalog. This catalog might be used as a storefront, an online brochure, or as part of a larger inventory system. Creating, modifying, and removing catalog items are accomplished through a series of online forms interacting with a database. The catalog data can then be displayed in any number of ways using the template system from Project 1.

    This project walks you through the creation of a dynamic catalog maintenance interface. The particular purpose of your catalog interface is up to you. Th e finished project will allow you to quickly apply a shopping cart application or integrate an inventory system. Along the way, you’ll learn about user interface design, communicating between client and server scripts, and using cookies to store information.

    Chapter 10: User Interface Design

    Overview As you learned in Chapter 2, user interface (UI) design is the creation of a process or set of tools for the user to communicate with a program or application. For Web forms this interaction can be as simple as clicking a submit button or as complex as typing a series of commands into a text field.

    In the first project, I compared creating a modular interface to building a plane with Lego blocks. On that basis, user interface design is comparable to creating the cockpit controls. For each function of the plane—turning, accelerating, braking, changing altitude, and all the rest—a tool or a set of controls must be assigned. The pilot manipulates these controls to get the plane to do what the given situation needs. Anyone who can easily and confidently access and work each control can fly the plane.

    Similarly, the users of your Web forms will need to manipulate the form’s control elements in a manner that is comfortable and predictable. Luckily, you will be able to take advantage of the standards created by the UI developers who have preceded you. Computer users are experienced with using forms, so you only have to meet their expectations, not shape them. In this project you will use JavaScript and other UI elements to apply this experience to your interface.

    How Does User Interface Design Apply to Web Forms? Perhaps the better question is: How doesn’t UI design apply? Each and every form element, control structure, and validation function is part of the user interface. A submit button allows the users to communicate that they are ready to send the form information. The placement and appearance of a form element defines the various areas of content within the interface. The type and amount of data accepted in a text field sets the boundaries of acceptable values for the user. To learn how to best apply the concepts of UI design to your Web forms you need to take a look at how others have implemented user interface elements.

    Command Line Interfaces In the computer age, the earliest interfaces were all of the command line type. Command line interfaces take specially formatted text input and perform actions based on the information supplied. These types of interfaces have great power and flexibility, but this comes at a price. Users have to understand a cryptic and often poorly documented input standard before they can perform even the simplest of functions. Conversely, someone who has mastered the technique can perform very complex applications with a single line of text. Figure 10.1 shows an example of a command line interface in action.

    Figure 10.1: The Darwin terminal for MacOS X Typical command line input formatting involves the judicious use of special characters such as -, +, and *. For example, the Linux command: mv -f -b memo* /usr/local/backup renames all the files in the current directory that begin with "memo" to the /usr/local/backup folder. The -f option tells the move command to overwrite any existing files in the new directory. The -b option creates a backup of any files thus overwritten. In Web forms, command line interfaces are usually reserved for a very special use: search fields. Many of the most popular search engines, including Google and Excite, support a type of command line functionality called Boolean searches, which involve using special characters or keywords to narrow search results based on the submitted commands. Here’s a breakdown of the most common Boolean search options: § OR. This option is typically turned on by default on search engines. OR allows one or more of the search terms to result in a match. For example, a Web page that includes information on dog medication would show up on an “herbs OR medication” search. § AND. Often the plus sign (+) is used rather than the keyword AND. This option specifies that all search terms must be found. The dog medication page would not be returned if "herbs AND medication" was the search phrase. However, a search for "dog AND medication" would find the site—and others that included both the search terms. § NOT. This option is sometimes written as -, !, or ANDNOT. NOT requires that the search term not appear in the results. A search for "medication NOT dog" will result in all pages that include "medication" but not "dog" among their terms. § NEAR. This option requires that the search terms be located within a defined proximity to one another. Most search engines that support this option define the proximity for you, but some use the NEAR/# notation to allow you to define the maximum number of words that may appear between the search terms. For example, a search for “all NEAR/3 men” would find a page that contained the phrase “all the king’s men” or “men are all kings”—NEAR does not define the order of the terms. § EXACT. This option almost always appears as a set of quotation marks. The EXACT option is often called a phrase. EXACT defines the order and content of the search terms in the results. If the phrase is not contained in total on a page, then that result is excluded. § WILDCARD. The typical wildcard character is ? or *—representing any single keyboard character. For example, a search for "mic?" would find pages that contain "mick," "mice," and "michael." § GROUP. Most often parentheses () are used to group or nest sets of search terms. Grouping allows you to apply other Boolean search options to more than one pair of terms. For example, the search for "(car OR auto OR automobile) AND horn" would find pages that contain "car horn," "auto horn," or "automobile horn." Note The term Boolean search is a bit of a misnomer. While the principal options of this search type are based on Boolean logic, the advanced Boolean search combines elements of regular expressions and proximity functions to go beyond the limitations of the form. Creating a Boolean search engine requires some powerful string manipulation skills, both to parse the search statement and to compile the database query. For the typical Web site this is overkill, but the added functionality can work for complex and detail- specific searches. An example of a Web site with the primary purpose of providing search results from a large database of information is the U.S. Patent Office’s site (http://patft.uspto.gov/netahtml/search-adv.htm). Searching patents is a highly technical and detail-driven process. The Boolean search options available through the Web page allow experienced users to speed up and at the same time pinpoint their searches. Still, Boolean searching isn’t for the common user. These days, most search engines offer Boolean searches that run from a form interface. At Google, for example, the user can fill out a form on the Advanced Search page and the corresponding Boolean notation is automatically added to the search string. Figure 10.2 shows the advanced search page for the Google search engine. This gives users most of the flexibility of a Boolean search without the difficulty of learning the detailed commands.

    Figure 10.2: The Google Advanced Search page

    Graphical User Interfaces Apple popularized the use of GUIs (graphical user interfaces) in operating systems with the release of the Macintosh in 1984. Since then, nearly all operating systems have included some GUI elements. Application developers have followed suit, either developing their own GUI or taking advantage of the GUI extensions made available through the operating system. Graphical user interfaces employ pointer control devices, such as a mouse or a touchpad, graphical representations of directories or files, such as icons or buttons, and frames or windows containing program-specific content. Of course the most recognizable GUI is Microsoft’s Windows. Figure 10.3 shows the Windows 2000 GUI.

    Figure 10.3: The Windows 2000 GUI

    Web forms are a type of GUI. Buttons represent commands, such as submit and reset, and selectors allow the user to graphically choose an option rather than typing it into a command line. Like GUI operating systems, Web forms can take advantage of the simplicity and improved organizational power of a graphical interface.

    Command line interfaces are limited in that they are linear. Each command is presented and processed in a vacuum; the preceding and following commands can’t directly affect one another even when they’re all required for a given operation. GUIs, however, use a two-dimensional plane to organize content. This allows for the presentation of multiple commands simultaneously. They are still processed in a linear fashion, but the GUI format allows for at least the appearance of multitasking.

    A number of GUI design principles can be applied to Web forms. § Group similar content. Whether through location, color, style, or appearance, you should separate form elements based on similarities in content, function, or application. Web forms and other GUIs can sometimes present the user with so many options it’s hard to figure out what to do. Grouping elements allows the user to quickly prioritize the input options. § Flow with the user. European and American users read left to right, top to bottom. Web forms designed for these users should align themselves in a similar fashion, with the most important elements in the top left. Other languages, such as Arabic or Korean, use different reading orientations. Find the flow that works best for your intended audience and create your interface to match. § Provide redundant functionality. Using a GUI can be a very personalized experience. Some users prefer keyboard shortcuts to mouse clicks or vice versa. Providing multiple ways of performing the same function allows the user to find a comfortable rhythm. § Link functionality to context. Don’t make the user search for a button or control element. As the user moves through the interface, the functional elements should be as close as possible in logistical terms to the conceptualization of the need for those functions. As an example, when the user is ready to submit the form, the submit button should be right there at the end of the form text, not further down the page or hidden in a corner.

    Form Interfaces As GUI operating systems and applications grew more complex, users who changed systems were again left in a position where they had to learn the specifics of a new interface or sacrifice flexibility and power. Form interfaces, however, have changed very little since they were first introduced. A user selecting a background picture on the MacOS 1.0 would use a select box familiar to users of Microsoft Windows XP. It is the longevity and familiarity of form elements that help make them so easy to integrate into any application. Figure 10.4 shows an example of a form interface.

    Figure 10.4: A form interface from As I mentioned earlier, forms are a type of GUI, but forms include a number of user interface elements that are different from other GUIs. Of course the standard form elements—text boxes, radio buttons, and select boxes—are all graphical interface elements common to forms. Additionally, a number of other form-specific elements are standard for non-Web forms. Only recently have these standards been applied to Web forms.

    Here are a few user interface elements customary in forms: § Tab indexing. Moving through a form can be accomplished by using the mouse or another control device such as a trackball, stylus, or touch screen. However, some users dislike these options or have a disability that makes them impossible to use; such users can navigate through a form using the Tab or other indexing key. The form designer can program the sequence of the form elements or can leave the default sequence. This is usually the order in which the elements appear in the code. § Keyboard shortcuts. Sometimes called hotkeys, keyboard shortcuts are a way for the user to activate form controls without using the mouse. Typically, form elements with shortcuts are distinguished by means of a standardized notation such as underlining the shortcut character within the button name. § Labels. The user can rarely be expected to accurately guess the nature of a form element. Labels are text captions that appear near the form element and describe the element’s function. Although mostly descriptive, labels can be functional as well. When a user wishes to select a check box element, for example, many forms allow the user to click either the label or the element itself. § Primary index. The user should not need to click or tab to move the insertion point to the first form element on the page. When the form appears, the insertion point should already be at the primary form element, whether that element is the first item on the page or further down.

    The User Interface System

    A user interface system is a standardized way of applying elements to an interface. The following section describe the design and implementation of a user interface system. Your UI system will include many of the modular elements from the first project. This will not only keep your code consistent, it will also improve the flexibility of the UI system overall.

    Designing the User Interface System When developing a user interface for the Web, you will be able to take advantage of the many user interface elements already built into modern browsers. The following UI elements are available in IE 5+ and Netscape 6+: tabindex, fieldset, legend, label, and accesskey. Some of these elements can also be recreated, in part, using JavaScript or CSS for older browsers such as Netscape 4. Primary indexing is available in most JavaScript-enabled browsers. tabindex The tabindex element is assigned as an attribute to each form element and given a positive integer value. As the user presses the Tab key the insertion point moves from the current location to the element with the next higher tabindex value. After the largest tabindex value has been reached, any elements without tabindex values are selected in standard order. After the last element on the page is selected the lowest tabindex value begins the cycle again. Use this code to test out the tabindex attribute:

    Index 5:
    Index 2:
    Index 4:
    Index 3:
    Index 1:
    Note The tabindex attribute can also be assigned to links and image maps. In this way you can define how the user navigates through the entire page using the Tab key, not simply through the form. Although you can’t replicate the effect of the tabindex attribute using JavaScript, you can force a specific tab order. The following script will create the same effect as the tabindex attribute used earlier, but take a look at what happens when you click on a form element that is out of sequence.



    fieldset and legend These attributes are used to segregate form elements into groups. The fieldset tag creates a border around the included tags. legend assigns a descriptive name to the form elements. This name appears as text within the top of the border. While it is entirely possible to replicate this functionality using tables or style sheets, the simplicity of fieldset and legend can’t be duplicated. Figure 10.5 shows how the fieldset and legend tags appear in a compatible browser. Here is the HTML used to create the page:
    Legend
    Text 1:
    Text 2:
    Text 3:

    Figure 10.5: The fieldset and legend tags in action To duplicate the effect for Netscape 4, you can apply style attributes rather than use the fieldset and legend tags. Here is an example: Legend

    Text 1:
    Text 2:
    Text 3:
    With a bit of JavaScript you can create a form that uses either the style attributes or the fieldset and legend tags, depending on the browser.


    Legend
    Text 1:
    Text 2:
    Text 3:
    label The label tag allows you to assign text to a specific form element. If a user clicks the label text, the supporting browser acts as if the user had clicked on the element itself. The label tag uses the for attribute to define the corresponding element. Most browsers support using the label tag only with radio buttons and check boxes. To use the label tag, precede the form element code with the label code and match the for attribute to the id attribute of the element this label corresponds to.

    With some JavaScript you can create a backwards-compatible version.

    accesskey The accesskey attribute allows you to assign a specific key combination to a form element. When the user presses the keys the form element is activated, just as if the user had clicked on it with the mouse. This clicks buttons, moves the insertion point into text fields, and checks or unchecks selectors. Browsers work with the operating system of the user to define the key combination required.

    For example, this form element: would require an Alt+p keypress for a Windows user, while a Macintosh visitor would use Option+p. The accesskey attribute was only implemented in the latest versions of the common browsers. Unfortunately, there is no way of completely simulating the effect with JavaScript, although you can come close. The following script works on Netscape 4 to capture key presses by the user: Note Netscape 4 uses event modifiers and other JavaScript properties to determine whether modifier keys such as Alt, Option, or Ctrl are pressed. However, the actual implementation of this functionality is buggy, preventing scripts from capturing these keys in combination with others. Additionally, the key-capture function only works when the insertion point is focused on the document body itself, not on a form element or link.

    Primary Index

    Most of the time you will want the user to begin entering input in the first form element on the page. Sometimes, though, you will want the insertion point to start in a form element further down the page. Often this is because you have multiple forms on a single page or because you need to help the user recover from an error in form input. In these cases you will want to assign a form element such as the primary index. This will cause the page to load with the insertion point in the specified form element. Unlike the other UI elements, the primary index is not assigned by using an HTML tag or attribute. Instead, this element consists of a JavaScript function that runs as the page loads. The function calls the focus() method of the primary form element. The function can reference the element by name or number: document.formLogin.passPassword.focus(); document.forms[0].elements[1].focus(); Generally, the primary index function is called using the onLoad attribute of the BODY tag.

    Implementing the User Interface System

    Developing a user interface system is only the first step. You still have to implement it. The focus of your implementation should be consistency. User interface systems are valuable only if the user can use them effectively. Each and every form, from the shortest to the longest, should use the same set of UI controls—and each individual control should work in a consistent manner from form to form. Managing Usability and Functionality As you’ve discovered, it isn’t possible to implement a single all-purpose UI that would work for every browser and user. Instead you must develop similar functionality for multiple clients, using code such as the JavaScript functions described earlier. It might have occurred to you that you could simply attach multiple user interface elements, such as the accesskey attribute and the corresponding f_accessKey JavaScript function to each form element. However, you might not be doing your users any favors. Notice that most of the JavaScript functions work differently from their attribute equivalents. These differences may be slight or significant from a programming point of view, but from the end users’ perspective any difference in functionality is significant. The reason is that any efficient and responsive user interface requires that you give the users a clear description of the UI elements available to them. Whether in the form of a help page, context-sensitive help, or simple visual clues, this assistance is critical to a successful UI system. But when a single help system tries to explain varied functionality you risk confusing the end user. Essentially, you wind up giving mixed signals. With some technical writing skills you may be able to illuminate the situation for most users, but there will always be some who just won’t ever get it.

    To resolve this problem you will need to implement a multipart user interface system that not only supports multiple platforms, but also provides the single best set of functionality for each client and nothing additional. The first step is to determine the nature of the client environment. Both JavaScript and server-side scripts provide the ability to gather this information. JavaScript uses various properties of the navigator object to determine client information. Table 10.1 lists the most common navigator properties. Table 10.1: JavaScript Navigator Properties Property Description Example

    appCodeName The code name associated with the Table 10.1: JavaScript Navigator Properties Property Description Example browser

    appName The name of Netscape, the browser Opera application

    appVersion The version 4.72, 5.5, number of 6.01 the browser

    userAgent The Mozilla/4.72– browser’s (Windows standard NT 5.0; I) header information

    platform The client Win32, operating MacPPC system

    Using these properties and some string manipulation you can determine most of the required client information. For example, if you wanted to display different welcome messages depending on the visitor’s browser, you could use the following script: var strNavigator = navigator.appName; if (strNavigator != "Microsoft Internet Explorer") { var strVersion = navigator.appVersion.substr(0,3); } else { var strVersion = navigator.appVersion.substr(navigator.appVersion.indexOf("MSIE") + 5,3); } document.writeln("Hello, you are using " + strNavigator + " version " + strVersion + ", come on in!
    "); PHP, like most server-side script, uses an environmental variable to store information about the client. This variable is HTTP_USER_AGENT. As the only information about the client that the server has access to is the header sent along with each page request, this value is essentially the same as what you would get from the JavaScript navigator.userAgent property. Additionally, PHP has a function called get_browser() that can be used to determine the capabilities of the client’s browser. The get_browser() function relies on a file called browscap.ini. This file holds information about hundreds of client browsers and operating systems. This resource was originally created for ASP, but the developers of PHP quickly saw the advantages and added browscap.ini functionality into their later versions. If you will be relying on the get_browser() function you will need to keep the browscap.ini file up to date with all the latest browsers. A copy of browser.ini comes with most Windows servers, but you can download a recently updated file at http://www.aspsimply.com/info/infoserver.asp. You will need to add the following lines to your php.ini file to point to the browscap file: browscap = \directory\browscap.ini The get_browser() function returns an object full of name-value pairs. You can use this object to determine the properties of the browser. When the get_browser() function is called, the browscap.ini file is loaded and searched using the information from the HTTP_USER_AGENT value. If a compatible record is found, the resulting information is assigned to the returned object. The more detailed the information in the browscap.ini file, the more specific the results of get_browser() can be. Here’s an example of a record from browscap.ini: [Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)] parent=IE 6.0 browser=IE Version=6.0 cookies=True javascript=True ActiveXControls=True platform=Windows 2000 The following script could then be used to obtain information about a user where the HTTP_USER_AGENT value matches the pattern "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)": $objBrowser = get_browser(); echo $objBrowser->parent . "
    "; echo $objBrowser->browser . "
    "; echo $objBrowser->version . "
    "; echo $objBrowser->cookies . "
    "; echo $objBrowser->javascript . "
    "; echo $objBrowser->activexcontrols . "
    "; echo $objBrowser->platform; Modularizing the User Interface System As you can see, keeping your browscap.ini file up to date is essential for the get_browser() function to work. You might also have noticed that the browscap.ini file is quite large. As this function requires the server to load the entire file into memory, you won’t want to use this function on every page. Instead, you should define global variables with the required information. These variables can then be called quickly from any page on the site without the added overhead.

    Here’s how you might add browser detection to the login page from the first project. First, you will need to change the “Help” link in navigation.tpl. Help

    Next, add the following lines to the main.php file: if (!$sessBrowser) { $objBrowser = get_browser();

    session_start(); session_register("sessBrowser"); session_register("sessVersion"); session_register("sessJavascript"); session_register("sessPlatform");

    $GLOBALS["sessBrowser"] = $objBrowser->browser; $GLOBALS["sessVersion"] = $objBrowser->version; $GLOBALS["sessJavascript"] = $objBrowser->javascript; $GLOBALS["sessPlatform"] = $objBrowser->platform; }; Then you will need to add additional code to the m_AddElements() method in the i_template.php file: if (($GLOBALS["sessBrowser"] == "Netscape") && ($GLOBALS["sessVersion"] < 5)) { $this->m_AssignContent("HELP_PAGE","helpNS"); } else if (($GLOBALS["sessBrowser"] == "IE") || (($GLOBALS["sessBrowser"] == "Netscape") && ($GLOBALS["sessVersion"] >= 5))) { $this->m_AssignContent("HELP_PAGE","helpIE"); } else { $this->m_AssignContent("HELP_PAGE","help"); }; You can continue to add else if statements to handle as many help pages as you need. Each of the help pages is created similarly to help.tpl, but includes instructions about user interface elements specific to the client. Note Be sure that you have all the latest browsers in your browscap.ini file. For example, I tried the function in this section on my version of Netscape and it appeared to be broken. Then I checked my browscap.ini file and found that there wasn’t a record for Netscape 4.72—only 4.71 and 4.70. After I added a new record, the script worked perfectly. Using these techniques, you will be able to create almost any browser-specific functionality you want. The following example creates a JavaScript version of the label attribute for an early version of Netscape. In this case, the selectElement() JavaScipt function is located on a separate file. $strLabelPair = $objTemplate->m_DefinePair("Label"); if (($GLOBALS["sessBrowser"] == "Netscape") && ($GLOBALS["sessVersion"] < 5)) { $strLabelBegin = ""; $strLabelEnd = ""; } else { $strLabelBegin = ""; }

    $strLabel = $objTemplate->m_AssignContent("LABEL",$strLabelBegin, $strLabelPair); $strLabelRight = $objTemplate->m_AssignContent("LABEL_CLOSE",$strLabelEnd, $objTemplate->p_arrPaired["Label"][1]); $objTemplate->m_AssignPair("Label", $strLabel, $strLabelRight; Whether you use server-side variables or JavaScript navigator objects for browser detection is mostly a matter of individual preference. As you’ve seen, both solutions have advantages and drawbacks. Client-side solutions are often easier to code and maintain, but using the browscap.ini file lends a great deal of flexibility and power to your browser detection scripts.

    Chapter 11: Designing a User Interface

    For the second project, you will create an online catalog with brochure and shopping cart pages as well as an inventory maintenance interface. In this chapter you will create the interface development plan, building on what you learned in Project 1. This project is significantly larger and more complicated than the first one. Here you’ll learn how to increase a project’s complexity, without significantly increasing time, effort, or resources.

    Site Architecture The catalog interface has the same basic site architecture as the member forms created earlier. It has three basic types of pages: form pages, action pages, and display pages. This project also introduces an additional distinction, though, between public and private pages. Public pages are what you might expect; any Web visitor may use public pages, without requiring a login. Private pages are reserved for the database management team or another specifically designated production individual. Private pages allow qualified users to change proprietary information that is out of reach of the general public. Private pages always require a login.

    Form Pages

    The form pages for this project will determine the ultimate flexibility of the catalog maintenance interface. It is entirely possible to create dozens of form pages covering each and every minute detail of product information, so the trick is to pick out the ones that will be most useful. Think about an online catalog for yo-yos. It would be possible to set up form pages that let the management team declare yo-yo–specific properties for each item: string length, string gauge, weight, radius, external width, bar width, rotational spring, sleeper rating, ease factor, tightness. As you create your online catalog feel free to include any number of additional elements that are specific to your client’s products, but don’t overwhelm the users with unnecessary options that will never actually be used.

    The form pages for this project will be limited to a few general product descriptors. Here’s a breakdown of the form pages you’ll be setting up: § Login Page. This page bridges the gap between the public and private pages. While this page is technically public, it is the gateway to the private pages throughout the rest of the interface. This page will work similarly to the Login page from the member forms, except that rather than using e-mail addresses it will use user names instead. § Item Selector. This private page will allow the user to select a catalog item for modification. The page will consist of a listing of current items and a set of control elements that will allow the user to add a new item, update an existing item, or delete a selected item from the database. This page will use the process item action page. § Add/Edit Item. A single private page will be used for both adding and updating item content. Values will be gathered for item name, product number, short and long descriptions, associated images and categories, price, weight, and size. Item name, short description, category, and price will all be required fields. Also, the short description will be checked for length (less than 200 characters). The category and image fields will be filled using a control element that will call subpages: category selector and image selector respectively. A submit button will send the page data to the Update Item action page. § Delete Item. The only form element on this private page is a confirmation button. The particular item’s vital statistics will be displayed along with a submit button. This form will use the Remove Item action page. § Image Selector. This private page will be used only as a subpage to the Add/Edit Item and Category pages. This page will contain a selection of the current images available from the database and control elements that will link to the Add/Edit image and Delete Image pages. The page will use the Process Image action page. § Add/Edit Image. Similar to the Add/Edit Item page, this private page will be used for both adding and updating image information. This form will require an image name, height, width, and alt value. The border value will be optional. Additionally, if it is used to add a new image, a file upload is required. The height, width, and border values will be validated to be integer and the file uploaded must be a .gif, .jpg, or .png. A submit button will send this page’s data to the update Image Action page. § Delete Image . This is another private page, similar to the Delete Item page. It contains only a single confirmation button and the associated image values. This page uses the remove Image Action page. § Category Selector. Another selector page, this is a private form page that allows the user to select a category for processing. This page is available as both a subpage of the Add/Edit Item page and as a stand- alone page.

    A selector will allow the user to choose a category to edit and control elements will be provided to allow for the addition or removal of a category. This page uses the process Category Action page. § Add/Edit Category. This private page handles both the addition and updating of categories. This form will require the field’s name and description. Additionally, a control element will allow the user to assign an image to the category through the Image Selector page. This page uses the Update Category action page. § Delete Category. The last of the delete pages, this private form will allow the user to confirm the deletion of a category. The category values will be displayed along with a submit button. This page uses the Remove Category Action page. § Shopping Cart. The first of the public pages for this project, the Shopping Cart page allows users to change product quantities in their shopping cart. Using this page, a user could request multiple copies of an item or remove a selection from the cart altogether. This form will contain a quantity field for each item. Two control pages will be used on this page, one that allows the user to update the contents of the cart and another that lets the user proceed to the checkout. This page uses the Update Cart Action page. § Checkout. This is a public page that allows the user to enter purchase information and confirm the sale. This form will require the following fields: name, address, city, state/province, postal code, country, e-mail, credit card type, card number, and expiration date. Nonrequired fields will allow for phone number and fax number. The e-mail and card number fields will be validated for format. A submit button will send the information to the purchase action page.

    Action Pages

    As with form pages, the number of action pages has increased significantly from the previous project. Despite this increase—or more accurately, because of this increase—a number of the action pages perform multiple processes. Having a single page perform multiple processes is a trade-off between simplicity and economy. On one hand, you only have to edit a single file for multiple functions, but on the other hand that single file can become lengthy and overly complicated. For this project any single action page will handle no more than three processes. The following is a list of the action pages you’ll be creating in this project: § Check Login Page. This page validates the user name and password against the database of authorized users. An incorrect match sends the user back to the Login form. A change from the Check Login page in the membership interface is that a correct match sets a cookie rather than a session variable. § Process Item. This page checks the name of the submit button pressed and either calls the Add/Edit item or Delete Item page. If the item is to be updated or deleted, then the item key value is passed in the URL. § Update Item. This page either adds a new item record to the database or, if an item key is supplied, edits an existing record. Missing values for item name, short description, category, and price, or an overly long short description (more than 200 characters) will result in a ret urn to the Add/Edit Item page with an error message. Correct information will return the user to the Item Selector page. § Remove Item. This page processes a request for the deletion of an item. If both the item key and the name of the submit button are passed, the item is deleted from the database and the Item Delete Confirmation page is displayed. Otherwise the Delete Item Form page is returned with the appropriate error message. § Process Image. The Process Image action page is analogous to the Process Item page. The functioning is the same, except that the pages returned are either the Add/Edit Image or Delete Image pages. The image key is passed in the URL if the image is to be edited or deleted. § Update Image . This page will add a new record to or update an existing record of the image database. A missing image name, height, width, or alt value, or an improper border value or file upload will result in a return to the Add/Edit Image page. If the information is acceptable, then the referring page is displayed. § Remove Image . Akin to the Remove Item page, this action page accepts the image key and the name of the submit button. If the values are acceptable, the Item Delete Confirmation page is displayed, otherwise the user is returned to the Delete Image page. § Process Category. Another processing page, this file returns either the Add/Edit Category or Delete Category pages. The category key is passed in the URL if it is to be edited or deleted. § Update Category. This page will add a new record to or update an existing record of the category database. If the category name or description is missing, the page will return the Add/Edit category page. If the information is acceptable, then depending on whether the page was used as a subpage, the referring page is displayed or the user is returned to the Category Selector page. § Remove Category. Similar to the other remove pages, this action page accepts the category key and the name of the submit button. If the values are acceptable, the Category Delete confirmation page is displayed, otherwise the user is returned to the Delete Category page. § Update Cart. This action page takes the item keys and quantities from the Shopping Cart page and modifies a cookie on the client to store the new information. This page always returns the Shopping Cart page. § Purchase . The last action page requires the item key and quantity for each item in the checkout form. Furthermore, the name, address, city, state/province, postal code, country, e-mail, credit card type, card number, and expiration date are all required fields. The e-mail and card number are validated for format and the expiration date is confirmed to be in the future. Valid data will result in the Order Confirmation page being displayed. Errors will return the user to the Checkout form.

    Display Pages

    The following is a list of the display pages for the catalog interface: § Help Page (General). This public page will contain helpful information about the form, contact information, and links to technical support. § Help Page (Netscape). This public page contains all the information in the general Help page, with the addition of a description of the user interface elements available to Netscape users. § Help Page (IE). This public page will be the same as the Netscape Help page, but will contain information about the UI elements presented to users of Internet Explorer and Netscape 6+. § Maintenance Home Page . This private page will contain links to the various other private pages accessible to the logged-in user. § Item Delete Confirmation. This private page confirms that an item has been removed from the database. § Image Delete Confirmation. This private page confirms that an image has been removed from the database. § Category Delete Confirmation. This private page confirms that a category has been removed from the database. § Catalog Page. This public page lists each of the categories pages based on the category information from the database. If an image has been attached to a category the image will appear as well. § Item Page. This public page shows the details of an individual item along with an image if appropriate. A link is given to the Shopping Cart page. § Category Page. This page lists the item name, price, and short description for each item that has been assigned to the category. A link is provided to each item page. § Order Confirmation Page. This page confirms the specifics of the transaction approved by the Purchase Action page. The user is encouraged to print the page as a permanent record. A link is provided to the Catalog page.

    Database Architecture By now you should know how to add tables to your database, so the following section won’t cover specifics about the procedure for adding the necessary tables. If you need assistance, you can refer back to the instructions in Chapter 7 or consult your database administrator. All the tables and fields for this interface are described in the following sections. After each description is a “seed” record or two that you will be using to test your scripts.

    For the purposes of this project it is assumed you are using the same database name (dwf) and therefore the same i_connect.php file as for the first project.

    AuthUsers Table

    The AuthUsers table will hold information about the authorized users from the interface. These users will be the only ones allowed to access the private pages and make changes to the database. Here is a breakdown of the fields in the AuthUsers table: § UserKey. Integer, primary key, not null, auto increment. § UserName. Varchar(255), not null. § Password. Varchar(255), not null. You can seed the table with any usernames and passwords you wish. MySQL uses the password() function to encrypt field values. It is particularly important with a catalog interface that must deal with prices, sales, and credit card numbers that sensitive data be encrypted in the database. Check your database documentation or contact your DBA for information about how to encrypt information in your database. The password() function is nonreversible; you can compare values, but you can never return the original password. Here’s an example of how the MySQL password() function works. Insert into AuthUsers (UserName, Password) values ('danr',password('ilikecheese')); Select Password from AuthUsers; // results an encrypted value. Select UserKey from AuthUsers where Password=password('ilikecheese'); // returns 1

    Item Table

    The Item table is the core of the catalog interface. This table will hold the general information about each of the items in your catalog. Other tables will use the primary key of this table to reference items. These are the fields in the Item table: § ItemKey. Integer, primary key, not null, auto increment. § ItemName. Varchar(255), not null. § ProductNum. Varchar(25). § DescShort. Varchar(255), not null. § DescLong. Text.

    Add the following values to your Item table: ItemName: Banjo ProductNum: P-S001 DescShort: coming soon DescLong: on the way

    ItemName: Harmonica ProductNum: W-H002 DescShort: available now DescLong: here it is

    Price Table

    The Price table holds the prices of the items for the catalog. The design of this table is intended to allow for the entry of multiple currency types. That way a single item can have a price listed in dollars, pounds, marks, yen, or any other currency as needed. The Price table has the following fields: § PriceKey. Integer, primary key, not null, auto increment. § ItemKey. Integer, not null. § Currency. Varchar(25), not null. § PriceValue . Float(2), not null.

    Add the following values to the Price table: ItemKey: 1 Currency: USD PriceValue: 129.99

    ItemKey: 2 Currency: USD PriceValue: 69.95

    ItemStatistics Table

    This table will hold the statistical data about the items in your catalog. These values are often specific to the product. The ItemStatistics table is designed to allow for any individual item to have an unlimited number of statistics, such as height, weight, length, and color. Here’s a list of the fields in the ItemStatistics table: § StatisticKey. Integer, primary key, not null, auto increment. § ItemKey. Integer, not null. § StatName. Varchar(60), not null. § StatValue. Varchar(255), not null. Add the following values to the ItemStatistics table: ItemKey: 1 StatName: weight StatValue: 3.7 lbs

    ItemKey: 1 StatName: color StatValue: white

    ItemKey: 2 StatName: weight StatValue: 9.2 oz

    ItemKey: 2 StatName: pitch StatValue: A#

    Category Table

    This table holds the general information about the product categories for the catalog interface. If more detailed information is needed you should create the category equivalent of the ItemStatistics table. The category also references the imagekey from the Image table. The following is a list of the fields in the Category table: § CategoryKey. Integer, primary key, not null, auto increment. § CatName. Varchar(255), not null. § Description. Text. § ImageKey. Integer.

    The following values should be added to the category table: CatName: Percussion - Strings Description: Guitars, banjos, and mandolins ImageKey: 1

    CatName: Winds - Harmonica Description: All types of mouth harps ImageKey: 2

    Image Table The Image table stores the general image information for the items and categories for the catalog interface. Of primary concern is that the table holds enough information to render the images on the Web page. For this reason, most of the fields correspond to attributes of the IMAGE tag. The following is a list of the fields for the Image table: § ImageKey. Integer, primary key, not null, auto increment. § ImgName. Varchar(255), not null. § FileName. Varchar(255), not null. § FileDir. Varchar(255), not null. § Height. Integer. § Width. Integer. § Border. Integer. § Alt. Varchar(255). The following values should be added to the Image table: ImgName: Cat - Harmonica FileName: harmonic.gif FileDir: categories Height: 25 Width: 25 Border: 0

    ImgName: Cat - Strings FileName: strings.gif FileDir: categories Height: 25 Width: 25 Border: 0

    ImgName: Banjo 1 FileName: banjo1.gif FileDir: item/strings Height: 100 Width: 200 Border: 0

    ImageAssignment Table

    This table is the link between the Item and Image tables. By using a separate table for assigning images to items, you allow for the possibility of multiple images for a single item. The following is a list of the fields for the ImageAssignment table: § ImageAssignKey. Integer, primary key, not null, auto increment. § ItemKey. Integer, not null. § ImageKey. Integer, not null.

    Add the following values to the ImageAssignment table: ItemKey: 1 ImageKey: 3

    CategoryAssignment Table

    This table is akin to the ImageAssignment table, except that it links items to categories. Once again this allows you to assign a single item to multiple tables. Here’s a listing of the fields in the CategoryAssignment table: § CatAssignKey. Integer, primary key, not null, auto increment. § ItemKey. Integer, not null. § CategoryKey. Integer, not null.

    Add the following values to the CategoryAssignment table: ItemKey: 1 CategoryKey: 1

    ItemKey: 2 CategoryKey: 2

    Sales Table

    This table holds the sales information for the catalog interface. Because this table holds credit card information, you should consider the security implications carefully. Many online retailers remove credit card numbers from the database on a regular basis, and others choose not to store card numbers at all. While they are in the database credit card numbers should be encrypted, so check with your DBA on how your database can encrypt field data.

    Here are the fields for the Sales table: § SalesKey. Integer, primary key, not null, auto increment. § PriceKey. Integer, not null. § CardType. Varchar(60), not null. § CardNumber. Blob, not null. § ExpDate. Varchar(40), not null. MySQL provides a simple way to encrypt files using the encode() function. Unlike password(), the encode() function can be reversed using the decode() function. Both functions take two parameters: the string to encode or decode and a text password. Here’s an example of how the encode() and decode() functions work: Insert into Sales (PriceKey, CardType, CardNumber, ExpDate) values ('1','Visa',encode('5555-5555-5555-5555','hooligan99!'), '12/04'); Select CardNumber from Sales; // returns encrypted value Select decode(CardNumber, 'hooligan99!') from Sales; // returns 5555-5555-5555-5555

    Shipping Table

    The Shipping table will track the names, addresses, and other shipping data of the purchases for your catalog interface. Item numbers and sales information are deliberately left off this table. This will permit you to store and reuse shipping and user information without having to remove or repeat information. The following is a list of the fields in the Shipping table: § ShipKey. Integer, primary key, not null, auto increment. § Name. Varchar(255), not null. § Email. Varchar(255), not null. § Address. Varchar(255), not null. § City. Varchar(255), not null. § State. Varchar(255). § Zip. Varchar(12), not null. § Country. Varchar(255), not null. § Phone. Varchar(30). § Fax. Varchar(30).

    ItemShip Table

    The last table, ItemShip, links three separate tables: Item, Sales, and Shipping. This format allows you to ship an unlimited number of items without clogging the other tables. The following fields should be added to the ItemShip table: § ItemShipKey. Integer, primary key, not null, auto increment. § ShipKey. Integer, not null. § ItemKey. Integer, not null. § SaleKey. Integer, not null.

    You do not need to seed the ItemShip or Shipping tables at this time.

    File System Architecture This project will follow the same basic file system architecture as project 1 for the public pages. The private pages will also use the o_Template class, but will be handled by a secure server using SSL. For this purpose you will need to create a certificate authority (CA), and then make and sign a server certificate. The following pages describe how to set up a secure server on Apache using mod_ssl and OpenSSL, two freeware products that are available on a number of different platforms. If you are using a server other than Apache, or a different security product, you will need to review the applicable documentation for installation and configuration instructions.

    Installing mod_ssl and OpenSSL Mod_ssl is an Apache mod that uses OpenSSL, a freeware SSL program that allows a server to create a secure connection using the SSL protocol. The first step to creating your secure server is to download the latest version of mod_ssl from http://www.modssl.org/source/. If you are using a Windows machine—or just prefer to use a pregenerated executable file—you should check http://www.modssl.org/ contrib/.

    Once you have downloaded the software, you will need to uncompress and install the files. If you have downloaded the source code, you will need to follow the directions in the file INSTALL. These directions are machine-specific and can be quite complicated; if you have any trouble, it’s probably a good idea to download the executable files instead. If you have chosen to download a binary file, then you should only have to move the files into your Apache server root directory. These files will overwrite the current files, so be sure that the binary was created for your version of Apache. You may want to make a backup of your Apache configuration files before copying the new files over.

    Next, you will have to modify the Apache configuration file, httpd.conf, to include references to the new mod. Find the location of the LoadModule directives. There should be several that have been commented out. Add the following line after the commented declarations: LoadModule ssl_module modules/ApacheModuleSSL.so

    If you have an older version of the mod_ssl, use this line instead: LoadModule ssl_module modules/ApacheModuleSSL.dll

    Locate the Port and Listen directives. You should add the following lines in the appropriate locations: Port 443 Listen 443

    Finally, at the end of the httpd.conf file add the following lines, replacing SERVER_NAME with the name of your server: SSLMutex sem SSLRandomSeed startup builtin SSLSessionCache none

    SSLLog logs/SSL.log SSLLogLevel info

    SSLEngine On SSLCertificateFile conf/ssl/my-server.cert SSLCertificateKeyFile conf/ssl/my-server.key Save the file, but do not restart the server. You will need to create a certificate and key before mod_ssl will function. Before you create the key, however, you will need to configure OpenSSL. This application comes with most binary distributions, but if you need to, you can download binaries and source code at http://www.openssl.org/.

    Follow the installation instructions that come with the files. Once you have installed OpenSSL, Windows users will have to copy the files ssleay32.dll and libeay32.dll from the openssl directory to the Windows\System32 folder or the equivalent.

    The final step in configuring OpenSSL is to configure the openssl.cnf file. This is usually located in the bin or /etc/ssl/ folders. If your version of OpenSSL did not come with an openssl.cnf file, you will have to create one. Use the following as a template for your file: openssl.cnf RANDFILE = .rnd

    [ ca ] default_ca = CA_default

    [ CA_default ] dir = OpenCA #the root directory for the CA files certs = $dir\certs #the certificates directory crl_dir = $dir\crl #the crl directory database = $dir\index.txt #the index file. new_certs_dir = $dir\newcerts #the default directory for certs. certificate = $dir\cacert.pem #the CA certificate file serial = $dir\serial #the serial number crl = $dir\crl.pem #the current CRL file private_key = $dir\private\cakey.pem #the private key file RANDFILE = $dir\private\private.rnd #the random number file default_days = 365 #the number of days the certification is good for default_crl_days= 30 #the number of days before the next CRL x509_extensions = x509v3_extensions default_md = md5 preserve = no policy = policy_match

    [ policy_match ] countryName = optional stateOrProvinceName = optional organizationName = optional organizationalUnitName = optional commonName = supplied emailAddress = optional

    [ policy_anything ] countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional commonName = supplied emailAddress = optional

    [ req ] default_bits = 1024 default_keyfile = privkey.pem distinguished_name = req_distinguished_name attributes = req_attributes

    [ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = US countryName_min = 2 countryName_max = 2 stateOrProvinceName = State or Province Name (full name) localityName = Locality Name (eg, city) 0.organizationName = Organization Name (eg, company) organizationalUnitName = Organizational Unit Name (eg, division, department, etc) commonName = Web Server Name (eg, www.myserver.com) commonName_max = 64 emailAddress = Email Address emailAddress_max = 40

    [ req_attributes ] challengePassword = A challenge password challengePassword_min = 4 challengePassword_max = 20

    [ x509v3_extensions ] nsCertType = 0x40

    Once you have created the openssl.cnf file, move it to the same folder as the openssl application file. Creating a Certificate Authority

    Usually a CA is an external company that vouches for a Web site, assuring the user that the connection is in fact secure, and that the company that runs the secure Web site is who it claims to be. For the purpose of learning the process, however, you don’t need an external company to verify your security. You can be your own CA. After all, if you can’t trust yourself, whom can you trust? Besides, this way you won’t have to fill out dozens of forms or pay hundreds of dollars to an external CA.

    The openssl application works from the command line, so you will have to open a DOS command prompt, Darwin terminal window, or another command line interface specific to your operating system. Navigate to the openssl/bin directory and type the following: openssl req -config openssl.cnf -new -out my-server.csr The application uses a pass phrase to ensure that only the administrator can create private keys for this certificate-signing request. Enter and confirm your pass phrase at the PEM prompt. Then the application will ask for information about the company issuing the CA. Enter your information as appropriate. If the application asks for your common name enter the exact name of the server, including the second-level domain such as “www” or “secure.” This process will create two files: my-server.csr and privkey.pem. The .csr file is the certificate signing request and the .pem file is the unsecured private key. To create the secured private key, type the following: openssl rsa -in privkey.pem -out my-server.key

    When the openssl application asks for the PEM pass phrase, enter the same pass phrase you used earlier. A new file will be created: myserver.key. This is your private key. You should delete the privkey.pem file once you have myserver.key, as it could potentially be used to decrypt the secure key you just created.

    At this point you would normally send the .csr file to your CA, which would generate the certificate file for you. For this project, however, you will use OpenSSL to generate the certificate file itself. Type the following: openssl x509 -in my-server.csr -out my-server.cert -req -signkey my-server.key -days 365

    Now you have my-server.cert, your certification file, which will expire in 365 days. For Internet Explorer users to use this certificate, however, you will need to create a DER- encoded version. Type the following: openssl x509 -in my-server.cert -out my-server.der.crt -outform DER

    Finally, create a new file named ssl in the conf directory. Move my-server.cert, my- server.key, and my-server.der.crt to this new folder. You can now restart Apache. Depending on your server configuration and operating system you may have to type the following: apache start apache start -D SSL apachectl startssl

    Test your secure service by visiting https://www.myserver.com/. Notice the use of the HTTPS protocol. You will get an error if you try to reach the secure site with just HTTP. If everything is working, you should get a security alert in your browser. To remove this alert, you will have to install the certificate on the client. Note Depending on the configuration of your server, you may have to attach :443 to the end of the server name for the proper server to be contacted. In the case of the example given, the URL would be https://www.myserver.com:443/. Installing the Certificate on the Client Few things are more intimidating to the end user than the security warning that appears for a site with an unauthorized CA. That’s one of the reasons that Thawt and Verisign are in business. These companies and a few others are considered authorized certificate authorities on almost all modern browsers. This means that unless the user has significantly increased the browser’s security settings, no security warning is generated. As you have just seen, being your own CA doesn’t provide that benefit. Figures 11.1, 11.2, and 11.3 show the security warning messages on various browsers.

    Figure 11.1: Netscape 6 security warning

    Figure 11.2: Internet Explorer 6 security warning

    Figure 11.3: Opera 5 security warning

    Despite the hassle, you still might want to go through the process of installing certificates on the client machines of your users if any of the following apply: § You’re testing an interface or otherwise just using the certificate for developmental purposes. § The site management team deems that the confusion and inconvenience presented to the user doesn’t outweigh the resources needed to get a CA from a trusted authority. § You have a limited number of known users on an intranet or extranet application and don’t mind the added technical support required with a self-signed CA.

    When presented with a self-signed certificate, a browser will generally give the user three options: accept the certificate, decline the certificate, or install the certificate permanently (at least until it expires). Surprisingly, many browser manufacturers have made it a simple and straightforward process to permanently accept a self-signed certificate.

    Unsurprisingly, Microsoft’s Internet Explorer makes the process unnecessarily confusing. It just asks the user, “Do you want to proceed?” and presents the options yes and no. You might think that answering yes would take you to the next step in the acceptance process, but instead the browser interprets this response as though you said, “Accept the certificate for this session only.” The only way to install the certificate permanently is to click on View Certificate. As you might expect, this opens a new window that explains in detail all the known information on the certificate and the CA. What you probably didn’t expect to find is a button at the bottom of the page labeled Install Certificate. That’s it— you’re done—right? Wrong. This just opens up the “helpful” Certificate Installation Wizard. Another two or three completely useless pages later, your certificate has been installed, hooray! You still have to click on Yes to proceed, though.

    Securing Your Pages Now that you have certificates on both the client and server, you need to start securing the private pages of your interface. If you didn’t add additional code to your PHP pages, it would be possible for a user to simply change the protocol from HTTPS to HTTP and the pages would be displayed without the secure protocol. The fix for this problem begins with creating a new directory in the root folder of your Web server. Call this folder anything you like; in the project files I’ve used the name “secure.”

    This folder will hold all the .php, .tpl, and image files for your private pages. The next step is to change the document root directory of the secure server to point to this new folder. With Apache you can do this by modifying the VirtualHost directive for the secure server in the httpd.conf file as follows: DocumentRoot /usr/local/wwwroot/secure SSLEngine On SSLCertificateFile conf/ssl/my-server.cert SSLCertificateKeyFile conf/ssl/my-server.key

    You should replace /usr/local/wwwroot/secure with the location of your secure folder. Next, find where in the httpd.conf file the directory directives are located and add a new record. Once again, replace the directory location with a reference to your secure folder: Options -Indexes AllowOverride None Order allow,deny Allow from all The -Indexes option prevents a user from viewing a directory listing of your secure folder. For additional security you could use this directive to grant access privileges only to specific users or groups. See the Apache documentation for more information on this topic. Caution Make sure that all the files used or referenced by the private pages are in this folder. It is possible to reference nonsecure items from a secure page, but this will produce a security warning on the client and may cause some browsers not to display the page at all. Figure 11.4 shows the error message that Internet Explorer generates when it encounters a page with mixed secure and nonsecure items.

    Figure 11.4: IE mixed security warning

    The last step in securing the private pages of your interface is to attach a bit of PHP to the beginning of any secure page. This code uses two environmental variables defined by PHP: HTTPS and REQUEST_URI. HTTPS is defined only if the page being sent is secured using that protocol. The REQUEST_URI variable is the name of the file that the client is requesting. The server name and path are not included in the REQUEST_URI variable. Note $REQUEST_URI and $HTTPS are global variables that work differently depending on the server you are using. For example, when using PHP with IIS the $HTTPS variable is set to “no” rather than left empty if the browser isn’t using HTTPS. IIS also uses $PATH_INFO and $QUERY_STRING rather than $REQUEST_URI. The code in these projects is optimized for the combination of PHP with Apache. If you’re using another server, you may have to make a few minor code modifications.

    Add the following code to any PHP pages that are private. This code checks for the HTTPS variable and if it is not found, redirects the user to the same page on the secure server: global $HTTPS; global $REQUEST_URI;

    if (!isset($HTTPS)) { $strFileRequested = $REQUEST_URI; $strNewLocation = "https://www.mydomain.com/" . $strFileRequested; Header("Location: " . $strNewLocation); exit; };

    Cookies

    Up till now, you have been using session variables or query strings to pass information from page to page within an interface. Both of these methods are easy to use but limited. Query strings can only hold a limited amount of data and must conform to the URL standard format. Session variables don’t have that limitation, but they expire as soon as the user leaves the Web site. With this catalog form, unlike the membership form, you can expect the authorized users to return on a daily basis. Depending on the required upkeep of the catalog, the database management team may even need to access the interface several times a day. If you continue to use session variables, you will require each authorized member to log in for every database change. The alternative is to use cookies. Cookies are packets of plain text information consisting of a name-value pair that is passed from the client to server. Because cookies are stored and transferred as plain text on the client system, it is not a good idea to store unencrypted passwords inside cookies. Often a developer will use server-side encryption techniques to encrypt the value of a password before setting the cookie.

    Browser developers handle the storage of cookies in different ways. Some create simple text files that can be read offline; others store the information within the application files or in a special format. Regardless of the storage method, the actual functionality of cookies remains the same in all cookie-enabled browsers. Each cookie is automatically assigned the domain name of the site that created, or set, the cookie. Theoretically, only the site that created a cookie can access the cookie’s information. There are ways around this, however. If a site contains a dependent file such as an image, script file, or style sheet that comes from a separate domain, then this third-party domain can also create and read cookies on the visitor’s system. Third-party cookies are often used by ad agencies or Web tracking software to gather information about browsing patterns of users across domains.

    Privacy advocates have convinced browser manufacturers to include special cookie filtering functionality in the latest Web browsers. Browsers have always had the ability to decline cookies, but now a user can distinguish between regular first-party cookies and the potentially invasive third-party cookies. Despite these options, the majority of the surfing public uses the default cookie setting: accept all cookies. Still, any developer that creates an interface requiring the use of cookies must take into account the possibility that at least a few users will have cookies disabled.

    The catalog interface uses a predefined set of authorized users and therefore doesn’t have to worry about the cookie settings of the general Web-browsing public. You will still need to include some information about activating cookies in the help pages of the interface, however.

    JavaScript Cookies Cookies can be set and read using both client-side and server-side scripts. On the client side, JavaScript uses the document.cookie property to create and read cookie information. Most browsers limit the number of cookies to 20 per domain and 300 total across all domains, so if you need to store a large amount of information, you should consider using server-side session variables. Setting a Cookie The document.cookie property has five parameters, only the first of which is required. Cookie parameters have a very specific formatting sequence: the entire parameter sequence is enclosed in quotes, semicolons separate the parameters, and the parameters must not contain any commas, semicolons, or white space. The JavaScript escape() function is useful for replacing undesired characters with their URL-encoded equivalents. Here is an example of how to set a cookie using the document.cookie property: document.cookie = "Color=Blue; expires= Tue%2C%2021%20Jan%202003%2021%3A49%3A30%20UTC; path=/;domain=.mydomain.com; secure"; name-value The name-value parameter, as you might expect, defines the name and value of the variable that you want to store in the cookie. This is the only parameter that the document.cookie property requires. If the browser encounters a cookie within the current domain that has been assigned the same name it will overwrite the cookie with the new information. Otherwise it will create a new cookie.

    For example, in this catalog interface, you will want to store the authorized user key in a cookie. To do this with JavaScript you would write the following: document.cookie = "memberKey=" + intMemberK ey; expires The expiration date of the cookie is assigned using the expires parameter. If expires is left out, the cookie will be considered temporary and will be removed the next time the browser application closes. Effectively, this makes the cookie the equivalent of a client- side session variable. Because you want your cookies to last longer than that, you will need to assign an expiration date.

    JavaScript only accepts expiration dates in Greenwich Mean Time (GMT) format. The first step to creating these GMT time strings is to convert the current date to a timestamp value. Because timestamps are measured in milliseconds, it becomes a simple process to add days, weeks, or years to a timestamp. Here’s a script that will create a timestamp and add the required time: function f_expireDate(strTimeToAdd) { var dateExpire = new Date(); var timeNow = dateExpire.getTime(); var strAddType = strTimeToAdd.substring(0,strTimeToAdd.indexOf("(")); var intAddNumber = strTimeToAdd.substring(strTimeToAdd.indexOf("(")+1,strTimeToAdd.indexOf(")"));

    switch (strAddType) { case "minutes": dateExpire.setTime(timeNow + f_expireMin(intAddNumber));

    break;

    case "hours": dateExpire.setTime(timeNow + f_expireHour(intAddNumber)); break;

    case "days": dateExpire.setTime(timeNow + f_expireDay(intAddNumber)); break;

    case "weeks": dateExpire.setTime(timeNow + f_expireWeek(intAddNumber)); break;

    case "months": dateExpire.setTime(timeNow + f_expireMonth(intAddNumber)); break;

    case "years": dateExpire.setTime(timeNow + f_expireYear(intAddNumber)); break; }

    function f_expireMin(intNumber) { return intNumber * 60 * 1000; }

    function f_expireHour(intNumber) { return intNumber * 60 * f_expireMin(1); }

    function f_expireDay(intNumber) { return intNumber * 24 * f_expireHour(1); }

    function f_expireWeek(intNumber) { return intNumber * 7 * f_expireDay(1); }

    function f_expireMonth(intNumber) { return intNumber * 30 * f_expireDay(1); }

    function f_expireYear(intNumber) { return intNumber * 365 * f_expireDay(1); }

    return dateExpire.toGMTString(); } This function uses the toGMTString() function to output the new date value. To use the f_expireDate() function you will need to reference the function using the "years(3)" format, where the time unit is followed by the number to add in parentheses. Before you can add the GMT string to the cookie parameters, however, you will need to use the escape() function. Here’s an example of how you might set a cookie that will expire in three months: document.cookie = "memberKey=" + intMemberKey + "; expires=" + escape(f_expireDate("months(3)")); Note Some browsers allow the user to filter out any cookies that have expiration dates set too far into the future. For this reason, you normally wouldn’t assign an expiration date of more than one year. path The path parameter is the first of three limiting parameters. The value of the path parameter defines the directory subset that will have access to the cookie. The default value of this parameter is the path of the file that created the cookie. For example, if you set a cookie from the file /home/joe/index.html and left the path value blank, then only pages in the /home/joe/ directory could access the cookie. Setting the path to "/" allows all pages in the domain to access the cookie. The path value is matched to the beginning of the path string. This means that a cookie with a path value of “/home” will be accessible from /home/joe/, /homelearning/, and /home.html. The following example limits the scope to pages in the “secure” directory: document.cookie = "memberKey=" + intMemberKey + "; expires=" + escape(f_expireDate("months(3)")) + "; path=/secure/"; domain The second of the limiting parameters, domain, defines the name of the domain from which the cookie is accessible. The default value of the domain parameter is the domain of the file that set the cookie. The domain value must have at least two periods. For example, by setting the domain to ".mydomain.com" you allow all domains that end with mydomain.com to have access to the cookie. This includes "www.my-domain.com," "secure.mydomain.com," "search.mydomain.com," and so on. This JavaScript will set a cookie that will only be accessible from a domain using “secure.mydomain.com”: document.cookie = "memberKey=" + intMemberKey + "; expires=" + escape(f_expireDate("months(3)")) + "; path=/secure/; domain= secure.mydomain.com"; Of course, the cookie will not be set if the domain parameter does not match the domain of the page that creates the cookie. secure If the optional secure parameter is omitted, then any page that matches the path and domain values will have access to the cookie data. If the secure parameter is included, only pages that use the HTTPS protocol can view the cookie information. This is helpful in ensuring that any data in the cookie is encrypted before transmission to the server. If you must store sensitive information in a cookie, remember to use the secure parameter. Unlike the other parameters, secure does not get an assigned value. Here is an example of how the secure parameter could be used: document.cookie = "memberKey=" + intMemberKey + "; expires=" + escape(f_expireDate("months(3)")) + "; path=/secure/; domain= secure.mydomain.com; secure"; Reading a Cookie Because the document.cookie property is read/write, you can retrieve cookie information simply by assigning a variable the value of document.cookie. When reading a cookie, you do not set the limiting parameters; instead the current document path, domain, and security settings are used. The output is a single string that contains the name-value pairs of any cookies that match these limiting parameters. Here is an example of the output from a document.cookie request: memberKey=532; password=escapade78

    Because the output is a string data type, you will not be able to treat the cookie data as an object. Instead, to find the value assigned to a specific cookie variable name, you will need to perform a number of string manipulation functions. The following code takes the name of a cookie variable as the parameter and returns the value associated with that name. function f_cookieValue(strName) { var strCookie = document.cookie; var intCookieLength = strCookie.length; var intNameLength = strName.length; var intStart = strCookie.indexOf(strName) + intNameLength + 1;

    if (strCookie.indexOf(";", intStart) != -1) { var intEnd = strCookie.indexOf(";", intStart); } else { var intEnd = intCookieLength; }

    return unescape(strCookie.substring(intStart,intEnd)); } Deleting a Cookie

    To remove a cookie value, simply assign a blank value to the cookie variable name. For example, the following script would delete the cookie value for the cookie named “memberKey”: document.cookie = "memberKey=";

    Server-Side Cookies

    There are a few advantages to using PHP rather than JavaScript to set a cookie. First, server-side cookies can contain arrays, greatly expanding the number of variables that can be stored. Second, the expiration date is set using a simple timestamp rather than GMT, making setting an expiration date simpler. Finally, when reading a cookie using PHP, you can handle the output as an object rather than a string. Setting a Cookie To set a cookie in PHP you use the setcookie() function. Like its JavaScript equivalent, setcookie() takes many of the same parameters.

    Here is how the JavaScript cookie example used earlier could be set using PHP: setcookie("memberKey", $intMemberKey, time() + 86400*90, "/secure/", "secure.mydomain. com", 1); The setcookie() function must be called before any HTTP headers are sent to the client. These headers get sent immediately if a space, new line character, or tab is encountered at the top of your PHP code. For this reason, it is always best to put the setcookie() function at the top of your scripts.

    PHP cookie variables can also be handled as numerical arrays. To assign multiple values to a single cookie variable, use the standard array notation: setcookie("memberKey[0]", "537"); setcookie("memberKey[1]", "beatles31"); Note While you can treat a PHP cookie variable as a numerical array, you still can’t use an associative array. The code setcookie("memberKey[‘password’]", "beatles31"); won’t work. You’ve seen that the first two parameters are the variable name and value, respectively. The third parameter of the setcookie() function is a timestamp for the expiration date. Unlike JavaScript, PHP timestamps are in seconds, not milliseconds. Here is the PHP equivalent of the f_expireDate() JavaScript function: function f_expireDate($strTimeToAdd) { $dateNow = time(); $intParen = strpos($strTimeToAdd, "("); $intParenEnd = strpos($strTimeToAdd, ")"); $strAddType = substr($strTimeToAdd,0,$intParen); $intAddNumber = substr($strTimeToAdd,$intParen + 1, $intParenEnd - 1 - $intParen);

    switch ($strAddType) { case "minutes": $intExpireTS = time() + $intAddNumber * 60; break;

    case "hours": $intExpireTS = time() + $intAddNumber * 60 * 60; break;

    case "days": $intExpireTS = time() + $intAddNumber * 24 * 60 * 60; break;

    case "weeks": $intExpireTS = time() + $intAddNumber * 7 * 24 * 60 * 60; break;

    case "months": $intExpireTS = time() + $intAddNumber * 30 * 24 * 60 * 60; break;

    case "years": $intExpireTS = time() + $intAddNumber * 365 * 24 * 60 * 60; break; }

    return $intExpireTS; } The fourth and fifth parameters of the setcookie() function are the path and domain limits of the cookie. These work nearly the same as in the JavaScript document.cookie property. The only difference is that the default value of path is "/" rather than the location of the file that created the cookie. The final parameter is the secure option. A value of "1" is the equivalent of assigning the secure parameter to the document.cookie property. Any other value is ignored. Reading a Cookie The browser will automatically send cookie data to any page that matches the defined limiting parameters. Therefore, it is not necessary to explicitly read cookie data. When the PHP engine encounters cookie variables it does two things: first, it assigns a global variable using the cookie name, and second, it adds the cookie data to a global associative array called $HTTP_COOKIE_VARS[]. The engine will replace any invalid characters encountered in the cookie variable name with underscores before creating the global variable. For example, the cookie variable name bad%20name will generate the global variable $bad_20name. The following lines will both access the cookie variable named memberKey: $memberKey; $HTTP_COOKIE_VARS["memberKey"]; Caution You can see how important it is to give your PHP variables unique names. Because PHP automatically generates global variables for submitted form variables, query strings, and cookie variables, it would be easy to accidentally reference the wrong value. When reading cookie variables it is important to understand the process of server-side cookie creation. When a setcookie() function is called, the PHP engine includes the cookie information in the header that it sends to the client along with the page. This means that the cookie is not set until after the page is sent to the client. Therefore, you won’t be able to reference a cookie variable on the same page that creates it. The following script will result in an empty page: setcookie("memberKey", "537"); echo $memberKey;

    Of course, the next time the page is called, the original cookie will have been set and the page will display “537”. The following code fixes this problem: $strCookieVar = "537"; setcookie("memberKey", $strCookieVar); echo $strCookieVar; Deleting a Cookie

    Deleting a cookie with PHP is done in a manner similar to JavaScript. Simply assign a null value to the cookie variable name, as follows: setcookie("memberKey");

    Using Subpages

    Subpages will often be useful on your form interface. While it would be possible to create a single form that includes all the necessary options, you risk overloading the users. In addition, you spend valuable screen real estate on form elements that may rarely be used, often pushing down more beneficial options. By separating out parts of a large form into smaller subpages, you can increase user efficiency and improve the general usability of an interface.

    The trade-off is that subpages require additional scripting and, from the users’ perspective, tend to become distracting. If subpages are abused, form users may become “lost” in the interface: unsure of where they are in the process and wondering where to go next. For maximum effect, subpages should be used judiciously and consistently. Client-Side

    In a way, creating client-side subpages is easier on the user. By manipulating the browser window’s size and appearance, you can make sure that subpages are instantly recognizable. JavaScript can open a new browser window, so the presence of original form can serve as a signpost to help the user navigate the multipart form. Additionally, client-side subpages are often quicker and easier to code. JavaScript has two functions used for opening a subpage: showModalDialog() and open(). showModalDialog() The showModalDialog() function is, by far, the easier solution to code. The drawback, however, is that at this time only Internet Explorer 4+ for Windows supports the showModalDialog() function. A modal dialog box presents the user with a set of form elements and requires that the user respond before the dialog box will disappear. The size of the form on the dialog box is variable; it can be as simple as an alert message with an OK button or a yes-no prompt.

    JavaScript Modal Dialog Boxes JavaScript has functions for a number of built-in modal dialog boxes; all of them are methods of the window object. Most of these have far more universal support than showModalDialog(). You are probably already familiar with some of these: § alert(). This modal dialog box displays a message and a button. This function is used to give vital information to the user. Because the users must click the button to proceed, you can assume that they have read the information in the alert. § confirm(). This dialog box presents the user with a message and two options. A positive response, which usually means clicking OK, will cause the confirm box to return a value of true and close; a negative response results in a value of false. Commonly the confirm() function is used in conjunction with an if statement to produce a conditional event. Here’s an example:

    § if (window.confirm("baa?"))

    § { document.write("sheep");

    § }

    § else

    § { document.write("no sheep");

    } § prompt(). This dialog box shows a message, a text field, and two control buttons. The text message is supplied as the first parameter of the prompt() function. The second parameter is the default value of the text field. Clicking the OK button causes the prompt() function to return the contents of the text field, otherwise a value of false is returned. Here’s an example of the prompt() function in action: § if (window.prompt("How old are you?","17") < 18) § { document.write("Sorry, you're not old enough."); § } § else § { document.write("Come on in."); } As you can see, the built-in modal dialog boxes are quite limited. The showModalDialog() function, however, allows you to put the entire contents of an HTML file into the dialog box. This function also allows you to pass variables, including arrays, onto the modal page. When the dialog box closes, the code passes a return variable back to the original page. The content of this variable can be defined on-the-fly using JavaScript on the dialog box page. What this means is that you could have an entire multi-element form in a modal dialog box and send the results of the form to the original page in a formatted array. Note Internet Explorer has another dialog box called a modeless dialog box. This is created using the showModelessDialog() function. The showModelessDialog() function works in every way exactly like showModalDialog(), except that the dialog box that opens up does not prevent the user from interacting with the original document.

    Using showModalDialog() showModalDialog() takes three parameters, in this order: the file to load into the dialog box, the variable to pass to the dialog box, and the display options for the dialog box. Only the first parameter is required. This is what the showModalDialog() function looks like: var objModal = window.showModalDialog("subpage.html", varVariable, "dialogHeight:200px; dialogWidth:200px; help:No; resizable:Yes; status:No");

    The first two parameters are self-explanatory, but the display options could use a little clarification. The display options are assigned using a colon rather than the equal sign and semicolons separate the options. The following list describes many of the display options available: § dialogHeight. This sets the height of the dialog box and cannot be set to less than 100 pixels. For IE 4, the default unit is the pixel. For IE 5+, the unit is the em. The following unit types are acceptable: cm, em, ex, in, mm, pc, pt, or px—meaning centimeters, point size, X-height, inches, millimeters, picas, points, or pixels, respectively. § dialogWidth. This sets the width of the dialog box. It uses the same units as dialogHeight. § dialogLeft. Sets the left offset value for the position of the dialog box on the screen relative to the top left corner of the screen. § dialogTop. This defines the top offset value. § center. Specifies whether to center the dialog box in the user’s monitor. The default for this option is yes. Acceptable values for this option (and the other Boolean options) are yes, no, 1, 0, on, and off. Obviously you would set this value to no if you wanted to use dialogLeft or dialogTop. § help. Determines whether to display the Window’s-specific context- sensitive help button. The default for this Boolean option is yes. § resizable. This Boolean option determines whether the user can use the mouse to resize the dialog window. The default value is no. This option only works with IE 5+. § scroll. Another Boolean option, scroll specifies whether the dialog box should display horizontal and vertical scrollbars. The default value is yes. This option only works with IE 5+. § status. Determines whether to display the status bar at the bottom of the dialog box. The default for this Boolean option is yes. This option only works with IE 5+. Calling Dynamic Pages The examples thus far have all called a static HTML file, but this is not required. Using showModalDialog() it is possible to call a dynamic page, or to send data in the query string of the requested URL. In this way you can use a JavaScript modal dialog box to call a PHP page that contains context-sensitive information. For example, in the catalog interface, if an item has been assigned image number 78, clicking on the image edit button would call up the image Add/Edit page with the query string "?ImageKey=78" added to the URL.

    The following is a piece of an HTML page that would open such a dialog box:

    Passing Variables Sometimes you need to pass more information to the dialog box than is feasible with a query string. You’ve seen how you can pass a variable into a dialog box using the second parameter of the showModalDialog() function. To access this variable, you must use the window.dialogArguments property of the subpage. Whenever a page is opened using the showModalDialog() function, the window.dialogArguments property of the new page is automatically set, even if the value is null.

    To capture the passed data, simply assign the value to a new variable, like so: var varVariable = window.dialogArguments;

    You can do more than access simple variables such as integers or strings; you can access objects and arrays using the appropriate standard notation. The following script will create two dialog boxes, one passing an array, the other an object: var arrArray = new Array(); arrArray[0] = "Zero"; arrArray["Zero"] = 0; var objObject = new Object(); objObject.day = "night"; objObject.night = "day"; var arrDialog = window.showModalDialog("array.html", arrArray, strOptions); var objDialog = window.showModalDialog("object.html" objObject, strOptions);

    To gather the array variable, the following script would be added to the array.html page: var arrNewArray = window.dialogArguments; document.write(arrNewArray[0]); document.write(arrNewArray["Zero"]);

    The object variable could be accessed with the following script: var objNewObject = window.dialogArguments; document.write(objNewObject.day); document.write(objNewObject.night); Returning Variables The best thing about using the showModalDialog() function as opposed to a simple open() function is that the transfer of data is designed to be two-way. Not only can you send information to the dialog box in the form of passed variables and query strings, you can also return variables to the original form. This is accomplished using the window.returnValue property. window.returnValue = varVariable; The original form will assign the value of this variable—whether an object, array, string, or other type—to the result of the showModalDialog() function. The process of accessing the returned data is the same as with the window.dialogArguments property. var objDialog = window.showModalDialog(strURL, objObject, strOptions); document.write(objDialog.property);

    Closing Dialog Boxes The user can close a dialog box by clicking the close icon in the top right of the dialog box window, but this should not be required. Instead, most often, you will want to provide your users with a control element such as a button or link that will process the form information, send the results to the referring page, submit the form, and then close the dialog box. A common way to do this is to call a predefined closing function with the onClick event of a button. Caution Be careful when attempting to submit a form that is within a dialog box. If you haven’t assigned the proper target attribute to the FORM tag, the submit action causes the contents of the original page to change, not the dialog box. This may cause the user to lose valuable information. This effect also occurs when the user clicks on a link within the dialog box.

    The following script, after validating the data, creates an associative array populated with the name-value pairs of the submitted form. This array is then passed to the original page, after which the form is submitted and the window closed.

    Name:
    open() A more universal, although less functional, alternative to showModalDialog() is the JavaScript open() function. This function doesn’t create a modal dialog box; instead it opens a new browser window and fills it with the contents of a Web page. While you can’t pass variables as with the showModalDialog() function, you can use query strings or cookies to pass information between the original form and the subpage. Here is the format of the open() function: var objWindow = open("URL", "WindowName", "Display=options", "History"); To create a modal effect you can assign the window.focus() method to the onBlur event of the new window: The first and third parameters should be familiar. The second parameter is the name of the new window and is required, although it could be “”. The name can be used to reference the window using JavaScript or the target attribute. The fourth parameter is a Boolean value that determines whether the new window should replace the current document in the browser’s history (true) or create a new entry (false). The default value is false. Display Options The display options for dialog boxes and new windows differ in several ways. First, assigning display options is accomplished using the equal sign rather than the colon. Additionally, commas rather than semicolons separate multiple display options. The list of available display options for the open() function varies by the browser type of the user. Here is a list of the most common options: § copyhistory. This Boolean option determines whether to duplicate the history of the original window into the new. The default value is yes. This option works with both Netscape and IE. § dependent. This Netscape-specific Boolean option will cause the new window to close if the original does, if it is set to yes. The default value is no. § directories. This Boolean option determines whether to display "What’s New" and other directory buttons. The default value is yes. This option works with both Netscape and IE. § fullscreen. This IE-specific Boolean option determines whether to fill the entire screen with the new window. The default value is no. § height. This option sets the height value, in pixels, for the content area of the new window. Browsers have varying defaults and most have a minimum and maximum allowable value. This option works with both Netscape and IE. § left. This IE-specific option defines the horizontal placement of the new window relative to the left side of the screen. § location. This option specifies whether to show the location bar in the new window. The default is yes. This option works with both Netscape and IE. § menubar. This option determines whether to display the browser- specific menu bar at the top of the new window. The default value is yes. This option works with both Netscape and IE, but only for non- Macintosh browsers. § resizable. This Boolean option determines whether the user can use the mouse to resize the new window. The default value is yes. This option works with both Netscape and IE, but only for non- Macintosh browsers. § scrollbars. This Boolean option specifies whether the new window will display horizontal and vertical scrollbars if the document is larger than the window. The default value is yes. This option works with both Netscape and IE. § status. This Boolean option determines whether to display the status bar at the bottom of the new window. The default value is yes. This option works with both Netscape and IE. § toolbar. This Boolean option specifies whether the new window will display user’s toolbar, including such buttons as Back, Forward, and Home. The default value is yes. This option works with both Netscape and IE. § top. This IE-specific option defines, in pixels, the vertical placement of the new window relative to the top of the screen. § width. This option sets the width value, in pixels, for the content area of the new window. Browsers have varying defaults and most have a minimum and maximum allowable value. This option works with both Netscape and IE.: Passing and Returning Variables

    While you can’t pass variables back and forth between the new window and the original as you can with modal dialog boxes, you still have ways to transfer data. To transfer information to the new window, you could pass small bits of information in the query string. You saw how this could be accomplished earlier. Another, more complex, method is to transfer information in cookies.

    The following script will create three temporary cookies and open a new window. The new window will then be able to access the cookie information, either with JavaScript or PHP, and to use that data to create dynamic content. document.cookie = "imageKey=37"; document.cookie = "imageName=Cello"; document.cookie = "imageSRC=cello1.jpg"; var objWindow = open("image.php", "");

    You can also return information to the original window by using cookies. In this case the temporary cookies are created on the new page. This script cycles through the form elements of a page and sets a cookie for each:

    Name:
    To process the cookie data in the original window, you will need to determine when the new window has been closed. You can do this with the onFocus attribute of the original document. Caution Be careful when assigning a function to the onFocus event. If the function contains a method such as alert() that causes the window to lose or gain focus, you could put the browser in an endless loop.

    There is yet another way to pass information from one window to another. You can use the window name to designate the changes that should affect another window. For example, if a window is named “winHome” you could change the value of a form element by using: winHome.document.forms[0].element[0].value = "some information"; You can name a window when you call the open() function, or later using the window. name property: window.name = "winHome"; Additionally, inside a new window, you can use the opener designation to reference the window that originally called the open() function. This works just as if you were using the name of the original window. opener.document.forms[0].element[0].value = "some information";

    Server-Side Scripts

    One day we will all live in peace and harmony, each and every person surfing a totally free Internet over super-fast broadband connections using the latest browser applications that all include complete JavaScript support. Until that day, though, you have to face the fact that some users just can’t or won’t use JavaScript. In particular, the overuse of pop-up ads has served to create a situation where some browsers now offer the option of turning off the JavaScript functions that create subwindows altogether. Intranet and extranet developers may be able to ignore this issue, but the typical Internet interface developer doesn’t have that luxury.

    Luckily, forms with a large number of fields or multiple pages can be handled using PHP and other server-side scripts, just without the speed, usability, and elegance of JavaScript. Difficulties Associated with Server-Side Solutions

    Server-side technologies pose some inherent difficulties when you want to create multiple page interfaces for the Web. The core of the problem lies with the fact that server-side scripts can only work when the user clicks a link or submits a form. Both actions take the user away from the original form. Once on the second form page, the user must click another link or submit button to get to a third page. The stateless nature of the HTTP protocol means that information from the first page does not automatically transfer to the third page.

    stateless An application, interface, or protocol that does not store information from one transaction to another. In terms of the HTTP, stateless refers to the fact that the client- server connection is created anew for each and every file request. There is no built-in system for the server to distinguish between a single request from a hundred separate clients or a hundred requests by a single client.

    Information transfer, however, is secondary to the larger issue of usability. Take, for example, the Add/Edit Item page from this interface. The development plan for this page calls for a large number of fields: item name, product number, short and long descriptions, associated images and categories, price, weight, and size. When creating this page, you are presented with three options: create a single long form, create a multipart form, or attach subpages to the form.

    Long Forms Creating a single form page that contains all possible elements is the simplest solution. After all, why do you need to create a separate page for image and category information at all? From a developer’s standpoint, you don’t, but if you took anything away from Chapter 2, it was that a Web interface designer has to look at the interface from the user’s standpoint. Long forms can be confusing and intimidating. Even forms that use a multicolumn approach can seem cluttered, often causing the user to become lost in the form, unsure of which elements are required and which are optional. Additionally, form elements can be missed, eliminating the possibility of gathering the required information.

    You might be tempted to conserve screen real estate by shrinking the form elements themselves. You could make a text area only two rows high or a text input field only a few characters long and still let users write as much as they needed to in those spaces. You’ll soon discover, however, that many users tend to enter information only until they reach the end of the visible area of a form element. Although they might have far more input area hidden from view, they tend to stop typing where the form element ends. When users see a small input area they automatically assume that the information requested should be similarly limited.

    Long forms do have their place, however. Most often they are used for one-time or special-purpose forms, such as contest entry forms or directory listings. Our example, the add/edit item page, however, doesn’t fall into this category.

    Multipart Forms

    The next option is to divide the content into separate pages and present these to the user one at a time. This practice is quite common on the Web—and with reason. It provides an alternative to a long form without requiring a great deal of additional programming. Any of the data transfer techniques used with subpages can be used with multipart forms, including cookies, temporary tables, or hidden fields. Still, multipart forms present a new difficulty: they interfere with the user’s natural workflow.

    Imagine you are a member of the database management team and it’s your job to enter all the catalog information for a new product: an oboe. You proceed to log in to the maintenance system and click on the “add new item” link. Presented with a form for entering item information, you fill out all the fields and click Submit. So far, so good. Next, you get a form asking for image information. Wait, the design team hasn’t given you a photo yet. Well, you’ll just skip this page for now. You click Submit and are presented with the category select page. You choose “brass - minor” and again click Submit.

    Just when you think you’re all done, the design team gives you the image for the new item. Perfect timing. So, you go to the item select page and choose the item you’ve just added. Nothing has changed on the first page, so you go to the second. You enter the image information and click Submit. Done, right? Nope, now you’re looking at the category select page again. Well, it’s just one more click, right? Then your boss tells you that you’ve assigned the item to the wrong category: oboes belong in “woodwind - reeds.” Again you are presented with three pages, where you only need one. After you finish that, the price changes, and you’ve got to go through all three pages yet again. Then the image file changes size, then the weight is increased, then the category changes again.

    You can see how the added weight of a linear form can drag on a user after anything more than occasional use. But what if you present the pages in a nonlinear format? That would solve the problem of forcing the user to go through unnecessary pages, but it damages user workflow in another, perhaps more destructive, way. It hinders objective- based work processes.

    Users don’t work in a vacuum, entering information one piece at a time, in a stateless manner. Interface users have objectives, a task or set of tasks that must be completed. Good interface designers build forms around these objectives, making it easy to perform repetitive or common tasks. It is possible to create a catalog interface that presents three separate and unlinked form pages: item information, image information, and category information. User objectives that involved just one of these pages would work tremendously well, but consider how common those types of objectives really are. Category information itself would change rarely and seldom would new categories be created, but assigning categories to items would occur each time an item was created. While image information might change occasionally, new images would need to be added for each new category or item. Item information itself would change often, but new items will almost always require the assignment of an image and category. As you can see, it’s far more likely for the user to want to use some combination of the three pages, rather than just one page by itself. Separating the pages forces the user to work in a nonintuitive manner, using multiple parts of the interface for what should be a simple task.

    Of course, you could give the user different forms depending on the circumstances: a linear form for new items and a separated form for item edits. While you’re at it, why don’t you provide a separate form for assigning an image to a category, or a category to an item, or an image to an item, and so on? You could provide a different form for each objective that you could think of—but all you’d end up doing is confusing your users. Creating different forms for different objectives improves the objective-based workflow at the expense of consistency.

    In the end, multi-page forms present more difficulties then they solve. While marginally better than presenting a single long form, multi-page forms are rarely worth the added effort. With a few additional programming types of interfaces, you could instead use a more user-friendly solution: subpages. Subpages

    The distinction between multi-page forms and those that use subpages is small but significant. Multi-page forms present the user with a defined set of form pages that are either separate and unlinked or required in a linear fashion. Subpages are presented as options that act as an addendum to another primary page. Subpages are neither separated, unlinked, nor required, they are simply options the user could take or leave as the situation requires.

    Admittedly, server-side subpages do take the user a little out of the interface workflow, at least compared to the client -side equivalents. This is because server-side technologies can’t create modal dialog boxes or dependent windows. Each time a server-side subpage is required, the original page must be temporarily replaced rather than moved to the background. Still, they are preferable to the alternatives already discussed. The key to using server-side subpages is the assignment of the name value to the submit button. Because the only reason to use server-side subpages is if JavaScript is not available on the client, you can’t use the onClick event of a button to change the URL or submit form data. Rather, by creating multiple submit buttons, each with a different name, you can use the script on the action page to determine which button has been clicked. From there it is a simple matter to send the user to different pages, or process the information differently.

    With the catalog interface including subpages you present the user the best of all solutions. Simple item changes can be made quickly, without going through extra pages. New items can be given all the available information from a single point of entry. A consistent interface is provided. The form is simple, short, and easy to use.

    The following sections describe the process of transferring information when using subpages, but these same techniques could also be used with multipart forms. Using Hidden Fields

    Perhaps the simplest and most straightforward way to transfer information from one form page to another is to use hidden fields. When the first page is submitted, a script reads the form variables and transfers the information to hidden form elements. Usually, each form element from the first page is given its own hidden element on the second, but with a little string manipulation, submitted data could be combined into a single hidden field. Regardless, when the second form is submitted the data is sent to a third page, as is any other submitted form data. The following script takes all form variables and, using the o_Template class, creates and populates a dynamic section called HiddenFormData. $strHiddenFormSection = $objTemplate->m_DefineSection("HiddenFormData"); foreach($HTTP_POST_VARS as $FormName => $FormValue) { if (($FormName != "") && ($FormValue != "")) { $strHidden = $objTemplate->m_AssignContent("HIDDEN_NAME", $FormName, $strHiddenFormSection); $strHidden = $objTemplate->m_AssignContent("HIDDEN_VALUE", $FormValue, $strHidden); $objTemplate->m_AssignSection("HiddenFormData", $strHidden); };

    };

    The .tpl file called by this page would have the following lines added: {{section HiddenFormData }} {{/section HiddenFormData }}

    The disadvantage to using hidden fields is that the information must be processed and sent multiple times between the client and browser. Each time the information travels back and forth the potential for a breach of security or for corruption of the data increases. Admittedly this is a just a small risk, but nonetheless it can be problematic for some interface projects. Using Session Variables or Cookies

    Session variables and cookies have the advantage that they don’t require additions to the HTML of the form pages. Instead, information is stored on the client or server, respectively, and can be accessed directly through the scripts. There is no need to submit the information twice. Between the two options, session variables are the better choice. Cookies are limited in size and number and are more likely to become corrupted. Additionally, the persistence of cookies actually makes them less appealing for this type of application. An improperly handled cookie can remain on the user’s system, producing erratic bugs on any page that attempts to use the cookie data.

    Changing the hidden field script to produce session variables is a simple matter. session_start(); foreach($HTTP_POST_VARS as $FormName => $FormValue) { if (($FormName != "") && ($FormValue != "")) { session_register($FormName); $HTTP_SESSION_VARS[$FormName] = $FormValue;

    };

    }; Additional Options

    More options are available to pass form information using server-side scripts than just hidden fields, session variables, and cookies. Extremely large amounts of information may be better handled using a file system or relational database. This option has the added benefit of increased permanence. If the form data is stored in a table, for example, even if the user connects through a different computer, the interface might recognize the user based on the login and convert the form information to the last set of values. Conversely, very small amounts of information can be handled using query strings rather than posted data. The final choice of how the information should be passed is up to you and the requirements of each project. For the catalog interface you will be using session variables, combined with the browser detection techniques covered in Chapter 10, to create a set of client- and server-side subpages.

    Chapter 12: Creating the Online Catalog Interface

    Now that you’ve learned all the tools, you can get to work on building the interface. As with the preceding project, the catalog interface is broken down into page types: form pages, action pages, and display pages. Depending on your personal workflow, you may find it easier to jump around in this chapter—that is, to create a form page and then immediately start the associated action page—rather than go through all the form pages first.

    Form Pages The first step in implementing the user interface elements, discussed in earlier chapters, is to create a new file in the global folder named i_interface.jss. Each of the form pages will include a reference to the i_interface.jss page. This page will contain various JavaScript functions specific to user interface elements. To start the file, add the f_accesskey() code from Chapter 9. var Event; if (Event) { document.captureEvents(Event.KEYPRESS); document.onKeyDown = f_accessKey; } function f_accessKey(objEvent) { var intKeyCode = objEvent.which; var strKeyVal = String.fromCharCode(intKeyCode); strKeyVal = strKeyVal.toLowerCase();

    switch (strKeyVal) { }

    } Next, add the f_primaryIndex() function to this page: function f_primaryIndex(objElement) { objElement.focus(); } Note For simplicity, the global folder will be located inside the secure directory on the server. This will allow you to reference the global files using relative links from both the public and private pages. Finally, add the f_selectElement() function. Between these three functions and some additions to the template files, most of the user interface elements will be covered. function f_selectElement(strForm,strElement) { objElement = document.forms[strForm].elements[strElement]; objElement.focus(); if ((objElement.type == "checkbox") || (objElement.type == "radio")) { if (objElement.checked == true) { objElement.checked = false; } else { objElement.checked = true; }

    }

    } The catalog interface will also have a number of JavaScript functions too specific to include in a more general file. For these functions, you will need to create a file called i_catalog.jss and place this file in the global folder. Begin this file with a reference to the o_ValidateClient() function. var objValidate = new o_ValidateClient(); Table 12.1 describes the files that should be created for each form page for the catalog interface. Table 12.1: Form Pages Page Object Page Template Secure Page Login secure/secure.php login.tpl Yes Item secure/itemselect.php itemselect.tpl Yes Selector Add/Edit secure/itemupdate.php itemupdate.tpl Yes Item Delete secure/itemdelete.php itemdelete.tpl Yes Item Image secure/imageselect.php imageselect.tpl Yes Selector Add/Edit secure/imageupdate.php imageupdate.tpl Yes Image Delete secure/imagedelete.php imagedelete.tpl Yes Image Table 12.1: Form Pages Page Object Page Template Secure Page Category secure/catselect.php catselect.tpl Yes Selector Add/Edit secure/catupdate.php catupdate.tpl Yes Category Delete secure/catdelete.php catdelete.tpl Yes Category Shopping cart.php cart.tpl No Cart Checkout secure/checkout.php checkout.tpl Yes

    Login Page

    The Login page for the catalog interface is very similar to the one used for the membership forms from the first project. The Catalog Login form is simpler, however, because it doesn’t need the extraneous navigation and display elements. The private pages of this interface are not intended for the general public and the front door is designed to be an obvious break from the standard public pages.

    The Login page uses the file secure.php. This file is very similar to the main.php file from the membership interface. However, the secure.php page includes a block of code to confirm that the user is using SSL. global $HTTPS; global $REQUEST_URI; if (!isset($HTTPS)) { $strFileRequested = $REQUEST_URI; $strNewLocation = "https://www.mydomain.com/" . $strFileRequested; Header("Location: " . $strNewLocation); exit; }; Another difference is that secure.php uses the getbrowser() function to set a number of cookie variables that will be used to display browser- and platform-specific content. if (!$Browser) { $objBrowser = get_browser(); setcookie("Browser", $objBrowser->browser, time() + 86400*90, "", "", 1); setcookie("Version", $objBrowser->version, time() + 86400*90, "", "", 1); setcookie("Javascript", $objBrowser->javascript, time() + 86400*90, "", "", 1); setcookie("Platform", $$objBrowser->platform, time() + 86400*90, "", "", 1); };

    Here is the complete code for the Login form page: secure.php

    $strFileName = "./templates/" . strtolower($Page) . ".tpl"; $objTemplate = new o_Template; $objTemplate->m_LoadFile($strFileName); $objTemplate->m_AddElements($Page); if ($sessErrors) { $objTemplate->m_AssignContent("ERRORS", $sessErrors); } else { $objTemplate->m_AssignContent("ERRORS", ""); }; if (!$Browser) { $objBrowser = get_browser(); setcookie("Browser", $objBrowser->browser, time() + 86400*90, "", "", 1); setcookie("Version", $objBrowser->version, time() + 86400*90, "", "", 1); setcookie("Javascript", $objBrowser->javascript, time() + 86400*90, "", "", 1); setcookie("Platform", $$objBrowser->platform, time() + 86400*90, "", "", 1); }; session_unregister("sessErrors"); $objTemplate->m_PrepTemplate(); $objTemplate->m_Output();

    ?> The Login page will use a function called f_valLI() to confirm that the username and password fields have acceptable data. In this case, that means that the values are more than three digits long and less than 30. Add the f_valLI() function to the i_catalog.jss page. function f_valLI(objElement) { if (!objValidate.m_isRangeLength(objElement.value, 30, 3)) { objValidate.m_Alert_x("Usernames and passwords must be at least three characters and not more than 30.", objElement); return false; } else { return true; } }

    The Login form has a single control element, the login button. The accesskey for this button is “l”. Add the following lines to the f_accessKey() function in the i_interface.jss file: case "l": f_doL(); break; The f_doL() function also needs to be added to the i_interface.jss file. This function simply submits the first form on the page. function f_doL() { document.forms[0].submit(); } The code for the Login template file follows. Notice the use of the label and accesskey elements. You will be using these throughout the pages of this interface. Figure 12.1 shows the completed Login page.

    Figure 12.1: The Login page

    login.tpl Catalog Maintenance - Secure Login

    {{ERRORS}}

    Help Me!

    Item Selector The Item Selector page is the first of the selection pages for the catalog interface. Using the o_Template class, it is a simple matter to create a repeating section to hold all the items for the select box. Be sure to include the applicable user interface elements in the itemselect.tpl file. Be sure to include the hidden form element subType, it will be needed shortly. Here is the code for the Item Selector template file: itemselect.tpl Catalog Maintenance - {{TITLE}} {{NAVIGATION}}

    {{ERRORS}}

    Select an Item to Update:

    Update Options:




    Add New Item

    {{FOOTER}} The template file references the f_ValidateSelect() function. The purpose of this function is to ensure that an item has been selected before the user is allowed to submit the form. The f_ValidateSelect() function should be added to the i_catalog.jss file, as follows: function f_ValidateSelect(objElement) { if (objElement.selectedIndex < 0) { objValidate.m_Alert_x("You must select an option to proceed.", objElement); return false; } else { return true; } } Two new buttons, Edit and Delete, appear on this page. Each needs an entry in the f_accessKey() function. case "e": f_doE(); break; case "d": f_doD(); break; The new functions—f_doE() and f_doD()—are very similar. First, they check that an item has been selected. Next, the subType hidden form element is assigned a new name. The action page will use this name to determine which button the user clicked. Finally, both functions submit the form. function f_doE() { if (document.forms[0].elements[1].selectedIndex >= 0) { document.forms[0].subType.name = "formEdit"; document.forms[0].submit(); } else { window.alert("You must select an item to proceed."); document.forms[0].elements[1].focus(); } } function f_doD() { if (document.forms[0].elements[1].selectedIndex >= 0) { document.forms[0].subType.name = "formDel"; document.forms[0].submit(); } else { window.alert("You must select an option to proceed."); document.forms[0].elements[1].focus(); } } Creating an object file for the Item Selector page is rather straightforward. You can use an object page from the membership interface as a starting point. You will need to be sure to include code blocks requiring login credentials and a secure connection. Figure 12.2 shows the completed Item Selector page.

    Figure 12.2: The Item Selector page

    Here is the code for the Item Selector form page:

    itemselect.php

    if (!isset($HTTPS)) { $strFileRequested = $REQUEST_URI; $strNewLocation = "https://ransom:443/" . $strFileRequested; Header("Location: " . $strNewLocation); exit; };

    include_once("global/i_connect.php"); include_once("global/i_template.php"); session_start();

    if (!isset($GLOBALS["UserID"])) { session_register("sessRefer"); $GLOBALS["sessRefer"] = "itemselect.php"; header("Location: secure.php?Page=login"); } else { $strFileName = "templates/itemselect.tpl"; $objTemplate = new o_Template; $objTemplate->m_LoadFile($strFileName); $objTemplate->m_AddElements("Item Selection"); $strItemSection = $objTemplate->m_DefineSection("Items");

    if ($sessErrors) { $objTemplate->m_AssignContent("ERRORS", $sessErrors); } else { $objTemplate->m_AssignContent("ERRORS", ""); };

    session_unregister("sessErrors");

    $strTable = "item"; $strSQL = "SELECT itemKey, itemName FROM $strTable"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't select item information.");

    while ($arrItems = mysql_fetch_array($objResult, MYSQL_ASSOC)) { $strItem = $objTemplate->m_AssignContent("ITEM_KEY", $arrItems['itemKey'], $strItemSection); $strItem = $objTemplate->m_AssignContent("ITEM_NAME", $arrItems['itemName'], $strItem); $objTemplate->m_AssignSection("Items", $strItem); }

    $objTemplate->m_PrepTemplate(); $objTemplate->m_Output(); }; ?>

    Add/Edit Item Page

    This page will handle both adding new items and updating the information on existing ones. If the object file detects that an item key has been passed in the querystring, the item’s information is pulled from the database. Otherwise it assumes the user is adding a new item.

    The object file begins just like itemselect.php, but after the error content has been displayed the code begins to differ. First, an array is created holding the available sizes. Using an array allows you to change the displayed types quickly and all at once. $arrSizeTypes = array(); $arrSizeTypes[0] = "Small"; $arrSizeTypes[1] = "Medium"; $arrSizeTypes[2] = "Large";

    $strSizeSection = $objTemplate->m_DefineSection("Size");

    Next, the button type dynamic content value is assigned. This value should be “button” for newer browsers and “submit” for older ones. In this way you allow the majority of users to benefit from the improvements in software, while still maintaining functionality for all users. if ((($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] < 5) && ($GLOBALS ["Version"] > 3)) || (($GLOBALS["Browser"] == "IE") || (($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] >= 5)))) { $objTemplate->m_AssignContent("BUTTON_TYPE","button"); } else { $objTemplate->m_AssignContent("BUTTON_TYPE","submit"); };

    A pet peeve of mine, and of many others I’m sure, is having to enter data into a form multiple times because the interface forgets your input from one page to the next. If you used only the error display functionality of the membership interface every time a user submitted improper data, then the entire form would have to be reentered. To prevent this, the action page can be modified to create a session cookie for each of the form values. Then, when the form page is displayed, the session variables can be read and entered into the output stream. You’ll learn the modifications to make to the action pages a little later, but right now you need to add code to itemupdate.php to read the session variables. if (isset($sessMultiform)) {

    $objTemplate->m_AddElements("Item Edit"); $objTemplate->m_AssignContent("ITEM_KEY", $HTTP_SESSION_VARS["formItemKey"]); $objTemplate->m_AssignContent("ITEM_NAME", $HTTP_SESSION_VARS["formItemName"]); $objTemplate->m_AssignContent("SHORT_DESC", $HTTP_SESSION_VARS["formShortDesc"]); $objTemplate->m_AssignContent("LONG_DESC", $HTTP_SESSION_VARS["formLongDesc"]); $objTemplate->m_AssignContent("PROD_NUM", $HTTP_SESSION_VARS["formProdNum"]); $objTemplate->m_AssignContent("PRICE", $HTTP_SESSION_VARS["formPrice"]); $objTemplate->m_AssignContent("WEIGHT", $HTTP_SESSION_VARS["formWeight"]);

    for ($l=0;$l<=2;$l++) { $strSize = $objTemplate->m_AssignContent("SIZE", $arrSizeTypes[$l], $strSizeSection); if ($HTTP_SESSION_VARS["formSize"] == $arrSizeTypes[$l]) { $strSize = $objTemplate->m_AssignContent("SIZE_SELECTED", "CHECKED", $strSize); } else { $strSize = $objTemplate->m_AssignContent("SIZE_SELECTED", "", $strSize); };

    $objTemplate->m_AssignSection("Size", $strSize); }; session_unregister("formItemKey"); session_unregister("formItemName"); session_unregister("formShortDesc"); session_unregister("formLongDesc"); session_unregister("formProdNum"); session_unregister("formPrice"); session_unregister("formWeight"); session_unregister("formSize"); session_unregister("sessMultiform"); } Caution It is very important to unregister, or destroy, the session variables just after using them. Many of the form pages use the same memory mechanism. If you don’t remove the session variables, the next page the user visits will try to gather the session variables from the previous form. You want the data to stay in memory for one page, but no more. The remainder of the object page contains a series of database calls and liberal use of the o_Template class. Here is the code for the Add/Edit Item form page: itemupdate.php m_LoadFile($strFileName);

    if ($sessErrors) { $objTemplate->m_AssignContent("ERRORS", $sessErrors); } else { $objTemplate->m_AssignContent("ERRORS", ""); };

    session_unregister("sessErrors");

    $arrSizeTypes = array(); $arrSizeTypes[0] = "Small"; $arrSizeTypes[1] = "Medium"; $arrSizeTypes[2] = "Large";

    $strSizeSection = $objTemplate->m_DefineSection("Size");

    if ((($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] < 5) && ($GLOBALS["Version"] > 3)) || (($GLOBALS["Browser"] == "IE") || (($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] >= 5)))) { $objTemplate->m_AssignContent("BUTTON_TYPE","button"); } else { $objTemplate->m_AssignContent("BUTTON_TYPE","submit"); };

    if (isset($sessMultiform)) {

    $objTemplate->m_AddElements("Item Edit"); $objTemplate->m_AssignContent("ITEM_KEY", $HTTP_SESSION_VARS["formItemKey"]); $objTemplate->m_AssignContent("ITEM_NAME", $HTTP_SESSION_VARS["formItemName"]); $objTemplate->m_AssignContent("SHORT_DESC", $HTTP_SESSION_VARS["formShortDesc"]); $objTemplate->m_AssignContent("LONG_DESC", $HTTP_SESSION_VARS["formLongDesc"]); $objTemplate->m_AssignContent("PROD_NUM", $HTTP_SESSION_VARS["formProdNum"]); $objTemplate->m_AssignContent("PRICE", $HTTP_SESSION_VARS["formPrice"]); $objTemplate->m_AssignContent("WEIGHT", $HTTP_SESSION_VARS["formWeight"]);

    for ($l=0;$l<=2;$l++) { $strSize = $objTemplate->m_AssignContent("SIZE", $arrSizeTypes[$l], $strSizeSection); if ($HTTP_SESSION_VARS["formSize"] == $arrSizeTypes[$l]) { $strSize = $objTemplate->m_AssignContent("SIZE_SELECTED", "CHECKED", $strSize); } else { $strSize = $objTemplate->m_AssignContent("SIZE_SELECTED", "", $strSize); };

    $objTemplate->m_AssignSection("Size", $strSize); }; session_unregister("formItemKey"); session_unregister("formItemName"); session_unregister("formShortDesc"); session_unregister("formLongDesc"); session_unregister("formProdNum"); session_unregister("formPrice"); session_unregister("formWeight"); session_unregister("formSize"); session_unregister("sessMultiform"); } else { if (isset($formItemKey)) { $objTemplate->m_AddElements("Item Edit"); $strTable = "item"; $strSQL = "SELECT * FROM $strTable WHERE ItemKey = $formItemKey"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't select item information.");

    while ($arrItems = mysql_fetch_array($objResult, MYSQL_ASSOC)) { $objTemplate->m_AssignContent("ITEM_KEY", $arrItems['ItemKey']); $objTemplate->m_AssignContent("ITEM_NAME", $arrItems['ItemName']); $objTemplate->m_AssignContent("SHORT_DESC", $arrItems['DescShort']); $objTemplate->m_AssignContent("LONG_DESC", $arrItems['DescLong']);

    if ($arrItems['ProductNum'] != "") { $objTemplate->m_AssignContent("PROD_NUM", $arrItems['ProductNum']); } else { $objTemplate->m_AssignContent("PROD_NUM", ""); }; };

    $strTable = "price"; $strSQL = "SELECT priceValue FROM $strTable WHERE itemKey = $formItemKey AND Currency = 'USD'"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't select price infromation.");

    $objTemplate->m_AssignContent("PRICE", mysql_result($objResult,"priceValue"));

    $strTable = "itemstatistics"; $strSQL = "SELECT statValue FROM $strTable WHERE itemKey = $formItemKey AND statName = 'Weight'"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't select weight.");

    if (mysql_num_rows($objResult)>0) { $objTemplate->m_AssignContent("WEIGHT", mysql_result($objResult,"statValue")); } else { $objTemplate->m_AssignContent("WEIGHT",""); };

    $strTable = "itemstatistics"; $strSQL = "SELECT statValue FROM $strTable WHERE itemKey = $formItemKey AND statName = 'Size'"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't select size.");

    for ($l=0;$l<=2;$l++) { $strSize = $objTemplate->m_AssignContent("SIZE", $arrSizeTypes[$l], $strSizeSection);

    if (mysql_num_rows($objResult)>0) { if ($arrSizeTypes[$l] == mysql_result($objResult,"statValue")) { $strSize = $objTemplate->m_AssignContent("SIZE_SELECTED", "CHECKED", $strSize); } else { $strSize = $objTemplate->m_AssignContent("SIZE_SELECTED", "", $strSize); }; } else { $strSize = $objTemplate->m_AssignContent("SIZE_SELECTED", "", $strSize); };

    $objTemplate->m_AssignSection("Size", $strSize); };

    } else { $objTemplate->m_AddElements("Add a New Item"); $objTemplate->m_AssignContent("ITEM_KEY", "-1"); $objTemplate->m_AssignContent("ITEM_NAME", ""); $objTemplate->m_AssignContent("PROD_NUM", ""); $objTemplate->m_AssignContent("SHORT_DESC", ""); $objTemplate->m_AssignContent("LONG_DESC", ""); $objTemplate->m_AssignContent("PRICE",""); $objTemplate->m_AssignContent("WEIGHT","");

    for ($l=0;$l<=2;$l++) { $strSize = $objTemplate->m_AssignContent("SIZE", $arrSizeTypes[$l], $strSizeSection); $strSize = $objTemplate->m_AssignContent("SIZE_SELECTED", "", $strSize); $objTemplate->m_AssignSection("Size", $strSize); }; }; };

    $objTemplate->m_PrepTemplate(); $objTemplate->m_Output(); }; ?>

    The template file for this page contains all the form elements you might expect. One item stands out, however. You will need to include a hidden form element that will hold the item key of the item being updated. The object page will supply the item key (or “-1” if a new item) to this element. The itemupdate.tpl file uses four new buttons: Submit, Reset, Image, and Category. Each needs an entry in the f_accessKey() function. case "s": f_doL(); break; case "r": f_doR(); break; case "i": f_doI('imageselect.php'); break; case "c": f_doI('catselect.php'); break; The f_doL() function has already been created, but you will need to add f_doR() and f_doI() to the i_interface.jss file. function f_doR() { window.location = window.location; } function f_doI(strPage) { f_subPage_x(strPage,document.forms[0].elements[0],450,300); }

    The new validation function for this page will need to be added to the i_catalog.jss file. function f_valDesc(objElement) { if (objElement.value == "") { objValidate.m_Alert_x("You must enter a description.", objElement); return false; } else if (!objValidate.m_isRangeLength(objElement.value, 200, 3)) { objValidate.m_Alert_x("The description must be more than 3 characters and no longer than 200.", objElement); return false; } else { return true; } } function f_valPrice(objElement) { if (!m_Exists(objElement.value)) { objValidate.m_Alert_x("You must enter a price.", objElement); return false; } else { return true; } } function f_ValidateItemUpdate(objForm) { if ((!f_valName(objForm.formItemName)) || (!f_valDesc(objForm.formShortDesc)) || (!f_valPrice(objForm.formPrice))) { return false; } else { return true; } } The itemupdate.tpl file also pioneers the use of subpages in the catalog interface. The image and catalog button both call a function f_subPage_x(). This function determines the user’s browser and either calls the modal dialog box function or the new window function. Add the f_subPage_x() function to the i_interface.jss file. function f_subPage_x(strPage,objElement,intW,intH) { var strNavigator = navigator.appName; var intItemKey = objElement.value; strPage = strPage + "?formItemKey=" + intItemKey;

    if (strNavigator != "Microsoft Internet Explorer") { var strVersion = navigator.appVersion.substr(0,3); } else { var strVersion = navigator.appVersion.substr(navigator.appVersion. indexOf("MSIE") + 5,3); }

    if ((strNavigator == "Microsoft Internet Explorer") && (strVersion >= 4)) { f_openDialog_x(strPage,intItemKey,intW,intH); } else if ((strNavigator == "Netscape") && (strVersion > 3)) { f_openWin_x(strPage,intW,intH); } else { return true; }

    } The f_openDialog_x() and f_openWin_x() functions are similar. Both take the name of the page to display and the dimensions of the resulting subpage as parameters. Other than the window type (dialog instead of standard), the primary difference between these functions is the type and number of display options. Add these functions to the i_interface.jss file. function f_subPageClose_x(varReturnValue) { var strNavigator = navigator.appName;

    if (strNavigator != "Microsoft Internet Explorer") { var strVersion = navigator.appVersion.substr(0,3); } else { var strVersion = navigator.appVersion.substr(navigator.appVersion.indexOf ("MSIE") + 5,3); }

    if ((strNavigator == "Microsoft Internet Explorer") && (strVersion >= 4)) { window.returnValue = varReturnValue; window.close(); } else if ((strNavigator == "Netscape") && (strVersion > 3)) { window.close(); } else { return true; } } function f_openWin_x(strPage,intW,intH) { window.blur(); var strDisplay = "width=" + intW + ",height=" + intH + ",status=no, dependent=yes,resizable=yes, scrollbars=no,toolbar=no,menubar=no"; objWindow = open(strPage, "", strDisplay); objWindow.focus(); }

    function f_openDialog_x(strPage,varVariabble,intW,intH) { var strDisplay = "dialogHeight:" + intH + "px; dialogWidth:" + intW + "px; help:No; resizable:Yes; status:No"; varReturn = window.showModalDialog(strPage,varVariable,strDisplay); } Figure 12.3 shows the completed page. Following is the code for the Add/Edit Item template file:

    Figure 12.3: The Item Add/Edit page

    itemupdate.tpl Catalog Maintenance - {{TITLE}} {{NAVIGATION}}

    {{ERRORS}}




    lbs.
    {{section Size }}{{SIZE}}  {{/section Size }}
      
    {{FOOTER}}

    Delete Item Page

    The template file for the Item Deletion page requires two important hidden form elements. The corresponding action page uses the item key from one to remove the item from the database and passes the item name from the other to the Delete Confirmation page. There are no new validation functions for this page, but there are two new buttons: Keep and Terminate. These buttons need entries in the f_accessKey() function. case "k": f_doK(); break; case "t": f_doT(); break;

    The new functions each change the name of the subType hidden field before submitting the form. function f_doK() { document.forms[0].subType.name = "formBack"; document.forms[0].submit(); } function f_doT() { document.forms[0].subType.name = "formSubmit"; document.forms[0].submit(); }

    Folowing is the code for the Delete Item template file: itemdelete.tpl Catalog Maintenance - {{TITLE}} {{NAVIGATION}}

    Confirm Deletion:

    Item Name: {{ITEM_NAME}}
    Product Number: {{PROD_NUM}}
    Short Description: {{DESC_SHORT}}
      
    {{FOOTER}}

    The object file for the Item Deletion page doesn’t hold any surprises. As with most of the other object pages in this interface, the purpose of this file is to gather the information and prepare it to be passed to the action page. The cooperation between the object and action pages is what creates a smooth interface. Figure 12.4 shows the final Item Deletion page. Here is the code for the Delete Item form page:

    Figure 12.4: The Item Delete page

    itemdelete.php

    global $HTTPS; global $REQUEST_URI; global $formItemKey;

    if (!isset($HTTPS)) { $strFileRequested = $REQUEST_URI; $strNewLocation = "https://ransom:443/" . $strFileRequested; Header("Location: " . $strNewLocation); exit; };

    session_start();

    if (!isset($GLOBALS["UserID"])) { session_register("sessRefer"); $GLOBALS["sessRefer"] = "itemdelete.php?formItemKey=" . $formItemKey; header("Location: secure.php?Page=login"); } else { $strFileName = "templates/itemdelete.tpl"; $objTemplate = new o_Template; $objTemplate->m_LoadFile($strFileName); $objTemplate->m_AddElements("Confirm Item Deletion"); $objTemplate->m_AssignContent("ITEM_KEY", $formItemKey);

    $strTable = "item"; $strSQL = "SELECT ItemName, ProductNum, DescShort FROM $strTable WHERE ItemKey = $formItemKey"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't select data.");

    while ($objItem = mysql_fetch_array($objResult)) { $objTemplate->m_AssignContent("ITEM_NAME", $objItem["ItemName"]); $objTemplate->m_AssignContent("PROD_NUM", $objItem["ProductNum"]); $objTemplate->m_AssignContent("DESC_SHORT", $objItem["DescShort"]); };

    $objTemplate->m_PrepTemplate(); $objTemplate->m_Output(); };

    ?>

    Image Selector Page

    The Image Selector is the first of the subpages you will be creating for this interface. As a subpage, it will need to be a bit more flexible in the way it is displayed. In older browsers this page will appear as part of a multiple-page form and will therefore need navigation elements. Users with newer browsers will see this page as a separate window; you don’t want to display the navigation elements to these users. The Image Selector object page uses the cookie variables that were assigned during the login to determine whether to display the navigation elements. if ((($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] < 5) && ($GLOBALS ["Version"] > 3)) || (($GLOBALS["Browser"] == "IE") || (($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] >= 5)))) { $objTemplate->m_AssignContent("TITLE", "Image Selection"); $objTemplate->m_AssignContent("NAVIGATION", ""); $objTemplate->m_AssignContent("FOOTER", ""); } else { $objTemplate->m_AddElements("Image Selection"); };

    Following is the code for the Image Selector form page: imageselect.php m_LoadFile($strFileName);

    if ((($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] < 5) && ($GLOBALS["Version"] > 3)) || (($GLOBALS["Browser"] == "IE") || (($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] >= 5)))) { $objTemplate->m_AssignContent("TITLE", "Image Selection"); $objTemplate->m_AssignContent("NAVIGATION", ""); $objTemplate->m_AssignContent("FOOTER", ""); } else { $objTemplate->m_AddElements("Image Selection"); };

    $strImageSection = $objTemplate->m_DefineSection("Images");

    if ($sessErrors) { $objTemplate->m_AssignContent("ERRORS", $sessErrors); } else { $objTemplate->m_AssignContent("ERRORS", ""); };

    session_unregister("sessErrors");

    $objTemplate->m_AssignContent("ITEM_KEY", $formItemKey);

    $strTable = "image"; $strSQL = "SELECT ImageKey, ImgName FROM $strTable"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't select image information.");

    while ($arrItems = mysql_fetch_array($objResult, MYSQL_ASSOC)) { $strImage = $objTemplate->m_AssignContent("IMAGE_KEY", $arrItems['ImageKey'], $strImageSection); $strImage = $objTemplate->m_AssignContent("IMAGE_NAME", $arrItems['ImgName'], $strImage); $strAssignTable = "imageassignment"; $strAssignSQL = "SELECT ImageKey FROM $strAssignTable WHERE ItemKey = $formItemKey AND ImageKey = " . $arrItems['ImageKey']; $objAssignResult = mysql_query($strAssignSQL,$CONNECTION) or die("Couldn't select image assignment information.");

    if (mysql_num_rows($objAssignResult)>0) { $strImage = $objTemplate->m_AssignContent("IMAGE_SELECTED", "SELECTED", $strImage); } else { $strImage = $objTemplate->m_AssignContent("IMAGE_SELECTED", "", $strImage); };

    $objTemplate->m_AssignSection("Images", $strImage); };

    $objTemplate->m_PrepTemplate(); $objTemplate->m_Output(); }; ?> The Image Selector template page only introduces one new user interface element: the Assign button. This button requires the following additions to the f_accessKey() function: case "a": f_doA(); break; The f_doA() function is similar to the f_doD() function, except that the hidden form element is renamed "formAssign" instead of "formDel". Add the f_doA() function to the i_interface.jss file. function f_doA() { if (document.forms[0].elements[1].selectedIndex >= 0) { document.forms[0].subType.name = "formAssign"; document.forms[0].submit(); } else { window.alert("You must select an option to proceed."); document.forms[0].elements[1].focus(); } } Note Notice that each subpage has a “window.name=’subpage’;” declaration in the onLoad attribute of the BODY tag. Using this attribute you can assign the target attribute of any links or FORM tags to "subpage." Without the subpage declaration and the use of the target attribute the browser is likely to open the resulting page in the parent window or a new window entirely. Figure 12.5 shows the finished Image Selector page. Here is the code for the Image Selector template file:

    Figure 12.5: The Image Selector subpage

    imageselect.tpl Catalog Maintenance - {{TITLE}} {{NAVIGATION}}

    {{ERRORS}}

    Select an Image to Assign/Update:

    Options:





    Add New Image

    {{FOOTER}}

    Add/Edit Image Page The Add/Edit image page is probably the most individual of all the form pages. Because this page involves manipulating the file system on the server, it has a few unique items that are not found on other pages. First, the FORM tag on this page must have the enctype attribute set to "multipart/form-data". Without this attribute, the server will not receive the file intact. Next, it has a number of new hidden form elements.

    The first two hold the item key and image key, respectively. The last two fields are used by the action page to move or rename the file on the server that corresponds to the image being edited. The imageupdate.tpl file also introduces a number of new validation functions. These should be added to the i_catalog.jss file. Both the height and width fields use the f_valSize() function, while the alt field uses f_valAlt(). function f_valSize(objElement) { if (!m_Exists(objElement.value)) { objValidate.m_Alert_x("You must enter a height and width.", objElement); return false; } else if ((!m_isInteger(objElement.value)) || (objElement.value < 0)) { objValidate.m_Alert_x("The height and width must contain only positive digits (no decimals).", objElement); return false; } else { return true; } } function f_valAlt(objElement) { if (!m_Exists(objElement.value)) { objValidate.m_Alert_x("You must enter an ALT value.", objElement); return false; } else { return true; } } The f_ValidateImageUpdate() function is called before form data can be submitted to the server. The function runs through the earlier validation functions, and then checks the value of the image key field to determine whether the page is updating an existing image or adding a new record to the database. If a new image is being added, then a file upload is required. function f_ValidateImageUpdate(objForm) { if ((!f_valName(objForm.formImageName)) || (!f_valSize(objForm.formHeight)) || (!f_valSize(objForm.formWidth)) || (!f_valAlt(objForm.formAlt))) { return false; } else if ((objForm.formImageKey.value < 0) && (objForm.formFile.value == "")) { objValidate.m_Alert_x("You must choose a file to upload.", objForm.formFile); return false; } else { return true; } }

    Here is the code for the Add/Edit Image template file: imageupdate.tpl Catalog Maintenance - {{TITLE}} {{NAVIGATION}}

    {{ERRORS}}

    New File:
      
    {{FOOTER}} The object file for this page is reminiscent of the itemupdate.php page. After the standard security checks, the page checks for the $sessMultiform session variable. This was the same variable used by itemupdate.php to remember form data. The imageupdate.php file uses the same technique. Then, like its sister page, imageupdate.php determines if the add variable has been sent or not. In this case that variable is $formAdd. if (!isset($formAdd)) { … Figure 12.6 shows the final Image Update page. Here is the code for the Add/Edit Image form page.

    Figure 12.6: The Image Add/Edit subpage

    imageupdate.php

    global $HTTPS; global $REQUEST_URI; global $formItemKey; global $formAdd; global $formImageKey; if (!isset($HTTPS)) { $strFileRequested = $REQUEST_URI; $strNewLocation = "https://ransom:443/" . $strFileRequested; Header("Location: " . $strNewLocation); exit; }; session_start(); if (!isset($GLOBALS["UserID"])) { session_register("sessRefer"); $GLOBALS["sessRefer"] = "itemselect.php?formItemKey=$formItemKey"; header("Location: secure.php?Page=login"); } else { $strFileName = "templates/imageupdate.tpl"; $objTemplate = new o_Template; $objTemplate->m_LoadFile($strFileName);

    if ($sessErrors) { $objTemplate->m_AssignContent("ERRORS", $sessErrors); } else { $objTemplate->m_AssignContent("ERRORS", ""); };

    session_unregister("sessErrors");

    if (isset($sessMultiform)) { if ((($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] < 5) && ($GLOBALS["Version"] > 3)) || (($GLOBALS["Browser"] == "IE") || (($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] >= 5)))) { $objTemplate->m_AssignContent("TITLE", "Edit Image Information"); $objTemplate->m_AssignContent("NAVIGATION", ""); $objTemplate->m_AssignContent("FOOTER", ""); } else { $objTemplate->m_AddElements("Edit Image Information"); };

    $objTemplate->m_AssignContent("ITEM_KEY", $HTTP_SESSION_VARS["formItemKey"]); $objTemplate->m_AssignContent("IMAGE_KEY", $HTTP_SESSION_VARS["formImageKey"]); $objTemplate->m_AssignContent("IMAGE_NAME", $HTTP_SESSION_VARS["formImageName"]); $objTemplate->m_AssignContent("OLD_FILE_NAME", $HTTP _SESSION_VARS["formOldFileDir"]); $objTemplate->m_AssignContent("OLD_FILE_DIR", $HTTP_SESSION_VARS["formOldFileName"]); $objTemplate->m_AssignContent("FILE_NAME", $HTTP_SESSION_VARS["formFileName"]); $objTemplate->m_AssignContent("FILE_DIR", $HTTP_SESSION_VARS["formFileDir"]); $objTemplate->m_AssignContent("HEIGHT", $HTTP_SESSION_VARS["formHeight"]); $objTemplate->m_AssignContent("WIDTH", $HTTP_SESSION_VARS["formWidth"]); $objTemplate->m_AssignContent("BORDER", $HTTP_SESSION_VARS["formBorder"]); $objTemplate->m_AssignContent("ALT", $HTTP_SESSION_VARS["formAlt"]);

    session_unregister("formItemKey"); session_unregister("formImageKey"); session_unregister("formImageName"); session_unregister("formOldFileDir"); session_unregister("formOldFileName"); session_unregister("formFileName"); session_unregister("formFileDir"); session_unregister("formHeight"); session_unregister("formWidth"); session_unregister("formBorder"); session_unregister("formAlt"); session_unregister("sessMultiform"); } else { if (!isset($formAdd)) { if ((($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] < 5) && ($GLOBALS["Version"] > 3)) || (($GLOBALS["Browser"] == "IE") || (($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] >= 5)))) { $objTemplate->m_AssignContent("TITLE", "Edit Image Information"); $objTemplate->m_AssignContent("NAVIGATION", ""); $objTemplate->m_AssignContent("FOOTER", ""); } else { $objTemplate->m_AddElements("Edit Image Information"); };

    $strTable = "image"; $strSQL = "SELECT * FROM $strTable WHERE ImageKey=" . $formImageKey; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't select image information."); $objTemplate->m_AssignContent("ITEM_KEY", $formItemKey); $objTemplate->m_AssignContent("IMAGE_KEY", $formImageKey);

    while ($objImage = mysql_fetch_array($objResult)) { $objTemplate->m_AssignContent("IMAGE_NAME", $objImage["ImgName"]); $objTemplate->m_AssignContent("FILE_NAME", $objImage["FileName"]); $objTemplate->m_AssignContent("FILE_DIR", $objImage["FileDir"]); $objTemplate->m_AssignContent("OLD_FILE_NAME", $objImage["FileName"]); $objTemplate->m_AssignContent("OLD_FILE_DIR", $objImage["FileDir"]); $objTemplate->m_AssignContent("HEIGHT", $objImage["Height"]); $objTemplate->m_AssignContent("WIDTH", $objImage["Width"]); $objTemplate->m_AssignContent("BORDER", $objImage["Border"]); $objTemplate->m_AssignContent("ALT", $objImage["Alt"]); };

    $objTemplate->m_AssignContent("RESET", "formReset"); } else { if ((($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] < 5) && ($GLOBALS["Version"] > 3)) || (($GLOBALS["Browser"] == "IE") || (($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] >= 5)))) { $objTemplate->m_AssignContent("TITLE", "Add a New Image"); $objTemplate->m_AssignContent("NAVIGATION", ""); $objTemplate->m_AssignContent("FOOTER", ""); } else { $objTemplate->m_AddElements("Add a New Image"); };

    $objTemplate->m_AssignContent("ITEM_KEY", $formItemKey); $objTemplate->m_AssignContent("IMAGE_KEY", "-1"); $objTemplate->m_AssignContent("IMAGE_NAME", ""); $objTemplate->m_AssignContent("FILE_NAME", ""); $objTemplate->m_AssignContent("FILE_DIR", ""); $objTemplate->m_AssignContent("HEIGHT", ""); $objTemplate->m_AssignContent("WIDTH", ""); $objTemplate->m_AssignContent("BORDER", ""); $objTemplate->m_AssignContent("ALT", ""); $objTemplate->m_AssignContent("RESET", "formResetAdd");

    };

    };

    $objTemplate->m_PrepTemplate(); $objTemplate->m_Output(); };

    ?>

    Delete Image Page

    Because the Delete Image page requires no form validation, the code added to the other form pages to induce memory or error display is not necessary. This produces a simple object file that only needs to gather the necessary information for display. Here is the code for the Delete Image form page: imagedelete.php m_LoadFile($strFileName); $objTemplate->m_AssignContent("ITEM_KEY", $formItemKey); $objTemplate->m_AssignContent("IMAGE_KEY", $formImageKey);

    $strTable = "image"; $strSQL = "SELECT * FROM $strTable WHERE ImageKey = $formImageKey"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't select data.");

    if ((($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] < 5) && ($GLOBALS["Version"] > 3)) || (($GLOBALS["Browser"] == "IE") || (($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] >= 5)))) { $objTemplate->m_AssignContent("TITLE", "Confirm Image Deletion"); $objTemplate->m_AssignContent("NAVIGATION", ""); $objTemplate->m_AssignContent("FOOTER", ""); } else { $objTemplate->m_AddElements("Confirm Image Deletion"); };

    while ($objImage = mysql_fetch_array($objResult)) { $objTemplate->m_AssignContent("IMAGE_NAME", $objImage["ImgName"]); $objTemplate->m_AssignContent("FILE_NAME", $objImage["FileName"]); $objTemplate->m_AssignContent("FILE_DIR", $objImage["FileDir"]); $objTemplate->m_AssignContent("HEIGHT", $objImage["Height"]); $objTemplate->m_AssignContent("WIDTH", $objImage["Width"]); $objTemplate->m_AssignContent("BORDER", $objImage["Border"]); $objTemplate->m_AssignContent("ALT", $objImage["Alt"]); };

    $objTemplate->m_PrepTemplate(); $objTemplate->m_Output(); };

    ?> The template file also requires very little innovation. The most important elements are the hidden fields that will be used by the action page. Figure 12.7 shows the completed Image Delete page. Here is the code for the Delete Image template file:

    Figure 12.7: The Image Delete subpage

    imagedelete.tpl Catalog Maintenance - {{TITLE}} {{NAVIGATION}}

    Confirm Deletion:

    Image Name: {{IMAGE_NAME}}
    File Name: {{FILE_NAME}}
    File Directory: {{FILE_DIR}}
    Height: {{HEIGHT}}
    Width: {{WIDTH}}
    Border: {{BORDER}}
    Alt Tag: {{ALT}}
      
    {{FOOTER}}

    Category Selector Page The category pages can be displayed as subpages and also as independent pages. This presents a unique challenge. The object page looks for the $Lone variable (usually found in the query string) to determine whether to display the navigation elements. if (isset($Lone)) { $objTemplate->m_AddElements("Category Selection"); } else if ((($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] < 5) && ($GLOBALS["Version"] > 3)) || (($GLOBALS["Browser"] == "IE") || (($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] >= 5)))) { $objTemplate->m_AssignContent("TITLE", "Category Selection"); $objTemplate->m_AssignContent("NAVIGATION", ""); $objTemplate->m_AssignContent("FOOTER", ""); } else { $objTemplate->m_AddElements("Category Selection"); }; Additionally the “Assign” section is used to hide or show the assignment button. The section appears only if the $Lone variable is not set. $strAssignSection = $objTemplate->m_DefineSection("Assign"); if (!isset($Lone)) { $strAssign = $objTemplate->m_AssignContent("ASSIGN", "formAssign", $strAssignSection); $objTemplate->m_AssignSection("Assign", $strAssign); $objTemplate->m_AssignContent("LONE", ""); } else { $objTemplate->m_AssignContent("LONE", "1"); }; Here is the final Category Selector form page: catselect.php m_LoadFile($strFileName);

    if (isset($Lone)) { $objTemplate->m_AddElements("Category Selection"); } else if ((($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] < 5) && ($GLOBALS["Version"] > 3)) || (($GLOBALS["Browser"] == "IE") || (($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] >= 5)))) { $objTemplate->m_AssignContent("TITLE", "Category Selection"); $objTemplate->m_AssignContent("NAVIGATION", ""); $objTemplate->m_AssignContent("FOOTER", ""); } else { $objTemplate->m_AddElements("Category Selection"); };

    if (!isset($formItemKey)) { $formItemKey = -1; };

    $strCatSection = $objTemplate->m_DefineSection("Categories"); $strAssignSection = $objTemplate->m_DefineSection("Assign");

    if (!isset($Lone)) { $strAssign = $objTemplate->m_AssignContent("ASSIGN", "formAssign", $strAssignSection); $objTemplate->m_AssignSection("Assign", $strAssign); $objTemplate->m_AssignContent("LONE", ""); } else { $objTemplate->m_AssignContent("LONE", "1"); };

    if ($sessErrors) { $objTemplate->m_AssignContent("ERRORS", $sessErrors); } else { $objTemplate->m_AssignContent("ERRORS", ""); };

    session_unregister("sessErrors");

    $objTemplate->m_AssignContent("ITEM_KEY", $formItemKey);

    $strTable = "category"; $strSQL = "SELECT CategoryKey, CatName FROM $strTable"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't select categories."); $strAssignTable = "categoryassignment";

    while ($arrItems = mysql_fetch_array($objResult, MYSQL_ASSOC)) { $strCat = $objTemplate->m_AssignContent("CAT_KEY", $arrItems['CategoryKey'], $strCatSection); $strCat = $objTemplate->m_AssignContent("CAT_NAME", $arrItems['CatName'], $strCat);

    if ($formItemKey > 0) { $strAssignSQL = "SELECT CategoryKey FROM $strAssignTable WHERE ItemKey = $formItemKey AND CategoryKey = " . $arrItems['CategoryKey']; $objAssignResult = mysql_query($strAssignSQL,$CONNECTION) or die("Couldn't select category key.");

    if (mysql_num_rows($objAssignResult)>0) { $strCat = $objTemplate->m_AssignContent("CAT_SELECTED", "SELECTED", $strCat); } else { $strCat = $objTemplate->m_AssignContent("CAT_SELECTED", "", $strCat); }; } else { $strCat = $objTemplate->m_AssignContent("CAT_SELECTED", "", $strCat); };

    $objTemplate->m_AssignSection("Categories", $strCat); }

    $objTemplate->m_PrepTemplate(); $objTemplate->m_Output(); }; ?> The catselect.tpl file works with the object page to pass the $Lone variable to the action page. This is accomplished using a hidden form element titled "Lone." The value of this element is assigned in the object page.

    Additionally, the “Add New Category” link has the “Lone” value attached. Add New Category Figure 12.8 shows the finished Category Selector page. Here is the code for the Category Selector template file:

    Figure 12.8: The Category Selector “Lone” page

    catselect.tpl Catalog Maintenance - {{TITLE}} {{NAVIGATION}}

    {{ERRORS}}

    Select a Category to Assign/Update:

    Options:

    {{section Assign }}

    {{/section Assign }}

    Add New Category

    {{FOOTER}}

    Add/Edit Category Page

    The last of the update pages, the category Add/Edit Category page uses the dynamic section “Images” to display the image information from the database in a select box. The None option allows the user to create a category without an image or remove the image from an existing category. The imageupdate.tpl file recycles a number of validation functions. One new function is f_ValidateCatUpdate(). This function simply references a series of other validation functions. Add the f_ValidateCatUpdate() function to the i_catalog.jss file. function f_ValidateCatUpdate(objForm) { if ((!f_valName(objForm.formCatName)) || (!f_valDesc(objForm.formDescription))) { return false; } else { return true; } }

    Here is the code for the add/edit category template file: catupdate.tpl Catalog Maintenance - {{TITLE}} {{NAVIGATION}}

    {{ERRORS}}


      
    {{FOOTER}} The object file uses a common select statement to gather information from the image table of the database. Later, while loops rotate through the results to display the data. Figure 12.9 shows how the image information is displayed.

    Figure 12.9: The Add/Edit Category “Lone” page $strImageTable = "image"; $strImageSQL = "SELECT ImageKey, ImgName FROM $strImageTable"; $objImageResult = mysql_query($strImageSQL,$CONNECTION) or die("Couldn't select images.");

    while ($arrImageArray = mysql_fetch_array($objImageResult, MYSQL_ASSOC)) { $strImage = $objTemplate->m_AssignContent("IMAGE_KEY", $arrImageArray["ImageKey"], $strImageSection); $strImage = $objTemplate->m_AssignContent("IMAGE_NAME", $arrImageArray["ImgName"], $strImage);

    if ($arrImageArray["ImageKey"] == $HTTP_SESSION_VARS["formImage"]) { $strImage = $objTemplate->m_AssignContent("IMAGE_SELECTED", "SELECTED", $strImage); } else { $strImage = $objTemplate->m_AssignContent("IMAGE_SELECTED", "", $strImage); };

    $objTemplate->m_AssignSection("Images", $strImage); };

    The catupdate.php file uses the same session variable technique as the other update pages to remember data. Here is the code for the Add/Edit Category form page: catupdate.php m_LoadFile($strFileName);

    if ($sessErrors) { $objTemplate->m_AssignContent("ERRORS", $sessErrors); } else { $objTemplate->m_AssignContent("ERRORS", ""); };

    session_unregister("sessErrors");

    if (!isset($Lone)) { if ((($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] < 5) && ($GLOBALS["Version"] > 3)) || (($GLOBALS["Browser"] == "IE") || (($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] >= 5)))) { $objTemplate->m_AssignContent(" TITLE", "Add/Update Category Information"); $objTemplate->m_AssignContent("NAVIGATION", ""); $objTemplate->m_AssignContent("FOOTER", ""); $objTemplate->m_AssignContent("LONE", ""); } else { $objTemplate->m_AddElements("Add/Update Category Information"); $objTemplate->m_AssignContent("LONE", ""); }; } else { $objTemplate->m_AddElements("Add/Update Category Information"); $objTemplate->m_AssignContent("LONE", "1"); };

    $strImageTable = "image"; $strImageSQL = "SELECT ImageKey, ImgName FROM $strImageTable"; $objImageResult = mysql_query($strImageSQL,$CONNECTION) or die("Couldn't select images.");

    if (isset($sessMultiform)) { $objTemplate->m_AssignContent("ITEM_KEY", $HTTP_SESSION_VARS["formItemKey"]); $objTemplate->m_AssignContent("CAT_KEY", $HTTP_SESSION_VARS["formCatKey"]); $objTemplate->m_AssignContent("CAT_NAME", $HTTP_SESSION_VARS["formCatName"]); $objTemplate->m_AssignContent("DESCRIPTION", $HTTP_SESSION_VARS["formDescription"]);

    if ($HTTP_SESSION_VARS["formImage"] == "0") { $objTemplate->m_AssignContent("NONE_SELECTED", "SELECTED"); } else { $objTemplate->m_AssignContent("NONE_SELECTED", "");

    while ($arrImageArray = mysql_fetch_array($objImageResult, MYSQL_ASSOC)) { $strImage = $objTemplate->m_AssignContent("IMAGE_KEY", $arrImageArray["ImageKey"], $strImageSection); $strImage = $objTemplate->m_AssignContent("IMAGE_NAME", $arrImageArray["ImgName"], $strImage);

    if ($arrImageArray["ImageKey"] == $HTTP_SESSION_VARS["formImage"]) { $strImage = $objTemplate->m_AssignContent("IMAGE_SELECTED", "SELECTED", $strImage); } else { $strImage = $objTemplate->m_AssignContent("IMAGE_SELECTED", "", $strImage); };

    $objTemplate->m_AssignSection("Images", $strImage); };

    };

    session_unregister("formItemKey"); session_unregister("formCatKey"); session_unregister("formCatName"); session_unregister("formDescription"); session_unregister("formImage"); session_unregister("sessMultiform"); } else { if (!isset($Add)) { $strImageSection = $objTemplate->m_DefineSection("Images");

    $strTable = "category"; $strSQL = "SELECT * FROM $strTable WHERE CategoryKey=" . $formCatKey; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't select category information.");

    $objTemplate->m_AssignContent("ITEM_KEY", $formItemKey); $objTemplate->m_AssignContent("CAT_KEY", $formCatKey);

    while ($objCat = mysql_fetch_array($objResult)) { $objTemplate->m_AssignContent("CAT_NAME", $objCat["CatName"]); $objTemplate->m_AssignContent("DESCRIPTION", $objCat["Description"]);

    if ($objCat["ImageKey"] == "") { $objTemplate->m_AssignContent("NONE_SELECTED", "SELECTED"); } else { $objTemplate->m_AssignContent("NONE_SELECTED", "");

    while ($arrImageArray = mysql_fetch_array($objImageResult, MYSQL_ASSOC)) { $strImage = $objTemplate->m_AssignContent("IMAGE_KEY", $arrImageArray["ImageKey"], $strImageSection); $strImage = $objTemplate->m_AssignContent("IMAGE_NAME", $arrImageArray["ImgName"], $strImage);

    if ($arrImageArray["ImageKey"] == $objCat["ImageKey"]) { $strImage = $objTemplate->m_AssignContent("IMAGE_SELECTED", "SELECTED", $strImage); } else { $strImage = $objTemplate->m_AssignContent("IMAGE_SELECTED", "", $strImage); };

    $objTemplate->m_AssignSection("Images", $strImage); }; }; };

    $objTemplate->m_AssignContent("RESET", "formReset"); } else { $objTemplate->m_AssignContent("ITEM_KEY", $formItemKey); $objTemplate->m_AssignContent("CAT_KEY", "-1"); $objTemplate->m_AssignContent("CAT_NAME", ""); $objTemplate->m_AssignContent("DESCRIPTION", $objCat["Description"]); $objTemplate->m_AssignContent("DESCRIPTION", ""); $objTemplate->m_AssignContent("NONE_SELECTED", "SELECTED"); $strImageSection = $objTemplate->m_DefineSection("Images");

    while ($arrImageArray = mysql_fetch_array($objImageResult, MYSQL_ASSOC)) { $strImage = $objTemplate->m_AssignContent("IMAGE_KEY", $arrImageArray["ImageKey"], $strImageSection); $strImage = $objTemplate->m_AssignContent("IMAGE_NAME", $arrImageArray["ImgName"], $strImage); if ($arrImageArray["ImageKey"] == $objCat["ImageKey"]) { $strImage = $objTemplate->m_AssignContent("IMAGE_SELECTED", "SELECTED", $strImage); } else { $strImage = $objTemplate->m_AssignContent("IMAGE_SELECTED", "", $strImage); };

    $objTemplate->m_AssignSection("Images", $strImage); };

    $objTemplate->m_AssignContent("RESET", "formResetAdd");

    };

    };

    $objTemplate->m_PrepTemplate(); $objTemplate->m_Output(); };

    ?>

    Delete Category Page Like the other category object pages, the catdelete.php file must determine whether to display the navigation elements. The values of the $Lone variable and the browser cookie variables determine the result. Additionally, the file adds a value to the "LONE" dynamic content area if the $Lone variable is present. Other than these small changes the final page is very similar to the Image Delete page. Figure 12.10 shows the end result.

    Figure 12.10: The Category Delete “Lone” page

    Here is the code for the Delete Category form page, followed by the code for the corresponding template file:

    catdelete.php

    global $HTTPS; global $REQUEST_URI; global $formCatKey; global $formItemKey;

    if (!isset($HTTPS)) { $strFileRequested = $REQUEST_URI; $strNewLocation = "https://ransom:443/" . $strFileRequested; Header("Location: " . $strNewLocation); exit; };

    session_start();

    if (!isset($GLOBALS["UserID"])) { session_register("sessRefer"); $GLOBALS["sessRefer"] = "catdelete.php?formCatKey=" . $formCatKey . "&formItemKey=" . $formItemKey; header("Location: secure.php?Page=login"); } else { $strFileName = "templates/catdelete.tpl"; $objTemplate = new o_Template; $objTemplate->m_LoadFile($strFileName); $objTemplate->m_AssignContent("ITEM_KEY", $formItemKey); $objTemplate->m_AssignContent("CAT_KEY", $formCatKey);

    $strTable = "category"; $strSQL = "SELECT CategoryKey, CatName, Description, ImgName FROM $strTable LEFT JOIN image on image.ImageKey=$strTable.ImageKey WHERE CategoryKey = $formCatKey"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't select data.");

    if (!isset($Lone)) { if ((($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] < 5) && ($GLOBALS["Version"] > 3)) || (($GLOBALS["Browser"] == "IE") || (($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] >= 5)))) { $objTemplate->m_AssignContent("TITLE", "Confirm Category Deletion"); $objTemplate->m_AssignContent("NAVIGATION", ""); $objTemplate->m_AssignContent("FOOTER", ""); $objTemplate->m_AssignContent("LONE", ""); } else { $objTemplate->m_AddElements("Confirm Category Deletion"); $objTemplate->m_AssignContent("LONE", ""); };

    } else { $objTemplate->m_AddElements("Confirm Category Deletion"); $objTemplate->m_AssignContent("LONE", "1"); };

    while ($objImage = mysql_fetch_array($objResult)) { $objTemplate->m_AssignContent("CAT_NAME", $objImage["CatName"]); $objTemplate->m_AssignContent("DESCRIPTION", $objImage["Description"]); $objTemplate->m_AssignContent("IMAGE", $objImage["ImgName"]); };

    $objTemplate->m_PrepTemplate(); $objTemplate->m_Output(); };

    ?> catdelete.tpl Catalog Maintenance - {{TITLE}} {{NAVIGATION}}

    Confirm Deletion:

    Category Name: {{CAT_NAME}}
    Description: {{DESCRIPTION}}
    Image: {{IMAGE}}
      
    {{FOOTER}}

    Shopping Cart Page

    The Shopping Cart page is the first of the public and unsecure pages for the catalog interface. As an unsecure page, its object and template files reside outside the secure directory. This means that any references to global files should include a link to the correct relative location. The cart.tpl file introduces two new buttons: Update and Make Purchase. Each of these buttons requires an addition to the f_accessKey() function. case "u": f_doU(); break; case "m": f_doT(); break; The new function, f_doU(), should look familiar. It follows the same protocol of assigning the name of a hidden form field to a specific value before submitting the form. Add the f_doU() function to the i_interface.jss file. function f_doU() { document.forms[0].subType.name = "formUpdate"; document.forms[0].submit(); } In addition, two new validation functions are called from the cart.tpl file. The f_valQuantity() and f_ValidateCart() functions should be added to the i_catalog .jss file. The first function simply checks that an acceptable value (an integer or blank) has been added to the text field. The f_ValidateCart() function does the same, but it checks every text field in the form. function f_valQuantity(objElement) { if (m_Exists(objElement.value)) { if (!m_isInteger(objElement.value)) { objValidate.m_Alert_x("You must enter a numerical value only.", objElement); return false; } else { return true; }

    } else { return true; } } function f_ValidateCart(objForm) { for (l=0;l

    Here is the code for the Shopping Cart template file: cart.tpl {{NAVIGATION}}

    Here are the items in your cart:

    {{ERRORS}}

    {{section Items }} {{/section Items }}
    Product Number Item Price Quantity
    {{PROD_NUM}} {{ITEM_NAME}} {{PRICE}}

     

    Total: ${{TOTAL}}
    {{FOOTER}} Note Notice that the item key and quantity fields are named with brackets like formQuantity[]. This allows the action page to read the various submitted values as an array, meaning there’s no limit to the number of different items that can be processed. The object file for the Shopping Cart page might seem a little confusing at first, but after a little study it will become clear. The code uses a multi-dimensional array variable, $arrItems, to hold both the item keys and the quantities. Between pages this array is stored as the session variable $sessCart. The first step in the cart.php page is to gather the information from the session variable and place it in $arrItems. This is done using two nested foreach loops. session_start(); $arrItems = array(); if (isset($sessCart)) { foreach ($sessCart AS $ItemKey=>$Item) { foreach ($Item AS $Quantity) { $arrItems[$ItemKey]['Quantity'] = $Quantity; };

    };

    };

    When the user clicks on the Add to Cart link, the variable “Add” is appended to the query string. The value of “Add” is the item key of the item to add to the cart. The code handles this by looking for the “Add” variable. If it is found, then the quantity is set to 1 if the item key is not found in the array or the quantity is increased by 1 if it is found. if (isset($Add)) { if ($arrItems[$Add]['Quantity'] > 0) { $arrItems[$Add]['Quantity'] = $arrItems[$Add]['Quantity'] + 1; } else { $arrItems[$Add]['Quantity'] = 1; };

    };

    Next the session variable is set to the new array. If the “Add” variable exists, the user is directed to a page without the addition to the querystring. This prevents users from refreshing the page and accidentally increasing their order. session_register('sessCart'); $GLOBALS['sessCart'] = $arrItems;

    if (isset($Add)) { header("Location: cart.php"); } The remainder of the cart.php file falls into line with the other form pages. Some simple math and string manipulation produces a running total, and a counter variable allows you to set the tabindex values of the form elements. Figure 12.11 shows the result.

    Figure 12.11: The Shopping Cart page

    Following is the code for the Shopping Cart form page:

    cart.php

    global $Add; $arrItems = array(); session_start();

    $strFileName = "templates/cart.tpl"; $objTemplate = new o_Template; $objTemplate->m_LoadFile($strFileName); $objTemplate->m_AddElements("Shopping Cart"); $objTemplate->m_DefineSection("AddToCart"); $strItemSection = $objTemplate->m_DefineSection("Items"); if (isset($sessCart)) { foreach ($sessCart AS $ItemKey=>$Item) { foreach ($Item AS $Quantity) { $arrItems[$ItemKey]['Quantity'] = $Quantity; };

    };

    }; if (isset($Add)) { if ($arrItems[$Add]['Quantity'] > 0) { $arrItems[$Add]['Quantity'] = $arrItems[$Add]['Quantity'] + 1; } else { $arrItems[$Add]['Quantity'] = 1; };

    }; session_register('sessCart'); $GLOBALS['sessCart'] = $arrItems; if ($sessErrors) { $objTemplate->m_AssignContent("ERRORS", $sessErrors); } else { $objTemplate->m_AssignContent("ERRORS", ""); }; session_unregister("sessErrors");

    $intItemIndex = 0; $intTotal = 0; $strTable = "item"; $strPriceTable = "price"; foreach ($arrItems AS $ItemKey=>$Item) { $strSQL = "SELECT ItemName, ProductNum, $strPriceTable.PriceValue FROM $strTable INNER JOIN $strPriceTable ON $strTable.ItemKey=$strPriceTable.ItemKey WHERE $strTable.ItemKey = $ItemKey AND Currency = \"USD\""; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't select categories.");

    while ($arrItem = mysql_fetch_array($objResult, MYSQL_ASSOC)) { $strItem = $objTemplate->m_AssignContent("ITEM_NAME", $arrItem['ItemName'], $strItemSection); $strItem = $objTemplate->m_AssignContent("PROD_NUM", $arrItem['ProductNum'], $strItem); $strItem = $objTemplate->m_AssignContent("ITEM_KEY", $ItemKey, $strItem); $strItem = $objTemplate->m_AssignContent("PRICE", $arrItem['PriceValue'], $strItem); $strItem = $objTemplate->m_AssignContent("QUANTITY", $Item['Quantity'], $strItem); $strItem = $objTemplate->m_AssignContent("ITEM_INDEX", $intItemIndex + 1, $strItem); $strItem = $objTemplate->m_AssignSection("Items", $strItem); $intTotal = $intTotal + ($arrItem['PriceValue'] * $Item['Quantity']); };

    $intItemIndex++; };

    $objTemplate->m_AssignContent("NEXT_ITEM_INDEX", $intItemIndex + 1); $objTemplate->m_AssignContent("LAST_ITEM_INDEX", $intItemIndex + 2); settype($intTotal,'string'); if (!strpos($intTotal,'.')) { $intTotal = $intTotal . "."; }; while ((strrpos($intTotal,'.')) > (strlen($intTotal) - 3)) { $intTotal = $intTotal . "0"; };

    $objTemplate->m_AssignContent("TOTAL", $intTotal); $objTemplate->m_PrepTemplate(); $objTemplate->m_Output();

    ?>

    Checkout Page The Checkout page is the last of the form pages for the catalog interface. This page is public but secure, meaning it goes inside the secure directory. The object file for this page works similarly to the other object pages. You should have noticed by now that the secure and unsecure pages do not share the same navigation. This is because the m_AddElements method uses a relative location to find the navigation and footer files and the secure and unsecure servers have different document root directories. Therefore, the relative files are in different places depending on whether the m_AddElements method is called from an HTTP or HTTPS file. This works out great, as you don’t want the two parts of the interface to share navigation anyway. The exception is the Checkout page. This page resides on the secure server but needs to include the unsecure navigation. The fix is simple enough: just call the navigation elements manually rather than using the m_AddElements method. $objTemplate->m_LoadFile("../templates/navigation.tpl","NAVIGATION"); $objTemplate->m_LoadFile("../templates/footer.tpl","FOOTER"); $objTemplate->m_AssignContent("TITLE","DWF - " . "Check Out");

    After that, the code should look familiar by now. The Checkout page uses the session variable memory technique explained earlier and the same multi-dimensional array as the Shopping Cart page.

    Here is the code for the Checkout form page: checkout.php

    $arrItems = array(); $arrCardTypes = array(); $arrCardTypes[0] = "Visa"; $arrCardTypes[1] = "Mastercard"; $arrCardTypes[2] = "American Express"; session_start();

    $strFileName = "templates/checkout.tpl"; $objTemplate = new o_Template; $objTemplate->m_LoadFile($strFileName); $objTemplate->m_LoadFile("../templates/navigation.tpl","NAVIGATION"); $objTemplate->m_LoadFile("../templates/footer.tpl","FOOTER"); $objTemplate->m_AssignContent("TITLE","DWF - " . "Check Out"); $objTemplate->m_DefineSection("AddToCart"); $strItemSection = $objTemplate->m_DefineSection("Items"); $strCardTypesSection = $objTemplate->m_DefineSection("CardTypes"); if (isset($sessCart)) { foreach ($sessCart AS $ItemKey=>$Item) { foreach ($Item AS $Quantity) { $arrItems[$ItemKey]['Quantity'] = $Quantity; };

    };

    }; if ($sessErrors) { $objTemplate->m_AssignContent("ERRORS", $sessErrors); } else { $objTemplate->m_AssignContent("ERRORS", ""); }; session_unregister("sessErrors"); if (isset($sessMultiform)) { $objTemplate->m_AssignContent("NAME", $HTTP_SESSION_VARS["formName"]); $objTemplate->m_AssignContent("ADDRESS", $HTTP_SESSION_VARS["formAddress"]); $objTemplate->m_AssignContent("CITY", $HTTP_SESSION_VARS["formCity"]); $objTemplate->m_AssignContent("STATE", $HTTP_SESSION_VARS["formState"]); $objTemplate->m_AssignContent("ZIP", $HTTP_SESSION_VARS["formZip"]); $objTemplate->m_AssignContent("COUNTRY", $HTTP_SESSION_VARS["formCountry"]); $objTemplate->m_AssignContent("EMAIL", $HTTP_SESSION_VARS["formEmail"]); $objTemplate->m_AssignContent("PHONE", $HTTP_SESSION_VARS["formPhone"]); $objTemplate->m_AssignContent("FAX", $HTTP_SESSION_VARS["formFax"]); $objTemplate->m_AssignContent("CARD_NUM", $HTTP_SESSION_VARS["formCardNum"]); $objTemplate->m_AssignContent("EXPIRE", $HTTP_SESSION_VARS["formExpire"]);

    for ($l=0;$l<=2;$l++) { $strCard = $objTemplate->m_AssignContent("CARD_TYPE", $arrCardTypes[$l], $strCardTypesSection); if ($HTTP_SESSION_VARS["formCardType"] == $arrCardTypes[$l]) { $strCard = $objTemplate->m_AssignContent("TYPE_SELECTED", "SELECTED", $strCard); } else { $strCard = $objTemplate->m_AssignContent("TYPE_SELECTED", "", $strCard); };

    $objTemplate->m_AssignSection("CardTypes", $strCard); };

    session_unregister("formName"); session_unregister("formAddress"); session_unregister("formCity"); session_unregister("formState"); session_unregister("formZip"); session_unregister("formCountry"); session_unregister("formEmail"); session_unregister("formPhone"); session_unregister("formFax"); session_unregister("formCardNum"); session_unregister("formExpire"); session_unregister("formCardType"); session_unregister("formUpdate"); session_unregister("formSubmit"); session_unregister("sessMultiform"); } else { $objTemplate->m_AssignContent("NAME", ""); $objTemplate->m_AssignContent("ADDRESS", ""); $objTemplate->m_AssignContent("CITY", ""); $objTemplate->m_AssignContent("STATE", ""); $objTemplate->m_AssignContent("ZIP", ""); $objTemplate->m_AssignContent("COUNTRY", ""); $objTemplate->m_AssignContent("EMAIL", ""); $objTemplate->m_AssignContent("PHONE", ""); $objTemplate->m_AssignContent("FAX", ""); $objTemplate->m_AssignContent("CARD_NUM", ""); $objTemplate->m_AssignContent("EXPIRE", "");

    for ($l=0;$l<=2;$l++) { $strCard = $objTemplate->m_AssignContent("CARD_TYPE", $arrCardTypes[$l], $strCardTypesSection); $strCard = $objTemplate->m_AssignContent("TYPE_SELECTED", "", $strCard); $objTemplate->m_AssignSection("CardTypes", $strCard); };

    };

    $intItemIndex = 0; $intTotal = 0; $strTable = "item"; $strPriceTable = "price"; foreach ($arrItems AS $ItemKey=>$Item) { $strSQL = "SELECT ItemName, ProductNum, $strPriceTable.PriceValue FROM $strTable INNER JOIN $strPriceTable ON $strTable.ItemKey=$strPriceTable.ItemKey WHERE $strTable.ItemKey = $ItemKey AND Currency = \"USD\""; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't select categories.");

    while ($arrItem = mysql_fetch_array($objResult, MYSQL_ASSOC)) { $strItem = $objTemplate->m_AssignContent("ITEM_NAME", $arrItem['ItemName'], $strItemSection); $strItem = $objTemplate->m_AssignContent("PROD_NUM", $arrItem['ProductNum'], $strItem); $strItem = $objTemplate->m_AssignContent("ITEM_KEY", $ItemKey, $strItem); $strItem = $objTemplate->m_AssignContent("PRICE", $arrItem['PriceValue'], $strItem); $strItem = $objTemplate->m_AssignContent("QUANTITY", $Item['Quantity'], $strItem); $strItem = $objTemplate->m_AssignContent("ITEM_INDEX", $intItemIndex + 1, $strItem); $strItem = $objTemplate->m_AssignSection("Items", $strItem); $intTotal = $intTotal + ($arrItem['PriceValue'] * $Item['Quantity']); };

    $intItemIndex++; };

    $objTemplate->m_AssignContent("TOTAL_ITEMS", $intItemIndex); settype($intTotal,'string'); if (!strpos($intTotal,'.')) { $intTotal = $intTotal . "."; }; while ((strrpos($intTotal,'.')) > (strlen($intTotal) - 3)) { $intTotal = $intTotal . "0"; }; if ((($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] < 5) && ($GLOBALS ["Version"] > 3)) || (($GLOBALS["Browser"] == "IE") || (($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] >= 5)))) { $objTemplate->m_AssignContent("BUTTON_TYPE","button"); } else { $objTemplate->m_AssignContent("BUTTON_TYPE","submit"); };

    $objTemplate->m_AssignContent("TOTAL", $intTotal); $objTemplate->m_PrepTemplate(); $objTemplate->m_Output();

    ?> The template file for the Checkout page combines the quantity form from the Cart page with a number of new form elements set to gather purchase information. The checkout.tpl file also introduces a number of new validation functions. These should be added to the i_catalog.jss file. The Zip code and e-mail address validation functions can be copied from the membership interface. The f_valShipData() function simply takes an additional parameter to write the complete alert message. function f_valZip(objElement) { if (!objValidate.m_isFormatZipUS(objElement.value)) { objValidate.m_Alert_x("Your zip code is invalid.", objElement); return false; } else { return true; } } function f_valEmail(objElement) { if (!objValidate.m_isFormatEmail(objElement.value)) { objValidate.m_Alert_x("Please enter a valid e-mail address.", objElement); return false; } else { return true; } } function f_valShipData(objElement,strType) { if (!m_Exists(objElement.value)) { objValidate.m_Alert_x("You must enter a value for your " + strType + ".", objElement); return false; } else { return true; } } The last of the new validation functions is f_ValidateCO(). This is just a conglomerate of the other validation functions used on the Checkout page. function f_ValidateCO(objForm) { if ((!f_valName(objForm.formName)) || (!f_valShipData(objForm.formAddress,'address')) || (!f_valShipData(objForm.formCity,'city')) || (!f_valShipData(objForm.formState,'state')) || (!f_valShipData(objForm.formCountry,'country')) || (!f_valShipData(objForm.formCardNum, 'credit card number')) || (!f_valShipData(objForm.formExpire,'credit card expiration date')) || (!f_valEmail(objForm.formEmail)) || (!f_valZip(objForm.formZip))) { return false; } else { return true; } } The remainder of the Checkout template page is standard fare. Just be sure to include the hidden form element to hold the total number of different items. The action page uses this value to determine the number of items to process. Figure 12.12 shows the completed page. Here is the code for the Checkout template file:

    Figure 12.12: The Checkout page

    checkout.tpl {{NAVIGATION}}

    Here is your order:

    {{ERRORS}}

    {{section Items }} {{/section Items }}
    Product Number Item Price Quantity
    {{PROD_NUM}} {{ITEM_NAME}} {{PRICE}} {{QUANTITY}}
    Total: ${{TOTAL}}

    Buyer Information:


     
    Credit Card Type:

    (5555-5555-5555-5555)

    (mm-yyyy)

     

    {{FOOTER}}

    Action Pages

    The action pages for this project are significantly more complex than the ones for the membership interface. Most of the action pages are private and secure. Even though these pages are never displayed in the browser themselves, they still need coding to ensure that malicious users can’t create their own form pages and submit to unsecured action pages. Each secure page requires the following code block: global $HTTPS; global $REQUEST_URI; if (!isset($HTTPS)) { $strFileRequested = $REQUEST_URI; $strNewLocation = "https://ransom:443/" . $strFileRequested; Header("Location: " . $strNewLocation); exit; };

    Private pages also require the following code: if (!isset($GLOBALS["UserID"])) { session_register("sessRefer"); $GLOBALS["sessRefer"] = $REQUEST_URI; header("Location: secure.php?Page=login"); } else { … Table 12.2 describes the files that should be created for each action page for the catalog interface. Table 12.2: Action Pages

    Page Object Page Secure Private Check secure/a_login.php Yes No Login Item secure/a_itemselect.php Yes Yes Process Item secure/a_itemupdate.php Yes Yes Update Item secure/a_itemdelete.php Yes Yes Delete Image secure/a_imageselect.php Yes Yes Process Image secure/a_imageupdate.php Yes Yes Update Image secure/a_imagedelete.php Yes Yes Delete Category secure/a_catselect.php Yes Yes Process Category secure/a_catupdate.php Yes Yes Update Category secure/a_catdelete.php Yes Yes Delete Update a_cart.php No No Cart Purchase secure/a_checkout.php Yes No

    Check Login Page You can use the Check Login action page to refamiliarize yourself with the o_Validate class. Like most of the other action pages for this interface, the Check Login page uses the o_Validate class to check the submitted form input. If errors are encountered the user is redirected to the appropriate form page. $strPassError = "The password you entered is not valid."; $strUsernameError = "The username you entered is not valid."; $strBadMatch = "We could not locate your user information. Are you sure that you have entered the information correctly? Remember the password is case sensitive.";

    $objValidate = new o_Validate; $objValidate->m_isRangeLength($formUsername, 255, 3, $strUsernameError); $objValidate->m_isRangeLength($formPassword, 255, 3, $strEmailError); $strErrors = $objValidate->m_Alert("
    "); session_start(); if ($strErrors != "") { header("Location: secure.php?Page=login"); } else { … Note For simplicity, this project creates the UserID cookie in plain text. For a production site this would be a security hazard. Once a malicious user discovered the cookie format they would only have to try a few times before they found a user ID that would give them access to the maintenance pages. Production sites should use a cryptography algorithm to hash user ID’s before creating the cookie. PHP developers can accomplish this using mcrypt() encryption functions. For information about PHP’s mcrypt encryption functions visit http://www.php.net/manual/en/ref.mcrypt.php.

    Here is the code for the Check Login action page: a_login.php

    $strPassError = "The password you entered is not valid."; $strUsernameError = "The username you entered is not valid."; $strBadMatch = "We could not locate your user information. Are you sure that you have entered the information correctly? Remember the password is case sensitive.";

    $objValidate = new o_Validate; $objValidate->m_isRangeLength($formUsername, 255, 3, $strUsernameError); $objValidate->m_isRangeLength($formPassword, 255, 3, $strEmailError); $strErrors = $objValidate->m_Alert("
    "); session_start(); if ($strErrors != "") { header("Location: secure.php?Page=login"); } else { $strTable = "AuthUsers"; $strSQL = "SELECT * FROM $strTable WHERE UserName=\"" . $formUsername . "\" AND Password=password(\"" . $formPassword . "\")"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't find login.");

    if (mysql_num_rows($objResult) != 1) { $GLOBALS["sessErrors"] = $strBadMatch; header("Location: secure.php?Page=login"); } else { setcookie("UserID", mysql_result($objResult, "UserKey"), time() + 86400*90, "", "", 1);

    if (isset($sessRefer)) { $strLocation = $sessRefer; session_unregister("sessRefer"); header("Location: $strLocation"); } else { header("Location: secure.php?Page=home"); };

    };

    };

    ?> Caution The Check Login action page should be secure but not private. If you require the UserID cookie to access the only page that can set this cookie, you’re just going to send the user around in circles.

    Process Item Page

    The Process Item page presents a new challenge. The form page that submits the data to the Process Item page contains not one but two submit buttons. The Process Item page demonstrates how to use the name of the clicked submit button to determine how to process the data. In this case the buttons are Delete Item and Edit Item—named formDel and formEdit, respectively. The user is sent to a different form page with the item key passed in the URL for each button. if (isset($formEdit)) { header("Location: itemupdate.php?formItemKey=" . $formItems[0]); } else if (isset($formDel)) { header("Location: itemdelete.php?formItemKey=" . $formItems[0]); }; Note Now you can see why the many of the functions called by the f_accessKey() function in i_interface.jss change the name of the subType hidden form field. Unlike the results of a simple button click, programmatically submitting the form does not automatically send the name of a submit button to the server. By renaming the hidden field to the name of the related button you ensure that the button name is passed to the action page.

    Following is the code for the Process Item action page: a_itemselect.php

    $objValidate = new o_Validate; $objValidate->m_Exists($formItems,$strOptionError);

    if ((isset($formDel)) && ($formItems[1])) { $objValidate->p_arrWarnings[intval($objValidate->p_intWarnCounter)] = $strDeleteError; $objValidate->p_intWarnCounter++; };

    if ((isset($formEdit)) && ($formItems[1])) { $objValidate->p_arrWarnings[intval($objValidate->p_intWarnCounter)] = $strEditError; $objValidate->p_intWarnCounter++; };

    $strErrors = $objValidate->m_Alert("
    ");

    if ($strErrors != "") { header("Location: itemselect.php"); } else { if (isset($formEdit)) { header("Location: itemupdate.php?formItemKey=" . $formItems[0]); } else if (isset($formDel)) { header("Location: itemdelete.php?formItemKey=" . $formItems[0]); };

    };

    };

    ?>

    Update Item Page

    The Update Item page also uses the “button name” technique to determine how to process the data. A wrinkle is introduced, however. The related form page has a variable number of submit buttons. If you recall, the item Add/Edit page had two buttons with dynamic content for the input type attribute: Image and Category. Newer browsers were assigned ‘type=“button”’ while older browsers got ‘type=“submit”’. This means that only browsers that can’t handle client-side subpages might submit the button names formImage or formCat. else if (isset($formImage)) { … Another first for this interface is that the Update Item action page creates the form session variables that were used on the form page to remember the input data. First, the $sessMultiform session variable is set, then a foreach loop cycles through the form post data and assigns a new session variable for each. Form session variables are set whenever the user is expected to return to the form page, such as when an error occurs or when older browsers are using the multi-page interface. if ($strErrors != "") { session_register("sessMultiform"); $GLOBALS["sessMultiform"] = "1";

    foreach($HTTP_POST_VARS as $FormName => $FormValue) { if (($FormName != "") && ($FormValue != "")) { session_register($FormName); $HTTP_SESSION_VARS[$FormName] = $FormValue; }; };

    header("Location: itemupdate.php?formItemKey=$formItemKey"); }

    Here is the code for the Update Item action page: a_itemupdate.php

    $strItemError = "Item names must be between 3 and 30 characters long."; $strDescError = "You must enter a short description that is more than 3 and less than 200 characters."; $strPriceError = "You must enter a price.";

    $arrItemStats = array(); $arrItemStats["Weight"] = $formWeight; $arrItemStats["Size"] = $formSize; session_start(); if (!isset($GLOBALS["UserID"])) { session_register("sessRefer"); $GLOBALS["sessRefer"] = $REQUEST_URI; header("Location: secure.php?Page=login"); } else { $objValidate = new o_Validate; $objValidate->m_isRangeLength($formItemName, 30, 3, $strItemError); $objValidate->m_isRangeLength($formShortDesc, 200, 3, $strDescError); $objValidate->m_Exists($formPrice, $strPriceError); $strErrors = $objValidate->m_Alert("
    ");

    if ($strErrors != "") { session_register("sessMultiform"); $GLOBALS["sessMultiform"] = "1";

    foreach($HTTP_POST_VARS as $FormName => $FormValue) { if (($FormName != "") && ($FormValue != "")) { session_register($FormName); $HTTP_SESSION_VARS[$FormName] = $FormValue; }; };

    header("Location: itemupdate.php?formItemKey=$formItemKey"); } else { if (isset($formSubmit)) { $strTable = "item"; $strStatTable = "itemstatistics"; $strPriceTable = "price";

    if ($formItemKey != -1) { $strSQL = "UPDATE $strTable SET ItemName=\"$formItemName\", ProductNum= \"$formProdNum\", DescShort=\"$formShortDesc\", DescLong=\"$formLongDesc\" WHERE ItemKey=" . $formItemKey; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't update data."); $strDeleteSQL = "DELETE FROM $strStatTable WHERE ItemKey = $formItemKey"; $objDeleteResult = mysql_query($strDeleteSQL,$CONNECTION) or die("Couldn't delete item statistic data.");

    foreach ($arrItemStats as $Stat=>$StatValue) { if ($StatValue != "") { $strStatSQL = "INSERT INTO $strStatTable (ItemKey, StatName, StatValue) VALUES ($formItemKey, \"$Stat\", \"$StatValue\")"; $objStatResult = mysql_query($strStatSQL,$CONNECTION) or die ("Couldn't insert item statistic."); };

    };

    $strPriceSQL = "UPDATE $strPriceTable SET PriceValue = \"$formPrice\" WHERE ItemKey=" . $formItemKey; $objPriceResult = mysql_query($strPriceSQL,$CONNECTION) or die("Couldn't update price."); } else { $strSQL = "INSERT INTO $strTable (ItemName, ProductNum, DescShort, DescLong) Values(\"$formItemName\", \"$formProdNum\", \"$formShortDesc\", \"$formLongDesc\")"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't insert data."); $intItemKey = mysql_insert_id(); foreach ($arrItemStats as $Stat=>$StatValue) { if ($StatValue != "") { $strStatSQL = "INSERT INTO $strStatTable (ItemKey, StatName, StatValue) VALUES($intItemKey, \"$Stat\", \"$StatValue\")"; $objStatResult = mysql_query($strStatSQL,$CONNECTION) or die("Couldn't insert item statistic."); };

    };

    $strPriceSQL = "INSERT INTO $strPriceTable (ItemKey, Currency, PriceValue) VALUES($intItemKey,\"USD\",$formPrice)"; $objPriceResult = mysql_query($strPriceSQL,$CONNECTION) or die ("Couldn't insert price.");

    $strImageTable = "imageassignment"; $strCatTable = "categoryassignment"; $strUpdateImageSQL = "UPDATE $strImageTable SET ItemKey = $intItemKey WHERE ItemKey = -1"; $objImageResult = mysql_query($strUpdateImageSQL,$CONNECTION) or die("Couldn't update image assignments"); $strUpdateCatSQL = "UPDATE $strCatTable SET ItemKey = $intItemKey WHERE ItemKey = -1"; $objCatResult = mysql_query($strUpdateCatSQL,$CONNECTION) or die("Couldn't update category assignments");

    };

    header("Location: itemselect.php"); } else if (isset($formImage)) { session_register("sessMultiform"); $GLOBALS["sessMultiform"] = "1";

    foreach($HTTP_POST_VARS as $FormName => $FormValue) { if (($FormName != "") && ($FormValue != "")) { session_register($FormName); $HTTP_SESSION_VARS[$FormName] = $FormValue; };

    };

    header("Location: imageselect.php?formItemKey=" . $formItemKey); } else if (isset($formCat)) { session_register("sessMultiform"); $GLOBALS["sessMultiform"] = "1";

    foreach($HTTP_POST_VARS as $FormName => $FormValue) { if (($FormName != "") && ($FormValue != "")) { session_register($FormName); $GLOBALS[$FormName] = $FormValue; };

    };

    header("Location: catselect.php?formItemKey=" . $formItemKey); } else if (isset($formReset)) { if (isset($sessMultiform)) { session_unregister("sessMultiform"); };

    $strImageTable = "imageassignment"; $strCatTable = "categoryassignment"; $strImageSQL = "DELETE FROM $strImageTable WHERE ItemKey = -1"; $objImageResult = mysql_query($strImageSQL,$CONNECTION) or die("Couldn't delete image assignments."); $strCatSQL = "DELETE FROM $categoryassignment WHERE ItemKey = -1"; $objCatResult = mysql_query($strCatSQL,$CONNECTION) or die("Couldn't delete category assignments.");

    if ($formItemKey != -1) { header("Location: itemupdate.php?formItemKey=" . $formItemKey); } else { header("Location: itemupdate.php"); };

    };

    };

    };

    ?>

    Remove Item Page The Remove Item page deletes all traces of an item from the database. Because many of the tables are linked, this involves multiple DELETE queries. As a shortcut, an array is created holding the names of the tables affected and a foreach loop creates the SQL statement and executes the query. $arrTables = array(); $arrTables[0] = "item"; $arrTables[1] = "categoryassignment"; $arrTables[2] = "imageassignment"; $arrTables[3] = "itemstatistics"; $arrTables[4] = "price"; foreach ($arrTables AS $TableName) { $strSQL = "DELETE FROM $TableName WHERE ItemKey=$formItemKey"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't delete from $TableName."); };

    After the deletion of the records from the database, the user is directed to the Delete Confirmation page. Here is the code for the Remove Item action page: a_itemdelete.php

    foreach ($arrTables AS $TableName) { $strSQL = "DELETE FROM $TableName WHERE ItemKey=$formItemKey"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't delete from $TableName."); };

    header("Location: deleteconfirm.php?ID=$formItemKey&Type=item&Name= $formItemName"); } else if ($formBack != "") { header("Location: itemselect.php"); };

    };

    ?>

    Process Image Page

    The Process Image page works ve ry similarly to the Process Item page if the user clicks Edit or Delete, but the a_imageselect.php file also has to account for the possibility that the user has clicked Assign. As a programmatic shortcut, the code simply deletes any records in the imageassignment table for the item and creates new ones. $strTable = "imageassignment"; $strDeleteSQL = "DELETE FROM $strTable WHERE ItemKey = $formItemKey"; $objDeleteResult = mysql_query($strDeleteSQL,$CONNECTION) or die("Couldn't delete data."); foreach ($formImages as $ImageKey) { $strInsertSQL = "INSERT INTO $strTable (ImageKey, ItemKey) VALUES($ImageKey, $formItemKey)"; $objInsertResult = mysql_query($strInsertSQL,$CONNECTION) or die("Couldn't insert data."); }; After the image assignments are made, an if statement determines whether the user is viewing a subpage or part of a multi-page form. Subpage users are sent a script that closes the subpage. Multi-page users are directed to the appropriate form page. if ((($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] < 5) && ($GLOBALS ["Version"] > 3)) || (($GLOBALS["Browser"] == "IE") || (($GLOBALS["Browser"] == "Netscape") && ($GLOBALS ["Version"] >= 5)))) { echo ""; } else { if (isset($formItemKey)) { header("Location: itemupdate.php?formItemKey=" . $formItemKey); } else { header("Location: catupdate.php?formCats=" . $formCatKey); };

    }; The f_subPageClose_x() function should be added to the i_interface.jss file. This function is effectively the opposite of the f_subPage_x() function. It determines the client browser and version and uses the appropriate commands to close the window. function f_subPageClose_x(varReturnValue) { var strNavigator = navigator.appName;

    if (strNavigator != "Microsoft Internet Explorer") { var strVersion = navigator.appVersion.substr(0,3); } else { var strVersion = navigator.appVersion.substr(navigator.appVersion.indexOf ("MSIE") + 5,3); }

    if ((strNavigator == "Microsoft Internet Explorer") && (strVersion >= 4)) { window.returnValue = varReturnValue; window.close(); } else if ((strNavigator == "Netscape") && (strVersion > 3)) { window.close(); } else { return true; } }

    Here is the code for the Process Image action page: a_imageselect.php

    $strOptionError = "You must select an option to proceed."; $strDeleteError = "You can only delete one image at a time."; $strEditError = "You can only edit one image at a time.";

    $objValidate = new o_Validate; $objValidate->m_Exists($formImages,$strOptionError);

    if ((isset($formDel)) && ($formImages[1])) { $objValidate->p_arrWarnings[intval($objValidate->p_intWarnCounter)] = $strDeleteError; $objValidate->p_intWarnCounter++; };

    if ((isset($formEdit)) && ($formImages[1])) { $objValidate->p_arrWarnings[intval($objValidate->p_intWarnCounter)] = $strEditError; $objValidate->p_intWarnCounter++; };

    $strErrors = $objValidate->m_Alert("
    ");

    if ($strErrors != "") { header("Location: imageselect.php?formItemKey=" . $formItemKey); } else { if ($formAssign != "") { $strTable = "imageassignment"; $strDeleteSQL = "DELETE FROM $strTable WHERE ItemKey = $formItemKey"; $objDeleteResult = mysql_query($strDeleteSQL,$CONNECTION) or die("Couldn't delete data.");

    foreach ($formImages as $ImageKey) { $strInsertSQL = "INSERT INTO $strTable (ImageKey, ItemKey) VALUES ($ImageKey, $formItemKey)"; $objInsertResult = mysql_query($strInsertSQL,$CONNECTION) or die("Couldn't insert data."); };

    if ((($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] < 5) && ($GLOBALS["Version"] > 3)) || (($GLOBALS["Browser"] == "IE") || (($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] >= 5)))) { echo ""; } else { if (isset($formItemKey)) { header("Location: itemupdate.php?formItemKey=" . $formItemKey); } else { header("Location: catupdate.php?formCats=" . $formCatKey); };

    }; } else if (isset($formDel)) { header("Location: imagedelete.php?formImageKey=" . $formImages[0] . "&formItemKey=$formItemKey"); } else if (isset($formEdit)) { header("Location: imageupdate.php?formImageKey=" . $formImages[0] . "&formItemKey=$formItemKey"); };

    };

    };

    ?>

    Update Image Page

    The Update Image action page must be able to handle the file system manipulation required by the image update form. Changes in the file name or file directory fields require the code to rename an existing file. Also, if a new file is uploaded the code must copy the file to the final location. The first step in both of these processes is to define the path of the files involved. $strPath = "C:/Inetpub/wwwroot/dwf/images/" . $formFileDir; if ($formFileDir != "") { $strPath = $strPath . "/"; };

    $strOldPath = "C:/Inetpub/wwwroot/dwf/images/" . $formOldFileDir; The name of the file is set to either the name submitted in the file name field of the form or, if no name was supplied, the name of the file uploaded. The “_name” designation references the name of the accompanying file. if (($formFileName == "") && ($formFile != "none")) { $strNewFileName = $formFile_name; } else { $strNewFileName = $formFileName; }; Now that the file names and directories have been assigned to variables, the actual files can be moved. An if statement determines whether a file has been uploaded, and if so creates the folder and file on the server. If a file has not been uploaded the current file is renamed to the new value. if ($formFile != "none") { if (!file_exists($strPath)) { mkdir($strPath, 0777); };

    copy($formFile, $strPath . $strNewFileName); } else if (file_exists($strPath . $formOldFileName)) { rename($strPath . $formOldFileName, $strPath . $strNewFileName); };

    Here is the code for the Update Image action page: a_imageupdate.php

    $objValidate = new o_Validate; $objValidate->m_isRangeLength($formImageName, 30, 3, $strNameError); $objValidate->m_isRangeNumber($formHeight, 2000, 0, $strHeightError); $objValidate->m_isRangeNumber($formWidth, 2000, 0, $strHeightError); $objValidate->m_Exists($formAlt, $strAltError);

    if (($formImageKey == -1) && ($formFile == "none")) { $objValidate->p_arrWarnings[$objValidate->p_intWarnCounter] = $strFileError; $objValidate->p_intWarnCounter++; };

    $strErrors = $objValidate->m_Alert("
    ");

    if ($strErrors != "") { session_register("sessMultiform"); $GLOBALS["sessMultiform"] = "1";

    foreach($HTTP_POST_VARS as $FormName => $FormValue) { if (($FormName != "") && ($FormValue != "")) { session_register($FormName); $HTTP_SESSION_VARS[$FormName] = $FormValue; };

    };

    if ($formImageKey != -1) { header("Location: imageupdate.php?formItemKey=" . $formItemKey . "&formImageKey=" . $formImageKey); } else { header("Location: imageupdate.php?formAdd=1&formItemKey=" . $formItemKey); }; } else { if (isset($formSubmit)) { $strTable = "image"; $strPath = "C:/Inetpub/wwwroot/dwf/images/" . $formFileDir;

    if ($formFileDir != "") { $strPath = $strPath . "/"; };

    $strOldPath = "C:/Inetpub/wwwroot/dwf/images/" . $formOldFileDir;

    if (($formFileName == "") && ($formFile != "none")) { $strNewFileName = $formFile_name; } else { $strNewFileName = $formFileName; };

    if ($formImageKey == "-1") { $strSQL = "INSERT INTO $strTable (ImgName, FileName, FileDir, Height, Width, Border, Alt) VALUES(\"$formImageName\", \"$strNewFileName\", \"$formFileDir\", " . settype($formHeight,"integer") . ", " . settype($formWidth, "integer") . ", " . settype($formBorder,"integer") . ", \"$formAlt\")"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't insert data."); } else { $strSQL = "UPDATE $strTable SET ImgName=\"$formImageName\", FileName=\"$strNewFileName\", FileDir=\"$formFileDir\", Height=" . settype($formHeight, "integer") . ", Width=" . settype($formWidth,"integer") . ", Border=" . settype($formBorder,"integer") . ", Alt=\"$formAlt\" WHERE ImageKey=$formImageKey"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't update data."); };

    if ($formFile != "none") { if (!file_exists($strPath)) { mkdir($strPath, 0777); };

    copy($formFile, $strPath . $strNewFileName); } else if (file_exists($strPath . $formOldFileName)) { rename($strPath . $formOldFileName, $strPath . $strNewFileName); };

    header("Location: imageselect.php?formItemKey=" . $formItemKey); } else if (isset($formReset)) { header("Location: imageupdate.php?formItemKey=" . $formItemKey . "&formImageKey=" . $formImageKey); };

    };

    };

    ?>

    Remove Image Page The Remove Image action page follows the pattern set by the Remove Item page. The main difference is the database tables that are affected. A copy of the DELETE command from a_itemdelete.php can be used for the image and ImageAssignment tables, but the category tables require a little more subtlety. Rather than deleting the entire record, you just need to set the value of the ImageKey column to null or "". $strCatTable = "category"; $strCatSQL = "UPDATE $strCatTable SET ImageKey='' WHERE ImageKey=$formImageKey"; $objCatResult = mysql_query($strCatSQL,$CONNECTION) or die("Couldn't delete category image assignments.");

    Here is the code for the Remove Image action page: a_imagedelete.php

    header("Location: deleteconfirm.php?formItemKey=$formItemKey&ID= $formImageKey&Type=image&Name=$formImageName"); } else if ($formBack != "") { header("Location: imageselect.php?formItemKey=" . $formItemKey); };

    };

    ?>

    Process Category Page As you learned earlier, the category pages can be both subpages and stand-alone pages. The $Lone variable is used to determine which type should be sent to the browser. To preserve this value across the category pages, the action pages must pass the variable in the querystring of any URL that the user is directed to as a result of the form data. First, define a variable that will be attached to each header argument. This variable will be empty if the $Lone variable is not passed, and "&Lone=1" otherwise. if ($Lone != "") { $strURLAttach = "&Lone=1"; } else { $strURLAttach = ""; }; The remainder of the page can proceed normally, but whenever the code directs the user to another category page, the $strURLAttach variable should be added to the header string. … else if (isset($formDel)) { header("Location: catdelete.php?formCatKey=" . $formCats[0] . "&formItemKey=" . $formItemKey . $strURLAttach); }

    Following is the code for the Process Category action page: a_catselect.php

    if (!isset($formItemKey)) { $formItemKey = "-1"; };

    $strOptionError = "You must select an option to proceed."; $strDeleteError = "You can only delete one category at a time."; $strEditError = "You can only edit one category at a time.";

    $objValidate = new o_Validate; $objValidate->m_Exists($formCats,$strOptionError);

    if ((isset($formDel)) && ($formCats[1])) { $objValidate->p_arrWarnings[intval($objValidate->p_intWarnCounter)] = $strDeleteError; $objValidate->p_intWarnCounter++; };

    if ((isset($formEdit)) && ($formCats[1])) { $objValidate->p_arrWarnings[intval($objValidate->p_intWarnCounter)] = $strEditError; $objValidate->p_intWarnCounter++; };

    $strErrors = $objValidate->m_Alert("
    ");

    if ($strErrors != "") { header("Location: catselect.php?formItemKey=" . $formItemKey . $strURLAttach); } else { if (isset($formAssign)) { $strTable = "categoryassignment"; $strDeleteSQL = "DELETE FROM $strTable WHERE ItemKey = $formItemKey"; $objDeleteResult = mysql_query($strDeleteSQL,$CONNECTION) or die("Couldn't delete data.");

    foreach ($formCats as $CatKey) { $strInsertSQL = "INSERT INTO $strTable (CategoryKey, ItemKey) VALUES($CatKey, $formItemKey)"; $objInsertResult = mysql_query($strInsertSQL,$CONNECTION) or die("Couldn't insert data."); };

    if ((($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] < 5) && ($GLOBALS["Version"] > 3)) || (($GLOBALS["Browser"] == "IE") || (($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] >= 5)))) { echo ""; } else { if (isset($formItemKey)) { header("Location: itemupdate.php?formItemKey=" . $formItemKey); } else { header("Location: catupdate.php?formCats=" . $formCatKey . $strURLAttach); };

    };

    } else if (isset($formDel)) { header("Location: catdelete.php?formCatKey=" . $formCats[0] . "&formItemKey=" . $formItemKey . $strURLAttach); } else if (isset($formEdit)) { header("Location: catupdate.php?formCatKey=" . $formCats[0] . "&formItemKey=" . $formItemKey . $strURLAttach); };

    };

    };

    ?>

    Update Category Page Besides maintaining the value of $Lone, the Update Category has another variable to keep track of—$Add. This variable determines whether the form page should query the database for image information or leave the form fields blank. Whenever the user must be redirected to the Add/Edit category page the $Add variable must be passed with the URL. else if (isset($formResetAdd)) { if (isset($formItemKey)) { header("Location: catupdate.php?Add=1&formItemKey=" . $formItemKey . $strURLAttach); } else { header("Location: catupdate.php?Add=1" . $strURLAttach); };

    };

    Here is the code for the Update Category action page: a_catupdate.php

    $strNameError = "You use a name between 3 and 30 characters."; $strDescError = "You must enter a description that is more than 3 and less than 200 characters.";

    $objValidate = new o_Validate; $objValidate->m_isRangeLength($formCatName, 30, 3, $strNameError); $objValidate->m_isRangeLength($formDescription, 200, 3, $strDescError); $strErrors = $objValidate->m_Alert("
    ");

    if ($strErrors != "") { session_register("sessMultiform"); $GLOBALS["sessMultiform"] = "1";

    foreach($HTTP_POST_VARS as $FormName => $FormValue) { if (($FormName != "") && ($FormValue != "")) { session_register($FormName); $HTTP_SESSION_VARS[$FormName] = $FormValue; };

    };

    if ($formCatKey != -1) { header("Location: catupdate.php?formItemKey=" . $formItemKey . "formAdd=1" . $strURLAttach); } else { header("Location: catupdate.php?formItemKey=" . $formItemKey . $strURLAttach); }; } else { if (isset($formSubmit)) { $strTable = "category";

    if ($formCatKey == "-1") { $strSQL = "INSERT INTO $strTable (CatName, Description, ImageKey) VALUES(\"$formCatName\", \"$formDescription\", $formImage)"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't insert data."); } else { $strSQL = "UPDATE $strTable SET CatName=\"$formCatName\", Description=\ "$formDescription\", ImageKey=$formImage WHERE CategoryKey=$formCatKey"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't update data."); };

    if (isset($formItemKey)) { header("Location: catselect.php?formItemKey=" . $formItemKey . $strURLAttach); } else { header("Location: catselect.php?Lone=1"); }; } else if (isset($formReset)) { header("Location: catupdate.php?formItemKey=" . $formItemKey . "&formCatKey=" . $formCatKey . $strURLAttach); } else if (isset($formResetAdd)) { if (isset($formItemKey)) { header("Location: catupdate.php?formAdd=1&formItemKey=" . $formItemKey . $strURLAttach); } else { header("Location: catupdate.php?formAdd=1" . $strURLAttach); };

    };

    };

    };

    ?>

    Remove Category Page

    The last of the deletion action pages, the Remove Category page doesn’t present any surprises. It follows the form of the other Delete Action pages by removing the related records from the database.

    Here’s the code for the Remove Category action page: a_catdelete.php

    if ($formSubmit != "") { $strTable = "category"; $strSQL = "DELETE FROM $strTable WHERE CategoryKey=$formCatKey"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't delete category."); $strAssignTable = "categoryassignment"; $strAssignSQL = "DELETE FROM $strAssignTable WHERE CategoryKey=$formCatKey"; $objAssignResult = mysql_query($strAssignSQL,$CONNECTION) or die("Couldn't delete category assignments.");

    if ($formItemKey > 0) { header("Location: deleteconfirm.php?formItemKey=$formItemKey&ID= $formCatKey&Type=cat&Name=$formCatName" . $strURLAttach); } else { header("Location: deleteconfirm.php?ID=$formCatKey&Type=cat&Name=$formCatName" . $strURLAttach); };

    } else if ($formBack != "") { if ($formItemKey > 0) { header("Location: catselect.php?formItemKey=" . $formItemKey . $strURLAttach); } else { header("Location: catselect.php?Lone=1"); };

    };

    };

    ?>

    Update Cart Page The Update Cart page is used to update the quantities of the items currently in the user’s shopping cart. This process uses a similar multi-dimensional array system as the one in cart.php. The difference is that the Update Cart action page uses the submitted quantity values rather than a session cookie. By naming the text fields with brackets ([]), the code can handle the resulting form data as an array. After the $arrItems variable is populated it can be used to overwrite the current $sessCart session variable. session_start(); if (isset($formItemKeys)) { for ($l = 1; $l <= $formTotalItems; $l++) { if (($formQuantities[$l] != "") && ($formQuantities[$l] != 0)) { $arrItems[$formItemKeys[$l]]['Quantity'] = $formQuantities[$l]; };

    };

    }; session_register('sessCart'); $GLOBALS['sessCart'] = $arrItems;

    After the session variable is modified, the user is directed either back to the shopping cart page or to the Checkout page. Here is the code for the Update Cart action page: a_updatecart.php

    $objValidate = new o_Validate; for ($l = 1; $l <= $formTotalItems; $l++) { if (($formQuantities[$l] != "") && (!is_numeric($formQuantities[$l]))) { $objValidate->p_arrWarnings[$objValidate->p_intWarnCounter] = $strQuantityError; };

    };

    $strErrors = $objValidate->m_Alert("
    "); if ($strErrors != "") { header("Location: cart.php"); } else { session_start();

    if (isset($formItems)) { for ($l = 1; $l <= $formTotalItems; $l++) { if (($formQuantities[$l] != "") && ($formQuantities[$l] != 0)) { $arrItems[$formItems[$l]]['Quantity'] = $formQuantities[$l]; };

    };

    };

    session_register('sessCart'); $GLOBALS['sessCart'] = $arrItems;

    if (isset($formUpdate)) { header("Location: cart.php"); } else if (isset($formSubmit)) { header("Location: https://ransom/checkout.php"); };

    };

    ?>

    Purchase Page

    The catalog interface culminates with the Purchase page. This is the action page that gathers the purchase information submitted on the Checkout page and enters the data into the appropriate database tables. This page recycles most of the techniques used in the other action pages, including the memory mechanism, a multi-dimensional item array, and checking for the name of the submit button.

    For security reasons, the memory mechanism is never used to store the credit card number. Instead, a blank value is passed. session_register("sessMultiform"); $GLOBALS["sessMultiform"] = "1"; foreach($HTTP_POST_VARS as $FormName => $FormValue) { if (($FormName != "") && ($FormValue != "")) { if ($FormName != "formCardNum") { session_register($FormName); $GLOBALS[$FormName] = $FormValue; };

    }; }; session_register("formCardNum"); $GLOBALS["formCardNum"] = ""; header("Location: checkout.php"); As the purchase information is added to the database tables, the mysql_insert_id() function is used to gather the values of the auto-incrementing fields. This is important because many of the tables are linked using these values. $strSalesSQL = "INSERT INTO $strSalesTable (PriceTotal, CardType, CardNumber, ExpDate) VALUES ($floatPriceTotal, \"$formCardType\", ENCODE(\"$formCardNum\",\"hooligan99!\"), \"$formExpire\")"; $objSalesResult = mysql_query($strSalesSQL,$CONNECTION) or die(mysql_error()); $intSaleKey = mysql_insert_id(); Note Remember to use the ENCODE() function to encrypt sensitive data, such as credit card numbers, before inserting the data into a database table.

    The memory mechanism is also used before the page directs the user to the Order Confirmation page. The session variable created is used to generate the receipt for the purchase. Here is the code for the Purchase Action page: a_checkout.php m_isFormatEmail($formEmail,$strEmailError); $objValidate->m_isFormatZip($formZip,$strZipError); $objValidate->m_isRangeLength($formName, 30, 3, $strNameError); $objValidate->m_Exists($formAddress,$strGenError. "address" . $strErrorEnd); $objValidate->m_Exists($formCity,$strGenError. "city" . $strErrorEnd); $objValidate->m_Exists($formState,$strGenError. "state" . $strErrorEnd); $objValidate->m_Exists($formCountry,$strGenError. "country" . $strErrorEnd);

    if (!ereg('([0-9]{4}\-){3}[0-9]{4}',$formCardNum)) { $objValidate->p_arrWarnings[$objValidate->p_intWarnCounter] = $strCardNumError; $objValidate->p_intWarnCounter++; };

    if (!ereg('([0-9]{1,2})-([0-9]{4})',$formExpire)) { $objValidate->p_arrWarnings[$objValidate->p_intWarnCounter] = $strExpireError; };

    $strErrors = $objValidate->m_Alert("
    ");

    if ($strErrors != "") { session_register("sessMultiform"); $GLOBALS["sessMultiform"] = "1";

    foreach($HTTP_POST_VARS as $FormName => $FormValue) { if (($FormName != "") && ($FormValue != "")) { if ($FormName != "formCardNum") { session_register($FormName); $GLOBALS[$FormName] = $FormValue; };

    }; };

    session_register("formCardNum"); $GLOBALS["formCardNum"] = ""; header("Location: checkout.php"); } else { $strShipTable = "shipping"; $strShipSQL = "INSERT INTO $strShipTable (Name, Email, Address, City, State, Zip, Country, Phone, Fax) VALUES (\"$formName\", \"$formEmail\", \"$formAddress\", \"$formCity\", \"$formState\", \"$formZip\", \"$formCountry\", \"$formPhone\", \"$formFax\")"; $objShipResult = mysql_query($strShipSQL,$CONNECTION) or die("Couldn't insert shipping data."); $intShipKey = mysql_insert_id(); $strSalesTable = "sales"; $strPriceTable = "price"; $strItemShipTable = "itemship"; $floatPriceTotal = 0;

    for ($l = 1; $l <= $formTotalItems; $l++) { $strPriceSQL = "SELECT PriceValue from $strPriceTable WHERE ItemKey = " . $formItemKeys[$l] . " AND Currency = \"USD\""; $objPriceResult = mysql_query($strPriceSQL,$CONNECTION) or die("Couldn't select price.");

    while ($arrPrice = mysql_fetch_array($objPriceResult, MYSQL_ASSOC)) { $floatPriceTotal = $floatPriceTotal + ($arrPrice['PriceValue'] * $formQuantities[$l]); };

    };

    $strSalesSQL = "INSERT INTO $strSalesTable (PriceTotal, CardType, CardNumber, ExpDate) VALUES ($floatPriceTotal, \"$formCardType\", encode(\"$formCardNum\",\"hooligan99!\"), \"$formExpire\")"; $objSalesResult = mysql_query($strSalesSQL,$CONNECTION) or die(mysql_error()); $intSaleKey = mysql_insert_id();

    for ($l = 1; $l <= $formTotalItems; $l++) { $strItemShipSQL = "INSERT INTO $strItemShipTable (ShipKey, ItemKey, Quantity, SaleKey) VALUES ($intShipKey, $formItemKeys[$l], $formQuantities[$l], $intSaleKey)"; $objItemShipResult = mysql_query($strItemShipSQL,$CONNECTION) or die("Couldn't insert item ship data."); };

    session_register("sessMultiform"); $GLOBALS["sessMultiform"] = "1";

    foreach($HTTP_POST_VARS as $FormName => $FormValue) { if (($FormName != "") && ($FormValue != "")) { if ($FormName != "formCardNum") { session_register($FormName); $GLOBALS[$FormName] = $FormValue; };

    };

    };

    session_unregister("formCardNum"); session_register("formCardNum"); $GLOBALS["formCardNum"] = "xxxx-xxxx-xxxx-" . substr($GLOBALS["formCardNum"], -4); header("Location: http://ransom/dwf/confirm.php"); };

    };

    ?>

    Display Pages

    The catalog interface contains many of the same display elements as the membership interface. Navigation and footer template files can be created for both the secure and unsecure pages. The content and format of these pages is left up to you. In addition, the Primary Object file, main.php, is nearly identical to its counterpart from the membership interface.

    Here is the code for the Primary Object file: main.php

    $strFileName = "./templates/" . strtolower($Page) . ".tpl"; $objTemplate = new o_Template; $objTemplate->m_LoadFile($strFileName); $objTemplate->m_AddElements($Page); $objTemplate->m_DefineSection("AddToCart"); if ($sessErrors) { $objTemplate->m_AssignContent("ERRORS", $sessErrors); } else { $objTemplate->m_AssignContent("ERRORS", ""); }; if (!$Browser) { $objBrowser = get_browser(); setcookie("Browser", $objBrowser->browser, time() + 86400*90, "", "", 1); setcookie("Version", $objBrowser->version, time() + 86400*90, "", "", 1); setcookie("Javascript", $objBrowser->javascript, time() + 86400*90, "", "", 1); setcookie("Platform", $$objBrowser->platform, time() + 86400*90, "", "", 1); }; session_unregister("sessErrors"); $objTemplate->m_PrepTemplate(); $objTemplate->m_Output(); ?> Table 12.3 describes the files that should be created for each display page in the catalog interface. Note The main.php file defines a dynamic section called AddToCart. This section is intended to go in the navigation.tpl file. The section contains a link that should only appear on item pages. Clicking on the link will add the current item to the user’s shopping cart. By defining but not setting the AddToCart section, the main.php removes the section from the output stream. Table 12.3: Display Pages Page Object Page Template Page

    Help main.php help.tpl IE Help main.php helpIE.tpl Netscape Help main.php helpNS.tpl Catalog catalog.php catalog.tpl Category category.php category.tpl Item item.php item.tpl Delete Confirmation secure/deleteconfirm.php deleteconfirm.tpl Order Confirmation confirm.php confirm.tpl

    Delete Confirmation Page The Delete Confirmation page is the only display page that is secure. As part of the maintenance portion of the catalog interface, this page will be visible only to authorized users. The Delete Confirmation page is designed to handle the confirmation relating to items, images, and categories. It accomplishes this by passing the name and ID of the item in question and a third variable, $Type. The code reads the value of $Type and, using a switch statement, performs the processing. The dynamic section URL is attached to an anchor tag in the template file. switch ($Type) { case "image"; $objTemplate->m_AssignContent("URL", "imageselect.php?formItemKey=$formItemKey"); break;

    case "cat"; if (isset($formItemKey)) { $objTemplate->m_AssignContent("URL", "catselect.php?formItemKey=$formItemKey"); } else { $objTemplate->m_AssignContent("URL", "catselect.php"); }; break;

    case "item"; $objTemplate->m_AssignContent("URL", "itemselect.php"); break;

    }; Figure 12.13 shows the completed page. Here is the code for the Delete Confirmation display page:

    Figure 12.13: The Delete Confirmation page

    deleteconfirm.php

    global $HTTPS; global $REQUEST_URI; global $Type; global $ID; global $Name; global $formItemKey;

    if (!isset($HTTPS)) { $strFileRequested = $REQUEST_URI; $strNewLocation = "https://ransom:443/" . $strFileRequested; Header("Location: " . $strNewLocation); exit; }; session_start(); if (!isset($GLOBALS["UserID"])) { session_register("sessRefer"); $GLOBALS["sessRefer"] = $REQUEST_URI; header("Location: secure.php?Page=login"); } else { $strFileName = "templates/deleteconfirm.tpl"; $objTemplate = new o_Template; $objTemplate->m_LoadFile($strFileName);

    if (!isset($formItemKey)) { $objTemplate->m_AddElements("Item Deleted"); } else if ((($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] < 5) && ($GLOBALS["Version"] > 3)) || (($GLOBALS["Browser"] == "IE") || (($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] >= 5)))) { $objTemplate->m_AssignContent("TITLE", "Item Deleted"); $objTemplate->m_AssignContent("NAVIGATION", ""); $objTemplate->m_AssignContent("FOOTER", ""); } else { $objTemplate->m_AddElements("Item Deleted"); };

    $objTemplate->m_AssignContent("NAME", $Name); $objTemplate->m_AssignContent("ID", $ID);

    switch ($Type) { case "image"; $objTemplate->m_AssignContent("URL", "imageselect.php?formItemKey=$formItemKey"); break;

    case "cat"; if (isset($formItemKey)) { $objTemplate->m_AssignContent("URL", "catselect.php?formItemKey=$formItemKey"); } else { $objTemplate->m_AssignContent("URL", "catselect.php"); }; break;

    case "item"; $objTemplate->m_AssignContent("URL", "itemselect.php"); break;

    };

    $objTemplate->m_PrepTemplate(); $objTemplate->m_Output(); };

    ?>

    Catalog Page The Catalog page shows the real advantage of using the o_Template class. This page has two dynamic sections, one nested in the other. The parent section is also repeated a variable number of times. The template system makes it a simple matter to designate the content sections using marker tags. {{section Categories }}

    {{section Image }}{{/section Image }} {{CAT_NAME}}

    {{/section Categories }} The Catalog Object page can then replace these marker tags with content from the database. Figure 12.14 shows the completed Catalog page.

    Figure 12.14: The Catalog page

    Following is the code for the Catalog Display page:

    catalog.php

    $strFileName = "templates/catalog.tpl"; $objTemplate = new o_Template; $objTemplate->m_LoadFile($strFileName); $objTemplate->m_AddElements("Catalog"); $objTemplate->m_DefineSection("AddToCart"); $strCatSection = $objTemplate->m_DefineSection("Categories"); $strImgSection = $objTemplate->m_DefineSection("Image");

    $strTable = "category"; $strImageTable = "image"; $strSQL = "SELECT * FROM $strTable LEFT JOIN $strImageTable ON $strTable.ImageKey= $strImageTable.ImageKey ORDER BY CatName"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't select categories.");

    while ($arrCats = mysql_fetch_array($objResult, MYSQL_ASSOC)) { $strCategory = $objTemplate->m_AssignContent("CAT_KEY", $arrCats['CategoryKey'], $strCatSection); $strCategory = $objTemplate->m_AssignContent("CAT_NAME", $arrCats['CatName'], $strCategory); $strImage = $objTemplate->m_AssignContent("IMG_NAME", $arrCats['ImgName'], $strImgSection); $strImage = $objTemplate->m_AssignContent("ALT", $arrCats['Alt'], $strImage); $strImage = $objTemplate->m_AssignContent("WIDTH", $arrCats['Width'], $strImage); $strImage = $objTemplate->m_AssignContent("HEIGHT", $arrCats['Height'], $strImage); $strImage = $objTemplate->m_AssignContent("BORDER", $arrCats['Border'], $strImage);

    if ($arrCats['FileDir'] != "") { $strImageSRC = "images/" . $arrCats['FileDir'] . "/" . $arrCats['FileName']; } else { $strImageSRC = "images/" . $arrCats['FileName']; };

    $strImage = $objTemplate->m_AssignContent("IMG_SRC", $strImageSRC, $strImage); $strCategory = $objTemplate->m_AssignSection("Image", $strImage, $strCategory); $objTemplate->m_AssignSection("Categories", $strCategory); }

    $objTemplate->m_PrepTemplate(); $objTemplate->m_Output();

    ?>

    Category Page The Category page shows a listing of all the items in the category. The category key is passed in the URL by the catalog page. The object file for the Category page uses several join statements to reduce the number of calls to the database. Joins should be familiar to any developer used to SQL syntax. Using joins allows you to pull data from multiple tables at once. For more information about joins, and SQL syntax in general, visit http://www.sqlcourse.com/. $strItemTable = "item"; $strItemAssignTable = "categoryassignment"; $strItemSQL = "SELECT ItemName, DescShort, $strItemTable.ItemKey FROM $strItemTable INNER JOIN $strItemAssignTable ON $strItemTable.ItemKey=$strItemAssignTable.ItemKey WHERE CategoryKey = $ID ORDER BY ItemName"; $objItemResult = mysql_query($strItemSQL,$CONNECTION) or die("Couldn't select item information."); Figure 12.15 shows the completed Category page. Here is the code for the Category Display page:

    Figure 12.15: The Category page

    category.php

    global $ID;

    $strFileName = "templates/category.tpl"; $objTemplate = new o_Template; $objTemplate->m_LoadFile($strFileName); $strImgSection = $objTemplate->m_DefineSection("Image"); $strItemSection = $objTemplate->m_DefineSection("Items");

    $strTable = "category"; $strImageTable = "image"; $strSQL = "SELECT * FROM $strTable LEFT JOIN $strImageTable ON $strTable.ImageKey=$strImageTable.ImageKey WHERE CategoryKey = $ID"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't select categories.");

    while ($arrCat = mysql_fetch_array($objResult, MYSQL_ASSOC)) { $objTemplate->m_AddElements("Category: " . $arrCat['CatName']); $objTemplate->m_DefineSection("AddToCart"); $objTemplate->m_AssignContent("CAT_NAME", $arrCat['CatName']); $strImage = $objTemplate->m_AssignContent("IMG_NAME", $arrCat['ImgName'], $strImgSection); $strImage = $objTemplate->m_AssignContent("ALT", $arrCat['Alt'], $strImage); $strImage = $objTemplate->m_AssignContent("WIDTH", $arrCat['Width'], $strImage); $strImage = $objTemplate->m_AssignContent("HEIGHT", $arrCat['Height'], $strImage); $strImage = $objTemplate->m_AssignContent("BORDER", $arrCat['Border'], $strImage);

    if ($arrCat['FileDir'] != "") { $strImageSRC = "images/" . $arrCat['FileDir'] . "/" . $arrCat['FileName']; } else { $strImageSRC = "images/" . $arrCat['FileName']; };

    $strImage = $objTemplate->m_AssignContent("IMG_SRC", $strImageSRC, $strImage); $objTemplate->m_AssignSection("Image", $strImage);

    $strItemTable = "item"; $strItemAssignTable = "categoryassignment"; $strItemSQL = "SELECT ItemName, DescShort, $strItemTable.ItemKey FROM $strItemTable INNER JOIN $strItemAssignTable ON $strItemTable.ItemKey= $strItemAssignTable.ItemKey WHERE CategoryKey = $ID ORDER BY ItemName"; $objItemResult = mysql_query($strItemSQL,$CONNECTION) or die("Couldn't select item information.");

    while ($arrItem = mysql_fetch_array($objItemResult, MYSQL_ASSOC)) { $strItem = $objTemplate->m_AssignContent("ITEM_NAME", $arrItem['ItemName'], $strItemSection); $strItem = $objTemplate->m_AssignContent("ITEM_KEY", $arrItem['ItemKey'], $strItem); $strItem = $objTemplate->m_AssignContent("DESCRIPTION", $arrItem['DescShort'], $strItem); $objTemplate->m_AssignSection("Items", $strItem); };

    };

    $objTemplate->m_PrepTemplate(); $objTemplate->m_Output();

    ?>

    Item Page The code needed to pull the information from the database to display on the Catalog page is much the same as the code in itemupdate.php. Rather than placing the dynamic content in form elements, however, the code displays the content directly on the page. The Item page is the only catalog page that will display the AddToCart dynamic section. It does this by assigning the value of the item key to the dynamic section. Figure 12.16 shows the finished Item page.

    Figure 12.16: The Item page $strCartSection = $objTemplate->m_DefineSection("AddToCart"); $strAdd = $objTemplate->m_AssignContent("ITEM_KEY", $ID, $strCartSection); $objTemplate->m_AssignSection("AddToCart",$strAdd);

    Here is the code for the Item Display page:

    item.php

    global $ID;

    $strFileName = "templates/item.tpl"; $objTemplate = new o_Template; $objTemplate->m_LoadFile($strFileName); $strImgSection = $objTemplate->m_DefineSection("Image"); $strCatSection = $objTemplate->m_DefineSection("Categories");

    $strTable = "item"; $strImageTable = "image"; $strImageAssignTable = "imageassignment"; $strPriceTable = "price"; $strStatisticsTable = "itemstatistics"; $strCatTable = "category"; $strCatAssignTable = "categoryassignment"; $strSQL = "SELECT ItemName, DescLong, ProductNum, $strPriceTable.PriceValue FROM $strTable INNER JOIN $strPriceTable ON $strTable.ItemKey=$strPriceTable.ItemKey WHERE $strTable.ItemKey = $ID AND Currency = \"USD\""; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't select item information"); while ($arrItem = mysql_fetch_array($objResult, MYSQL_ASSOC)) { $objTemplate->m_AddElements("Item: " . $arrItem['ItemName']); $strCartSection = $objTemplate->m_DefineSection("AddToCart"); $strAdd = $objTemplate->m_AssignContent("ITEM_KEY", $ID, $strCartSection); $objTemplate->m_AssignSection("AddToCart",$strAdd); $objTemplate->m_AssignContent("ITEM_NAME", $arrItem['ItemName']); $objTemplate->m_AssignContent("PRICE", $arrItem['PriceValue']); $objTemplate->m_AssignContent("PROD_NUM", $arrItem['ProductNum']); $objTemplate->m_AssignContent ("DESCRIPTION", $arrItem['DescLong']);

    $strImageSQL = "SELECT * FROM $strImageTable INNER JOIN $strImageAssignTable ON $strImageTable.ImageKey=$strImageAssignTable.ImageKey WHERE ItemKey = $ID ORDER BY ItemKey"; $objImageResult = mysql_query($strImageSQL,$CONNECTION) or die("Couldn't select item information");

    while ($arrImage = mysql_fetch_array($objImageResult, MYSQL_ASSOC)) { $strImage = $objTemplate->m_AssignContent("IMG_NAME", $arrImage['ImgName'], $strImgSection); $strImage = $objTemplate->m_AssignContent("ALT", $arrImage['Alt'], $strImage); $strImage = $objTemplate->m_AssignContent("WIDTH", $arrImage['Width'], $strImage); $strImage = $objTemplate->m_AssignContent("HEIGHT", $arrImage['Height'], $strImage); $strImage = $objTemplate->m_AssignContent("BORDER", $arrImage['Border'], $strImage);

    if ($arrImage['FileDir'] != "") { $strImageSRC = "images/" . $arrImage['FileDir'] . "/" . $arrImage['FileName']; } else { $strImageSRC = "images/" . $arrImage['FileName']; };

    $strImage = $objTemplate->m_AssignContent("IMG_SRC", $strImageSRC, $strImage); $objTemplate->m_AssignSection("Image", $strImage); }; $strStatSQL = "SELECT StatName, StatValue FROM $strStatisticsTable WHERE ItemKey = $ID"; $objStatResult = mysql_query($strStatSQL,$CONNECTION) or die("Couldn't select item information"); $arrItemStats = array();

    while ($arrStats = mysql_fetch_array($objStatResult, MYSQL_ASSOC)) { if ($arrStats['StatName'] == "Size") { $arrItemStats["Size"] = $arrStats['StatValue']; } else if ($arrStats['StatName'] == "Weight") { $arrItemStats["Weight"] = $arrStats['StatValue']; };

    };

    $objTemplate->m_AssignContent("SIZE", $arrItemStats["Size"]); $objTemplate->m_AssignContent("WEIGHT", $arrItemStats["Weight"]);

    $strCatSQL = "SELECT $strCatTable.CategoryKey, CatName FROM $strCatTable INNER JOIN $strCatAssignTable ON $strCatTable.CategoryKey=$strCatAssignTable.CategoryKey WHERE $strCatAssignTable.ItemKey = $ID"; $objCatResult = mysql_query($strCatSQL,$CONNECTION) or die("Couldn't select category information");

    while ($arrCats = mysql_fetch_array($objCatResult, MYSQL_ASSOC)) { $strCat = $objTemplate->m_AssignContent("CAT_NAME", $arrCats['CatName'], $strCatSection); $strCat = $objTemplate->m_AssignContent("CAT_KEY", $arrCats['CategoryKey'], $strCat); $objTemplate->m_AssignSection("Categories", $strCat); };

    };

    $objTemplate->m_PrepTemplate(); $objTemplate->m_Output();

    ?> Order Confirmation Page

    The final page of the catalog interface is probably the most important to your users. The Order Confirmation page is the user’s receipt; it confirms the purchase and shipping information just entered. To save on database calls, this page uses the session variables that were set on the Purchase Action page. if (isset($sessMultiform)) { $objTemplate->m_AssignContent("NAME", $HTTP_SESSION_VARS["formName"]); $objTemplate->m_AssignContent("ADDRESS", $HTTP_SESSION_VARS["formAddress"]); $objTemplate->m_AssignContent("CITY", $HTTP_SESSION_VARS["formCity"]); $objTemplate->m_AssignContent("STATE", $HTTP_SESSION_VARS["formState"]); $objTemplate->m_AssignContent("ZIP", $HTTP_SESSION_VARS["formZip"]); $objTemplate->m_AssignContent("COUNTRY", $HTTP_SESSION_VARS["formCountry"]); $objTemplate->m_AssignContent("EMAIL", $HTTP_SESSION_VARS["formEmail"]); $objTemplate->m_AssignContent("PHONE", $HTTP_SESSION_VARS["formPhone"]); $objTemplate->m_AssignContent("FAX", $HTTP_SESSION_VARS["formFax"]); $objTemplate->m_AssignContent("CARD_NUM", $HTTP_SESSION_VARS["formCardNum"]); $objTemplate->m_AssignContent("CARD_TYPE", $HTTP_SESSION_VARS["formCardType"]);

    session_unregister("formName"); session_unregister("formAddress"); session_unregister("formCity"); session_unregister("formState"); session_unregister("formZip"); session_unregister("formCountry"); session_unregister("formEmail"); session_unregister("formPhone"); session_unregister("formFax"); session_unregister("formCardNum"); session_unregister("formExpire"); session_unregister("formCardType"); session_unregister("sessMultiform"); } The end result, shown in Figure 12.17 is a page showing the most pertinent information. Here is the code for the Order Confirmation display page:

    Figure 12.17: The Order Confirmation page

    confirm.php

    $arrItems = array(); session_start();

    $strFileName = "templates/confirm.tpl"; $objTemplate = new o_Template; $objTemplate->m_LoadFile($strFileName); $objTemplate->m_AddElements("Receipt for Purchase"); $objTemplate->m_DefineSection("AddToCart"); $strItemSection = $objTemplate->m_DefineSection("Items");

    if (isset($sessCart)) { foreach ($sessCart AS $ItemKey=>$Item) { foreach ($Item AS $Quantity) { $arrItems[$ItemKey]['Quantity'] = $Quantity; };

    };

    };

    if (isset($sessMultiform)) { $objTemplate->m_AssignContent("NAME", $HTTP_SESSION_VARS["formName"]); $objTemplate->m_AssignContent("ADDRESS", $HTTP_SESSION_VARS["formAddress"]); $objTemplate->m_AssignContent("CITY", $HTTP_SESSION_VARS["formCity"]); $objTemplate->m_AssignContent("STATE", $HTTP_SESSION_VARS["formState"]); $objTemplate->m_AssignContent("ZIP", $HTTP_SESSION_VARS["formZip"]); $objTemplate->m_AssignContent("COUNTRY", $HTTP_SESSION_VARS["formCountry"]); $objTemplate->m_AssignContent("EMAIL", $HTTP_SESSION_VARS["formEmail"]); $objTemplate->m_AssignContent ("PHONE", $HTTP_SESSION_VARS["formPhone"]); $objTemplate->m_AssignContent("FAX", $HTTP_SESSION_VARS["formFax"]); $objTemplate->m_AssignContent("CARD_NUM", $HTTP_SESSION_VARS["formCardNum"]); $objTemplate->m_AssignContent("CARD_TYPE", $HTTP_SESSION_VARS["formCardType"]);

    session_unregister("formName"); session_unregister("formAddress"); session_unregister("formCity"); session_unregister("formState"); session_unregister("formZip"); session_unregister("formCountry"); session_unregister("formEmail"); session_unregister("formPhone"); session_unregister("formFax"); session_unregister("formCardNum"); session_unregister("formExpire"); session_unregister("formCardType"); session_unregister("sessMultiform"); } else { header("Location: catalog.php"); }

    $intItemIndex = 0; $intTotal = 0; $strTable = "item"; $strPriceTable = "price"; foreach ($arrItems AS $ItemKey=>$Item) { $strSQL = "SELECT ItemName, ProductNum, $strPriceTable.PriceValue FROM $strTable INNER JOIN $strPriceTable ON $strTable.ItemKey=$strPriceTable.ItemKey WHERE $strTable.ItemKey = $ItemKey AND Currency = \"USD\""; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't select categories.");

    while ($arrItem = mysql_fetch_array($objResult, MYSQL_ASSOC)) { $strItem = $objTemplate->m_AssignContent("ITEM_NAME", $arrItem['ItemName'], $strItemSection); $strItem = $objTemplate->m_AssignContent("PROD_NUM", $arrItem['ProductNum'], $strItem); $strItem = $objTemplate->m_AssignContent("PRICE", $arrItem['PriceValue'], $strItem); $strItem = $objTemplate->m_AssignContent("QUANTITY", $Item['Quantity'], $strItem); $strItem = $objTemplate->m_AssignSection("Items", $strItem); $intTotal = $intTotal + ($arrItem['PriceValue'] * $Item['Quantity']); };

    $intItemIndex++; };

    $objTemplate->m_AssignContent("TOTAL_ITEMS", $intItemIndex); settype($intTotal,'string'); if (!strpos($intTotal,'.')) { $intTotal = $intTotal . "."; }; while ((strrpos($intTotal,'.')) > (strlen($intTotal) - 3)) { $intTotal = $intTotal . "0"; };

    $objTemplate->m_AssignContent("TOTAL", $intTotal); $objTemplate->m_PrepTemplate(); $objTemplate->m_Output();

    ?>

    Chapter 13: Completing the Catalog Interface

    The catalog interface is ready for the final steps: testing and personalization. In this chapter I will give you some ideas on how to use the catalog interface as a launch pad for creating your own projects and Web form applications. Then I’ll review some testing recommendations and processes. When you’re finished, you will be ready to use the interface to create that collectable plush toy online store you’ve always wanted. Personalizing the Interface

    Just as not every script you write works the first time you try it out, not every interface application works seamlessly out of the box. The catalog interface is no exception. As a learning tool and a test bed for some new technologies and techniques it makes a great starting point, but it isn’t ready to go live yet. The following section covers some ideas on how you can expand the interface to fit your needs.

    Housecleaning

    As it appears in this book, the catalog interface is a great example of a complex interdependent series of Web forms, but it’s far from perfect code. To save development time a lot of loose ends were left hanging. These are not enough to prevent the interface from functioning, but they are enough to limit the interface to development only. Before you can take your catalog interface live you will need to do a little code housecleaning.

    The following are a few things to look out for: § All the variables are left open and in memory. This is common, but it can affect performance on some servers. Consider using the unset() function to clear these variables from the system. § Unlike PHP, other server-side scripting languages such as ASP or JSP use explicit variable declarations. If you are creating the catalog interface in one of these languages you should take advantage of this by declaring all the variables for each page at the top of the file. § Database query results are left in memory. Even more than simple variables, unreleased database results can affect performance and could potentially hang the Web server. PHP uses the mysql_free_result() function to clear the memory associated with a query to a MySQL server. § Loops are used as a shortcut to create session variables for the memory mechanism. You will need to refine these loops so that only the form data that is required is stored as a session variable. This will increase performance and lower the chances of an unwanted session variable disrupting other pages.

    Integration and Reporting

    Before you can hand your work off to the maintenance team you will need to integrate the interface into the existing system. For example, you will have to tie the purchase information into the billing department, the shipping information will have to get to distribution, and the sales data will have to get to the marketing department. In addition, the maintenance team will most likely require numerous types of reports detailing the number, name, and details of the items currently in the database.

    Have you considered how the interface will react to multiple users at once? You might consider implementing some type of transaction-based system. As an alternative you could lock out an item when a user selects it for editing. That way, the users won’t be stepping over each other.

    Currently the catalog interface has only one setting for authorized users: full access. You might consider how you will integrate limited access levels. It’s likely you won’t want just any user to have permission to delete the entire catalog. Access privileges can be tricky, but you may be able to integrate an existing database or network access system into your interface.

    Combining Membership Forms and the Catalog Interface

    One of the common elements of the most popular online retailers is the integration of a membership system with an ordering system. Amazon.com, for example, allows users to define lists of favorite books or authors. Users can even create wish lists of desired items and send these to friends and family. In fact, the integration of online communities with a catalog interface is an e-tailer’s best friend.

    Quality membership systems keep visitors coming back and encourage them to invite others. Functional catalog interfaces allow the users to quickly and easily buy items that have value to them. It’s a win-win scenario. While you can’t expect to create another Amazon.com overnight, you could implement a small community of users for the public area of your catalog interface. At the very least, you could store the shipping key assigned to a purchase in a long-term cookie so that shipping data doesn’t have to be re- entered for each purchase.

    Testing

    The catalog interface needs testing even more than the membership interface does. The relative complexity of this project goes beyond the sheer increase in the number of pages. Many of the pages are actually designed to perform or display differently depending on the circumstances. Replicating all of the many possible conditions can be difficult and time-consuming. However, the alternative—a buggy interface—isn’t any better. Chapter 9, “Completing the Membership Interface,” taught you how to anticipate and replicate some of the common types of general user errors. For the catalog interface, you must be concerned with more than simply unexpected input. The actual functionality of the interface is in question. Will the user interface elements work as designed? Will the subpage/multi-page combination work for all browsers? Will the memory mechanism work consistently? To get as wide a range of results as possible, I recommend testing the interface on a minimum of six browsers. Table 13.1 lists the browser and platform combinations I recommend. Of course, if you’re lucky enough to be developing for a limited audience, such as users of an intranet, you will have far fewer browsers to check. Table 13.1: Client Testing Configurations Browser Operating Notes System Internet Explorer 6.x Windows The most 9x/NT/ME/ popular XP client Netscape 4.x Windows, Still high in Linux popularity Netscape 6.x Any Increased standardiza tion and functionality Internet Explorer 5.x MacOS X The next wave of Mac browsers Opera 5.x Windows, Some Linux, Mac JavaScript and DHTML Lynx Linux, Dos A text-only browser iCab 2.x Macintosh Some JavaScript Table 13.1: Client Testing Configurations Browser Operating Notes System and CSS WebTV Viewer 2.x Windows, Wreaks Mac havoc on forms

    Develop a Test Plan The catalog interface has almost a score of pages—most with multiple form fields, user interface elements, or display properties. Testing all the possible variations on a single browser without a test plan is daunting and on many browsers well nigh impossible. A test plan sets out a sequence of tests or trials that is designed to hit the maximum amount of different interface scenarios in a single run. By creating and following a comprehensive test plan you can minimize the overall time and effort you spend testing the interface.

    Creating a test plan is often as personalized as creating the interface itself. Here are a few guidelines you can use to get your test plan on paper. § Remember the users. You might have consulted with the anticipated user of the catalog interface before beginning the project. Now is the perfect opportunity to renew that relationship. Ask a sampling of users for examples of the type of data they might enter into the database. § Use fresh eyes. If you’re like me, when you’re developing a Web page you spend more time looking at the screen than the Surgeon General would approve of. Get some help with the testing. Having the users themselves test the interface is optimal, but even other developers or coworkers are likely to see things you might be too brain-fried to notice. § Be comprehensive. Design the test plan so that each page of the interface will be used at least twice. Each form field should be tested, but preferably not all at once. § Stay general. Rather than giving the testers a sheet of data that needs to be entered into the interface, let them decide on the most appropriate values. For example, you might have a task that asks each user to create a new image, but leave out the file directory option. The specifics of the data entered are left up to the tester, allowing for a larger range of possible outcomes. § Watch first, question later. Give the test plan to the testers and let them have at the interface. If you can be in the room to watch, great, but try not to interrupt or direct. When the testing is complete you can get information about bugs, user impressions, and suggestions. § Don’t include user interface elements in the plan. User interface elements are supposed to be transparent and self-explanatory. You might encourage the user to try interacting with the interface in an unconventional manner, but even this is pushing the limits. Only when the tester has completed the test plan should you ask about user interface elements.

    Using the Test Results

    Now that you’ve created and run through a comprehensive test plan you might be thinking that you should go right back to the code and fix the problems. Take my advice: slow down. Large, complex interfaces such as these catalog forms can be fickle. Oftentimes fixing a “small bug” can produce catastrophic interface failure. The interdependencies of many of the pages and the sharing of data across forms can be great from a user’s perspective. But when bug hunting in a jungle, a sharp machete is more important than a big one. Here are a few bits of advice, in case you do decide to take on the challenge. § Know your foe. Find out why the problem is occurring before trying to resolve the issue. For example, at one point when creating the catalog interface I found out the Item Update page had stopped functioning properly. After hacking the code on the page to pieces I discovered the problem was with the Shopping Cart page, which was using the same name for a session variable. Hours of work, and I wasn’t even working on the right part of the interface. § Stay on target. It’s easy to get distracted when fixing bugs, and small detours have a way of becoming long side trips. Sometimes the original problem gets lost along the road. I keep a piece of paper by my keyboard when I’m working on fixing a bug, and I pause only long enough to jot down any other problems I encounter—along with the page they occurred on. Only when the first problem is solved will I move on to the second. § Duct tape makes a lousy girder. Code patches and workarounds are fine for a quick fix or as a last resort, but they make the code more difficult to read and lower its overall stability. Whenever possible, integrate any bug fixes into the interface rather than just slapping them on top. § Don’t make the patient any worse . Sometimes attempts at fixing a problem can wreck the interface to the point where it’s just quicker to start over. Periodically make a backup of all the files for the interface and always keep an original copy when making any major fixes. Consider using a version control system if you don’t have one already. § Take baby steps. Don’t overfix the problem or implement new functionality without considering the implications. Major changes to the code will require another run-through of the test plan. Consider how a fix might impact the functionality of the other pages and the interface in general before you commit yourself to it.

    Project 2 Summary

    In this project you created a catalog interface. Using this interface, the maintenance team will be able to create and modify items for an online storefront. Project files are separated into public (non-secure) pages and private pages using SSL. The implementation of this interface involves extensive use of user interface design principles, including solutions for older browsers, keyboard navigation, and forms with memory.

    Part IV: Project 3: Creating Online Information Systems

    Chapter List Chapter 14: Functional Design Chapter 15: Designing a Functional Interface Chapter 16: Creating the Online Information System Itself Chapter 17: Completing the Information System Interface

    Project 3 Overview

    One of the most effective uses of Web forms is to create online information systems. The Web is full of information systems—news distribution sites, online tutorials, Web-based product brochures. All of these information systems on the Web permit the exchange of data between physically separated but like-minded individuals. The developers of online information systems want to share with others their expertise, information about their products, or their collected wisdom. Visitors appreciate the well-organized and up-to-date information of a well-built online information system, and if the information is of value they will return time and again.

    This project walks you through the creation of a dynamic online information system. The design of the information is intended to be generic enough that it can be applied to a host of possible uses. The finished project will involve both the private maintenance pages and a public user contribution system. While completing the project you’ll discover the concepts of functional design, create a collection of handy tools for Web forms, implement a context-sensitive help system, and create a transaction-based maintenance interface.

    Chapter 14: Functional Design

    Overview

    Functional design goes beyond user interface design. While UI design focuses on giving the users the elements required for interacting with the system, functional design is about giving the users added-value tools that speed up workflow and improve production quality. To reuse the analogy with Lego blocks: if UI design is creating the cockpit controls, then functional design is making the toy actually fly and even perform stunts.

    Developers of functional design interfaces create a set of widgets or preprogrammed mini-applications that perform helpful functions. For example, you could create a pop-up calendar that lets the user select a date graphically or a WYSIWYG HTML editor that allows the Web site visitor to enter rich-text content into a form. In this chapter you will create both of these and more.

    WYSIWYG

    Acronym for What You See Is What You Get. WYSIWYGs are graphical editors that allow the user to see the effects of style elements as the content is created. Because WYSIWYG HTML editors often produce overly complex code or have limited functionality, they have relatively little appeal to experienced designers. It’s novices or those who prefer speed over precision who most often use them.

    The Basics of Functional Design for Web Forms

    As you have seen, Web development is different from straight application development. Because you can never be sure of the capabilities of the client system, you must develop a functional design that works with many clients. For those clients for which the tool simply won’t work, the functional design must degrade gracefully by providing a less functional but still usable alternative system.

    Functional design relies on three essentials: problem-free operation, convenient access, and added value. Together these elements produce tools that the user will use not because the system demands it but because it is just simpler or more productive to do so. To produce a functional design that your users will appreciate, you must balance the three elements. Sacrifice problem-free operation for a little added functionality and you’ve corrupted the tool and lessened its appeal to the user. Unlike UI elements, functional design tools are included in an interface in case the user needs them. Designing Problem-Free Tools

    The first and most important of the three essentials to proper functional design, problem- free design is more than just creating a bug-free tool. Ask anyone who’s ever worked tech support and you learn that users have more problems than just dealing with bugs. Although you could try to anticipate as many of the user’s difficulties as possible, a good rule of thumb is to keep the tools simple. The more complex the tool, the more likely a user is to become confused.

    Problem-free tool design involves applying many of the same concepts you learned about user interface design. Here’s a breakdown of some of the essentials of problem- free tool design. § The tool must be error free. § Working with the tool must be as simple as possible. § The tool should work the way the user expects, not necessarily the way that would be easiest in absolute terms. § When possible, include on-screen instructions, help pages, or context- sensitive help about the tool and its operation.

    Designing Convenient Tools

    Convenience is about more than just putting a tool where the user can get to it. For example, you could create a pop-up calendar that appears whenever the user types **CALENDAR** in a text field. It could work exactly as intended, but the end result is a tool that users will never use; they are more likely just to look at the desktop calendar and enter the date manually because that takes fewer keystrokes. Convenience is about locality, accessibility, and relevancy.

    Here are a few rules to keep in mind: § Locality. Make the tool accessible right on the page where the function is needed. Position the activation link where the user would expect to encounter it. This might mean you would place it right next to the affected form element, but not always. Ideally the element should be available on the page at the exact spot where the user first thinks about using it. § Accessibility. Make the tool easy to access. This might involve adding a keyboard shortcut, a clickable link, or programming in automatic activation. When deciding how to make the tool accessible, keep in mind what you learned in Project 2 about user interface design. § Relevancy. Make the tool relevant to what the user is doing. You can accomplish this by ensuring that the function of the tool is apparent from the link. In addition, the tool must clearly relate to the part of the form that it is intended to affect. If the user isn’t sure what the tool does or when it should be used, the tool loses its value.

    Designing Added-Value Tools

    Creating tools with added value is an essential of functional design that is often overlooked. Problem-free and convenient tools that don’t do more than the users could do on their own with the same amount of effort are a waste of your programming time. The tools you create must provide the users some functionality or increased productivity that people will appreciate—tasks they want to perform and couldn’t accomplish on their own. Sometimes it’s tempting to create fancy widgets that really don’t do anything of benefit for the user. Just look at Windows 98.

    Here are a few questions you should ask about the value of your tools: § Can the user accomplish the same task manually? § If so, how much faster is using the tool than performing the task manually? § Does the tool provide information, advice, or sample values that could assist the user? § Will the user appreciate the extra functionality of the tool enough to want to use it?:

    The Web Form Toolbox

    To ensure a functional design for the information systems interface you will need to create a set of code widgets. Collectively these will be your Web form toolbox. The concept is to create a set of modular tools that are flexible enough to use in as many situations as possible. Ideally, once you have the toolbox, applying any single tool to a form should involve no more than a few lines of code. By applying these modular design concepts to the toolbox you can place functional design elements throughout the interface quickly and easily.

    The Web form toolbox will be a collection of JavaScript, server-side scripts, and HTML code. This will mean that the toolbox files will include numerous file types such as .jss, .php, and .tpl. To designate the toolbox files for this project, I will designate them with a preceding “t”, as in t_toolbox.php. In addition, the toolbox files will be located in a new folder called toolbox, located in the global directory.

    Select Box Reordering

    The first tool you will create will allow the user to rearrange items in a select box without having to submit the form to the server. For information systems, this is a perfect way to allow an administrator to set the order of the information items for the interface. For example, the maintainer of a news Web site could use this tool to declare the order in which each day’s articles would appear on the main page. This tool will be created using a single function, f_moveOption_x(), that will be located in a file called t_toolbox.jss. The f_moveOption_x() function will use two parameters—an object representing the select box that will be reordered and a Boolean value that represents whether the selected item should be moved up (true) or down (false). function f_moveOption_x(objElement, boolUp) { …

    The first part of the function sets a variable to the index number of the currently selected option. If no option is selected the user is presented with an error message. var intSelectedItem = objElement.selectedIndex; if (intSelectedItem==-1) { alert("You must first select the item to reorder."); } else { … Next a series of if statements use the value of boolUp to set the value of a new variable, intNextSelectedItem. This variable represents the index number of the option that will be swapped with the selected option. If the upper or lower limit of the options index is reached, then the index number of the opposite extreme option is used. In other words, if the user has selected the last option and activates the f_moveOption_x() function with boolUp set to false, the selected option will be swapped with the first option in the select box. if (boolUp) { var intNextSelectedItem = intSelectedItem - 1; } else { var intNextSelectedItem = intSelectedItem + 1; } if (intNextSelectedItem < 0) { intNextSelectedItem = objElement.length - 1; } if (intNextSelectedItem >= objElement.length) { intNextSelectedItem = 0; } To complete the function, the option values and texts are switched. Two temporary variables—strOldText and strOldVal—are used to store the replacement information. When the option information has been changed the original option (at its new position) is selected. var strOldVal = objElement[intSelectedItem].value; var strOldText = objElement[intSelectedItem].text; objElement[intSelectedItem].value = objElement[intNextSelectedItem].value; objElement[intSelectedItem].text = objElement[intNextSelectedItem].text; objElement[intNextSelectedItem].value = strOldVal; objElement[intNextSelectedItem].text = strOldText; objElement.selectedIndex = intNextSelectedItem; Create a new file called t_toolbox.jss in the toolbox folder, then add the following complete f_moveOption_x() function code to this new file: function f_moveOption_x(objElement, boolUp) { var intSelectedItem = objElement.selectedIndex;

    if (intSelectedItem==-1) { alert("You must first select the item to reorder."); } else { if (boolUp) { var intNextSelectedItem = intSelectedItem - 1; } else { var intNextSelectedItem = intSelectedItem + 1; }

    if (intNextSelectedItem < 0) { intNextSelectedItem = objElement.length - 1; } if (intNextSelectedItem >= objElement.length) { intNextSelectedItem = 0; }

    var strOldVal = objElement[intSelectedItem].value; var strOldText = objElement[intSelectedItem].text; objElement[intSelectedItem].value = objElement[intNextSelectedItem].value; objElement[intSelectedItem].text = objElement[intNextSelectedItem].text; objElement[intNextSelectedItem].value = strOldVal; objElement[intNextSelectedItem].text = strOldText; objElement.selectedIndex = intNextSelectedItem; } } Now that you’ve reordered the box, you need to prepare the data for submission. Because only the selected option is sent to the server when a form is submitted, you will need to transfer the information to a hidden field. This function is called f_outputSelectBox() and it has three parameters—an object reference to the original select element, an object reference to the hidden field, and an optional delimitating character. function f_outputSelectBox(objFrom, objTo, strDelim) { … The function creates a new array variable and populates it with the values from the select box. If the delimiting character is not supplied, then “|" is used. function f_outputSelectBox(objFrom, objTo, strDelim) { var arrOutPut = new Array();

    for (l = 0; l < objFrom.length; l++) { arrOutPut[l] = objFrom.options[l].value; }

    if (!strDelim) { strDelim = '|'; }

    objTo.value = arrOutPut.join(strDelim); }

    Select Box Searching

    As your interface is used more and more, the select boxes get longer and longer. It can often be a difficult and slow process for the user to select a single item from a list of hundreds using just a drop-down or scroll bar. This tool provides an alternative. It combines a text box with a select element. As the user types in the text box, the first item in the select element that includes this text is automatically selected. The select box search function is called f_searchSelect_x(). This function takes four parameters —an object reference to the text box, an object reference to the select element, the index of the character from the item name to begin the search from, and the index of the character where the search should stop. function f_searchSelect_x(objElement, objDestination, intFirst, intLast) { …

    The last two parameters make it possible to create a single select element that can be searched in different ways. Consider a search box that lets the user select a single product from a list. Users may want to search the list by the name of the product, but they might also want to search by SKU or another product designation. Each option in the select element would look like this: It is important to keep all of the searchable parts of the option names the same length, except for the last search area. This way, you can search for product numbers in the options just illustrated by adding the onKeyUp="f_searchSelect_x(this, this.form.formSelectBox, 0, 10);" attribute to a text field. Th en you could create another text field with onKeyUp="f_searchSelect_x(this,this.form. formSelectBox, 13);". Leaving the last parameter blank makes the function search to the end of the option name. Leaving both of the last two parameters out makes the function search the entire name. The f_searchSelect_x() function uses two variables, strText and strPartText. The first holds the complete text of each option while the second holds just the searchable part of each option name. var strText = ''; var strPartText = ''; Before the value of the text field is compared to the option names both names are converted to lowercase. A for loop is used to cycle through the option name and assign the variables in turn. The loop runs backward so that the first item in the select box will be last to be compared. var strValue = objElement.value.toLowerCase(); var intNumOptions = objDestination.options.length - 1; for (l = intNumOptions; l >= 0; l—) { strText = objDestination.options[l].text.toLowerCase(); strPartText = strText.substring(intFirst,intLast); …

    Finally, if the text value is found in the search area the option is selected. if (strPartText.indexOf(strValue) >= 0) { objDestination.selectedIndex = l; } Here is the complete code for the f_searchSelect_x() function. It should be added to the t_toolbox.jss file. Figure 14.1 shows an example of how this tool can be applied.

    Figure 14.1: Select box searching function f_searchSelect_x(objElement, objDestination, intFirst, intLast) { var arrText = ''; var arrPartText = ''; var strValue = objElement.value.toLowerCase(); var intNumOptions = objDestination.options.length - 1;

    for (l = intNumOptions; l >= 0; l—) { strText = objDestination.options[l].text.toLowerCase(); strPartText = strText.substring(intFirst,intLast);

    if (strPartText.indexOf(strValue) >= 0) { objDestination.selectedIndex = l; } } }

    Linked Select Boxes

    Often, for clarity, data in an information system is grouped into categories or topics. In the second project you saw one way in which you could assign an item to a group. But sometimes the user wants to assign a bunch of categories all at once. In the catalog interface, the user would have to go through one item at a time to assign each category. Using Linked Select Boxes, you can allow the user to copy or move an option from one select box to another. In the case of the catalog interface, you could present the user with an assignment page displaying one select box that shows the entire list of items, and another showing just the items that are assigned to the specific category. With the click of a button, an option can be moved from one box to the other. The code that allows the adding and removing of options is specific to the browser. Add the following code to the top of the t_toolbox.jss file. It will allow the code to determine the browser that the client is using. var boolIE = false; var boolNN = false; var boolN6 = false; if (document.all) { boolIE = true; } else if (document.layers) { boolNN = true; } else if (document.getElementsByTagName('BODY')) { boolN6 = true; } This tool uses the f_addOption_x() function. This function takes three parameters: an object reference to the original select element, an object reference to the destination select element, and a Boolean value that determines whether to remove (true) or keep (false) the original selected option. If the last parameter is left out, the original option is kept. function f_addOption_x(objFrom, objTo, boolRemove) { …

    The first part of the function creates an array and a counter to hold index values. Then the options of the select element are cycled through to determine whether each has been selected. If an option has been selected, then further processing can occur. var arrSelected = new Array(); var intCounter = 0; for (l = 0; l < objFrom.length; l++) { if (objFrom.options[l].selected) { … The next part of the function appends the index to an item to the arrSlected array and increments the counter. Then two variables are declared, the value and the text of the selected option. arrSelected[intCounter] = l; intCounter++ var strOldVal = objFrom.options[intSelected].value; var strOldText = objFrom.options[intSelected].text;

    Each of the next sections of the function are browser-specific. A code block is created for each. The browser variables that were created at the beginning of this section are used to determine the proper method of adding the options to the destination element. if (boolIE) { var objOption = new Option; objOption.value = strOldVal; objOption.text = strOldText; objTo.add(objOption); } else if (boolNN) { objTo.options[objTo.length] = new Option(strOldVal, strOldText); } else if (boolN6) { objOption = document.createElement("OPTION"); objOption.value = strOldVal; objOption.text = strOldText; objTo.add(objOption, null); } Finally, the arrSelected array is reversed and the original items are deleted if the boolRemove variable has been set. arrSelected.reverse(); for (k = 0; k < arrSelected.length; k++) { if ((boolIE) && (boolRemove)) { objFrom.remove(arrSelected[k]); } else if ((boolNN) && (boolRemove)) { objFrom.options[arrSelected[k]] = null; } else if ((boolN6) && (boolRemove)) { objFrom.remove(arrSelected[k]); } } Add the f_addOption_x() function to the t_toolbox.jss file. function f_addOption_x(objFrom, objTo, boolRemove) { var arrSelected = new Array(); var intCounter = 0;

    for (l = 0; l < objFrom.length; l++) { if (objFrom.options[l].selected) { arrSelected[intCounter] = l; intCounter++ var strOldVal = objFrom.options[l].value; var strOldText = objFrom.options[l].text;

    if (boolIE) { var objOption = new Option; objOption.value = strOldVal; objOption.text = strOldText; objTo.add(objOption); } else if (boolNN) { objTo.options[objTo.length] = new Option(strOldVal, strOldText); } else if (boolN6) { objOption = document.createElement("OPTION"); objOption.value = strOldVal; objOption.text = strOldText; objTo.add(objOption, null); } } }

    arrSelected.reverse();

    for (k = 0; k < arrSelected.length; k++) { if ((boolIE) && (boolRemove)) { objFrom.remove(arrSelected[k]); } else if ((boolNN) && (boolRemove)) { objFrom.options[arrSelected[k]] = null; } else if ((boolN6) && (boolRemove)) { objFrom.remove(arrSelected[k]); } } }

    Select Text The Select Text tool is very simple. It allows the user to select the entire contents of a text field or text area with a click of the button. This tool uses the f_selectFormText_x() function. The function is basically self-explanatory. The only parameter of this function is an object reference to the text element. Add the f_selectFormText_x() function to the t_toolbox.jss file. function f_selectFormText_x(objElement) { objElement.focus() objElement.select() } WYSIWYG HTML Text Editors

    Sometimes you will want to give your users the ability to enter more than just simple text in a text field. WYSIWYG HTML text editors allow compatible users to enter rich-text information such as boldface, italics, underlining, and even links. This is possible through DHTML using the MSHTML Editor included with PC versions of Internet Explorer 4 and later. Therefore, only clients using this configuration will be able to use the WYSIWYG editor.

    Most DHTM L WYSIWYG editors use an iframe rather than a text area to gather user input. To make the iframe editable, the design mode is set to “On.” When the form is submitted the contents of the iframe can be moved to a text field. Once you have an editable iframe, you can use the execCommand() method to change the format of the selected text.

    Find It Online For more information about the execCommand() method and a complete list of commands, visit: http://msdn.microsoft.com/workshop/author/dhtml/reference/commandids.asp. Rather than create an entire WYSIWYG editor from scratch, however, you can download one that has already been created. The makers of the XS DHTML Editor have made their product available as freeware, at http://sourceforge.net/projects/xsdheditor/. This editor uses XML and DHTML Behaviors to produce the WYSIWYG effect. The first step in applying this tool to a Web page is to assign the namespace and the location of the .htc file in the header of the document. This declaration will be visible on some versions of Netscape, so you may want to surround it with a dynamic section declaration. {{section IEHead }} {{/section IEHead }}

    To add the WYSIWYG control you need to add the XML tag and a hidden text area. The htmlEditor.htc file that comes with the XS DHTML Editor includes code that creates a large number of formatting functions. By modifying this file you can customize the editor to your needs. Additionally, the editor comes with a good set of documentation that should help you resolve any difficulties you might encounter. Figure 14.2 shows how the XS DHTML Editor looks in the browser.

    Figure 14.2: The XS DHTML Editor

    Pop-Up Calendar

    The most complicated of the tools for this project is the Calendar tool. This tool uses DHTML layers to create a calendar that the user can use to automatically enter a date value in a text field. The calendar opens with the current month displayed and the date highlighted. When the user clicks a date, the value is formatted and assigned to the text field. The calendar also allows the user to move back and forth between months to set dates far in the past or long into the future.

    The Calendar tool uses the t_calendar.html file. This file may seem intimidating at first, but it is really quite simple. I’ve included comments to help you understand how the code works. By the time you reach the end, you should have a handle on it. Include the t_calendar.html file in the toolbox folder.

    t_calendar.html

    Pick a date

    <<   >>

    Su Mo Tu We Th Fr Sa

    To apply the Calendar tool you will need to create a function that will open a new window. The URL for the new window should include the query sting required by the Calendar page. The following example shows how this can be accomplished using the f_openWin_x() function from the catalog interface. Figure 14.3 shows the end result.

    Figure 14.3: The Calendar tool

    Date Me You should add the f_doCalendar() function to the t_toolbox.jss file.

    In the next chapters you’ll learn how to integrate the Calendar tool and the rest of the toolbox into a complete interface.

    Chapter 15: Designing a Functional Interface For this project you will create the information maintenance pages as well as a number of public display pages. The project will be built around a series of information packets called articles. The interface will allow the maintenance team to create, modify, and delete these articles. A good deal of the code for this project will be recycled from the catalog and membership forms. The information systems interface introduces some new concepts and techniques including access limitations and transactional updates.

    Site Architecture

    The membership interface introduced the three types of pages in a form interface: form, action, and display. In the online catalog project you learned about other types of pages: public and private. Following the trend, the information systems interface introduces another page type: restricted. Unlike private pages, which are available to anyone with a proper login, restricted pages require that the user have specific permission to access the page. Restricted pages may also involve levels of access. For example, all users may be able to access the Article Selector page, but only a few will see the button to edit an article and even fewer will see the delete button.

    Form Pages Many of the form pages for this interface will seem familiar. The basic concepts are taken from the pages you’ve already created. These pages, however, take the code a few steps further. Each page that is designated as a restricted page will have a block of code that will determine the user’s access privileges. Additionally, these form pages will contain some of the toolbox elements created in the last chapter, and other new features, such as context-sensitive help.

    A number of form pages will be taken from earlier projects, most notably the Login pages. Here are the form pages that are unique to this project: § Article Selector. This restricted page is similar to the Item Selector from the catalog interface. A new feature is the inclusion of the select box search tool. § Add/Edit Article. This restricted page is very large and complex. It will include such toolbox elements as linked select boxes, select box reordering, and the pop-up calendar. The user will be required to enter data in fields for the article title, author, description, and active date field. An active check box will also be available. Control elements will be included on this page, including links to add a new page, edit a page, delete a page, add a section, edit a section, delete a section, reset the form, and submit the form. § Delete Article. This restricted page will be similar to the Delete Item page. § Add/Edit Section. This restricted subpage will include the WYSIWYG editor. In addition, the page will require content for the heading and section content fields. A check box will also be available to designate whether to show the heading or not. Finally, the page will contain a control link to assign an image to a section. § Feature Articles. This restricted page will present a list of all the articles currently in the database. The Select Box search tool will allow the user to search the list by title or ID. Toolbox elements will allow the user to add articles to another Select Box that contains the currently featured articles. Another toolbox element will allow the user to reorder the featured articles. Submit and reset buttons will be available. § Select User. This restricted page works in the same way as the Article Selector page. § Add/Edit User. The restricted page allows for the addition or modification of user information and access privileges. New users require the name and password fields. Existing users only require the name field, but password fields are presented as optional input. Additionally, a list of all the pages in the maintenance area of the site is presented. The linked select box tool will allow the user to designate specific pages as full access or limited access by moving the page options to the respective lists. Submit and reset buttons will be available. § Delete User. This restricted page works in the same way as the Delete Article page. § Select/Deactivate Feedback. This private but unrestricted page allows for the selection and deactivation of feedback items. A search field is required to find a feedback item, while options are presented to search by member name, date posted, feedback ID, and text search. § Add/Edit Help. This private and unrestricted page will allow the user to create and edit existing help items. The user will select from a list of the current pages for the interface. Selecting an item will allow the user to select from a smaller list of the help items specific to the page. The help item name and help text fields will be required. The help text field will be validated for size (fewer than 200 characters). A button will allow the user to add a new help item to the selected page; another will allow a current help item to be deleted. A submit button will save the values. § Article Search. The first public page, the Article Search page allows the Web user to search through the information in the articles currently active in the database. The only form field will hold a search string. A submit button will begin the search. § Submit/Edit Feedback. Another public page, the Submit/Edit feedback page allows the Web user to create or modify a feedback item. The page is restricted, when used to edit a feedback item. This page contains a single required text area and the WYSIWYG editor. Submit and reset buttons will be available.

    Information Systems Action Pages

    In the second project the focus was on user interface design. Because of this, most of the action pages were much longer than you might typically expect because you needed to take into account the possibility of clients with older, non-JavaScript browsers. In the interest of coding economy, the action pages for this project won’t repeat those usability code blocks. Instead, the focus of this project is on the form and display pages where the toolbox elements and new other functionality are present. Table 15.1 lists the new action pages you’ll be creating in this project and the related pages. Table 15.1: Information Systems Action Pages Action Page Form Page Result

    Process Article Article Selector Add/Edit Article Update Article Add/Edit Article Article Selector Remove Article Delete Article Delete Confirmation Update Section Add/Edit Section Closes subpage Update Featured Articles Feature Articles Feature Articles Process User Select User Add/Edit User Update User Add/Edit User Select User Remove User Delete User Select User Process Feedback Select/Deactivate Select/Deactivate Feedback Feedback Update Help Add/Edit Help Add/Edit Help Process Article Search Article Search Article Search Results Update Feedback Submit/Edit Article Page Feedback

    Display Pages

    The project will involve displaying data in a variety of ways. The display pages, as well as some of the form pages, will require the same information to be displayed in multiple formats. This project will also be the first to include full-text searches. This will require a new type of display, the Search Result.

    These are the display pages unique to this interface: § Article Showcase . This page will show all the featured articles. In addition, the first featured item will be highlighted even further. If no featured articles exist then the last five active articles will be displayed by date. § Article Page. This is a single page from a multi-page article. It will include each assigned section, as well as the page title and links to the next and preceding pages, if appropriate. Additionally, the page will include a link to the Articles by Author page. § All Articles. This page will list all the articles in the database alphabetically. § Articles by Date. This page will list all the articles in the database, sorted by the date they were assigned. § Articles by Author. This page will list all the articles written, ordered by author and then by the date they were assigned. § Article Search Results. This page will show the results, if any, of a search of the article database.

    Database Architecture

    As with the site architecture, the database architecture for this interface includes reusing existing tables. Most notable among the recycled tables are image and ImageAssignment from the catalog interface, and membership and member Demographics from the member forms. Also, the AuthUsers table will be reused with a slight change. The following section contains information about the tables and fields unique to this interface. After each table is created feel free to seed the tables with a record or two. This will make your life easier when it comes time to test the interface.

    AuthUsers Table

    With the addition of one field, the AuthUsers table will be ready for the information systems interface. Because the AuthUsers table will now be modified directly through the interface, it is important to give the records in the table a descriptive identifier. In this case, that identifier is a real name. This will make it far easier to locate a user’s records. For example, rather than searching for “waskly1,” a user can look for “Bugs Bunny.”

    To change the AuthUsers table, open the MySQL administrator and switch to the dwf database. Then type the following: ALTER AuthUsers ADD RealName varchar(255) not null;

    You will then have to update each of the current records with the RealName value. Caution If you are using a relational database other than MySQL, contact your DBA before performing the table change. Some databases will drop all of the table data when a table is modified using the ALTER command.

    Article Table

    The Article table contains the basic information for the articles, including the title, a description, and the active value. In addition, the Article table contains a number of fields with new uses. The information systems interface will append the user key of the last user to modify the article and the date and time the change was made. Moreover, the Article table will include a number of fields that will allow the interface to lock out other users so that only a single person can make changes at any one time.

    Following are the fields for the Article table: § ArticleKey. Integer, primary key, not null, auto increment § ArticleTitle. Varchar(255), not null § Author. Integer, not null § Description. Text, not null § Active. Integer § ActiveDate. Varchar(20), not null § LastModBy. Integer, not null § LastModDate. Datetime, not null § LockedBy. Integer § LockedTime. Datetime

    ArticlePage Table

    The ArticlePage table will contain the title information for each page of an article. In addition, this table will be used to define the order of the pages for display in the interface.

    The following fields should be included in the ArticlePage table: § PageKey. Integer, primary key, not null, auto increment § ArticleKey. Integer, not null § PageTitle. Varchar(255), not null § PageSort. Integer, not null

    PageSection Table

    The PageSection table does for the article sections what the ArticlePage table does for the article pages. Besides the sort order and heading fields the PageSection table also includes a field for the section content and a field for the heading display.

    The following fields should be included in the PageSection table: § SectionKey. Integer, primary key, not null, auto increment § PageKey. Integer, not null § Heading. Varchar(255), not null, fulltext § ShowHeading. Integer § SectionContent. Text, not null, fulltext § SectionSort. Integer, not null In addition to these fields, you will need to create a full-text index for the SectionContent and Heading fields. A full-text index is a reference tool used by a database to allow for quick full-text searching of a column or set of columns. Some database systems refer to this as a full-text catalog. When using a full-text index to retrieve information from a table the results are automatically sorted by relevance. Each database system has its own unique algorithm for determining relevance, so results from a MySQL database may not match those from Oracle or PostgreSQL even with the same data. With MySQL you can create a full-text index at the same time you create the table by adding FULLTEXT(SectionContent, Heading) to the end of the CREATE TABLE statement, like so: CREATE TABLE PageSection (SectionKey int primary key not null auto_increment, PageKey in not null, Heading varchar(255) not null, ShowHeading int, SectionContent text not null, SectionSort int not null, FULLTEXT (SectionContent, Heading)); You can also add an index after the table has been created using the ALTER TABLE syntax: ALTER TABLE PageSection ADD FULLTEXT(SectionContent, Heading);

    FeatureOrder Table

    The FeatureOrder table simply sets the sort order for the featured articles. The lower the sort order, the closer to the top of the page the article will appear. The article with the sort order of 0 will be additionally highlighted.

    Following are the fields in the FeatureOrder table: § SortKey. Integer, primary key, not null, auto increment § ArticleKey. Integer, not null § ArticleSort. Integer, not null Feedback Table

    The Feedback table holds all the information for the feedback items in the interface, with fields for the content, the member ID of the user who created the feedback, the date and time it was last modified, and an active value. Only feedback items that are marked as active will appear in the interface. A full-text index of the FeedbackContent field will also be created.

    Following are the fields for the Feedback table: § FeedBackKey. Integer, primary key, not null, auto increment § MemberKey. Integer, not null § FeedbackContent. Text, not null, fulltext § LastModDate. Datetime, not null § Active. Integer

    PageAssignment Table

    The PageAssignment table contains information about all the pages for the interface. This is the one table that won’t be modified by the interface. Instead you, as the developer, will create a record for each page. This arrangement exists because the records in the PageAssignment table are useless without the appropriate scripts in the object files, and only developers have access to these files—so there is no reason to create a user interface for this table. The PageAssignment table contains fields for the file name and the name of the page.

    Following are the fields for the PageAssignment table: § PageAssignmentKey. Integer, primary key, not null, auto increment § FileName. Varchar(25), not null § PageName. Varchar(255), not null

    Access Table

    New to the information system interface is variable access functionality. Using the Access table, server-side scripts can determine whether the current user has permission to access the requested file. Requests from users who do not have the proper access privileges result in an error message being displayed.

    Following are the fields for the Access table: § AccessKey. Integer, primary key, not null, auto increment § PageAssignmentKey. Integer, not null § UserKey. Integer, not null § Permission. Integer

    Help Table

    The last new table is the Help table. This table will be used by the context-sensitive help function to generate the help messages on the screen. The Help table includes fields for the page assignment key, the name of the element to which the help text refers, and the text itself.

    Following are the fields in the Help table: § HelpKey. Integer, primary key, not null, auto increment § PageAssignmentKey. Integer, not null § ElementName. Varchar(40), not null § HelpContent. Varchar(255), not null

    File System Architecture

    This project will expand on the file system architecture that was created for the catalog interface. Before you begin to create the project files for the information system interface you should create a backup copy of the files you have created thus far. This project will expand on the concepts of the earlier chapters and thus will require a few minor modifications to the common project files. These modifications may cause errors in the catalog interface. When you’re done with the project, if you wish, you can repost the original catalog files, keep the information systems files, or apply the new concepts to the catalog pages so that both systems will work concurrently.

    Variable Access

    Variable access has been around since the beginning of computer age. To secure a system against unwanted intrusion, a systems administrator would set up a set of records detailing the names of authorized users and their passwords. Once this type of security was implemented, administrators immediately saw the benefit of creating levels of access. Most users would be limited to a set of directories or files that they would be able to view or change. Other, more experienced or important users would be given increased access to the system. Finally, the administrators and a few select others would be given a global or “root” access to the entire system.

    Variable access does more than just give system administrators something to do. Requiring a login can help prevent outside intrusion in a system, such as from a hacker. Variable access helps protect the system from its most dangerous antagonists: the users themselves. A distracted or improperly trained employee can bring down a system far faster than can an outsider. Don’t believe me? Just ask your systems admin who is more dangerous, the lone individual who created the “ILUVYOU” virus or the thousands of employees who opened the e-mail attachment despite the numerous warnings against doing so. The variable access system for this interface will apply adaptable restrictions to restricted Web pages. Although the files in this project will only use three levels of access—full, limited, and none—the access system code is designed to handle an unlimited number of levels. The core of the variable access system is a pair of classes called o_Access and o_AccessPublic. As you might expect, the first class is used to restrict access for private pages, while the second performs variable access functions for public pages. You will be creating both classes in a new file called i_access.php. Place this file in the global directory and add the following code to the top of the document; this will ensure that the $CONNECTION variable has been defined: global $HTTPS; if (!defined($CONNECTION)) { if (isset($HTTPS)) { include_once("./global/i_connect.php"); } else { include_once("./secure/global/i_connect.php"); };

    }; The o_Access class has only two properties and a single constructor method. class o_Access { var $p_strAccessDenied = false; var $p_intAccess; function o_Access() { … The o_Access() constructor method is automatically called when the class is instantiated. The first part of this method ensures that the user has logged into the site by checking the value of the UserID cookie. global $PHP_SELF; global $CONNECTION; global $REQUEST_URI; if (!isset($GLOBALS["UserID"])) { session_register("sessRefer"); $GLOBALS["sessRefer"] = $REQUEST_URI; header("Location: secure.php?Page=login"); }; Next, the o_Access() method uses the environmental variable $PHP_SELF to determine the name of the file that called the class. $PHP_SELF does not include the query string, but it does include the path to the file. The o_Access() method uses the explode() function to split the string into an array. The last item in the array is the name of the file. $arrFileName = explode("/", $PHP_SELF); $strFileName = $arrFileName[count($arrFileName) - 1]; Once the name of the file is determined, a query to the database determines the value of the Permission field for the current user and the specified page. This value is then assigned to the p_intAccess property. $strAccessTable = "Access"; $strPageAssignmentTable = "PageAssignment"; $strAccessSQL = "SELECT Permission FROM $strAccessTable LEFT JOIN $strPageAssignmentTable ON $strAccessTable.PageAssignmentKey = $strPageAssignmentTable.PageAssignmentKey WHERE $strPageAssignmentTable.FileName LIKE \"$strFileName\" AND UserKey =" . $GLOBALS["UserID"]; $objAccessResult = @mysql_query($strAccessSQL,$CONNECTION); $arrResult = mysql_fetch_array($objAccessResult); $this->p_intAccess = $arrResult["Permission"]; mysql_free_result($objAccessResult); Finally, if the permission value is not found or if the number 0 is returned, then the p_strAccessDenied property is assigned an access denied message. This message can be used by the original page to alert the user that access to the page has been blocked. if (!$this->p_intAccess) { $this->p_strAccessDenied = "You do not have permission to access this page, please contact the site administrator if you think you have received this message in error."; }; Below is the complete code for the o_Access class so far. A little later you will be adding another method to the o_Access class to determine whether another user has locked out an item. Add the o_Access class to the i_access.php file. class o_Access { var $p_strAccessDenied = false; var $p_intAccess;

    function o_Access() { global $PHP_SELF; global $CONNECTION; global $REQUEST_URI;

    if (!isset($GLOBALS["UserID"])) { session_register("sessRefer"); $GLOBALS["sessRefer"] = $REQUEST_URI; header("Location: secure.php?Page=login"); exit(); };

    $arrFileName = explode("/", $PHP_SELF); $strFileName = $arrFileName[count($arrFileName) - 1]; $strAccessTable = "Access"; $strPageAssignmentTable = "PageAssignment"; $strAccessSQL = "SELECT Permission FROM $strAccessTable LEFT JOIN $strPageAssignmentTable ON $strAccessTable.PageAssignmentKey = $strPageAssignmentTable.PageAssignmentKey WHERE $strPageAssignmentTable.FileName= \"$strFileName\" AND UserKey =" . $GLOBALS["UserID"]; $objAccessResult = mysql_query($strAccessSQL,$CONNECTION) or die("Couldn't get page permission information."); $arrResult = mysql_fetch_array($objAccessResult); $this->p_intAccess = $arrResult["Permission"]; mysql_free_result($objAccessResult);

    if (!$this->p_intAccess) { $this->p_strAccessDenied = "You do not have permission to access this page, please contact the site administrator if you think you have received this message in error."; };

    }

    } The public side of the variable access system performs two functions. First, it can be used to restrict access to a public page to only those who have logged in. Second, it can be used to determine if the memberID of the current user matches the value of a supplied parameter. Each of these functions is performed by methods of the o_AccessPublic class. The first method, m_doRestrict() is essentially just a collection of the login validation code block from the membership forms. By using this method you avoid having to repeat the same code block for each of the restricted pages. function m_doRestrict() { session_start(); global $REQUEST_URI;

    if (!$GLOBALS['MemberID']) { session_register("sessRefer"); $GLOBALS["sessRefer"] = $REQUEST_URI; header("Location: main.php?Page=login"); } } The second method, m_isMember() will be used to determine whether to display member-specific content. For example, in this project members will be able to add their comments to an article using the feedback system. Once feedback has been added, only the member who created the item will be able to edit the post. By using the m_isMember() method you can determine whether to display the Edit Post link or not, depending on the memberID of the user. The actual code for the m_isMember() method is quite simple. It takes the supplied parameter and compares it to the value of the memberID from the session variable that was set after the user logged in. A match returns a value of true; otherwise a value of false is returned. function m_isMember($intMemberID) { if ($intMemberID == $GLOBALS['MemberID']) { return true; } else { return false; };

    } Following is the complete code for the o_AccessPublic class. Add this class below the o_Acccess class in the i_access.php file. class o_AccessPublic { function m_doRestrict() { session_start(); global $REQUEST_URI;

    if (!$GLOBALS['MemberID']) { session_register("sessRefer"); $GLOBALS["sessRefer"] = $REQUEST_URI; header("Location: main.php?Page=login"); exit(); }; }

    function m_isMember($intMemberID) { if ($intMemberID == $GLOBALS['MemberID']) { return true; } else { return false; };

    }

    }

    ?>

    Context-Sensitive Help Chapter 2 covered the basics of context-sensitive help systems and the advantages of such systems for the user. Now, you get a chance to create your own context-sensitive help system. This will be in two parts, a PHP class and a JavaScript DHTML component. By combining the client -side and server-side elements you can make the entire system modular enough to go on any page of the interface. For the server-side part of the help system you will create a new class called o_Help. Basically, this class will load the JavaScript file that holds the client-side portion of the help system, and then replace the dynamic marker tags with the help text content. By generating the JavaScript dynamically, you avoid having to create a separate help application for each page. The o_Help class will be located in the toolbox directory inside the global file. To begin the help system, create this file and add the following: global $HTTPS; if (!defined($CONNECTION)) { if (isset($HTTPS)) { include_once("./global/i_connect.php"); } else { include_once("./secure/global/i_connect.php"); };

    }; The o_Help class has two properties and a single constructor method. The first property will be set to the name of the page that the class is called from, while the second is the template output that will be passed to the referring script. class o_Help { var $p_strPage; var $p_strOutput;

    function o_Help() { … The first part of the o_Help() method is similar to the beginning of the o_Access() method where the name of the page is determined. The difference is that the o_Help() method also takes into account the possibility that the user is on the main.php page. In this case, the value of the $Page query string variable is used as the page name. global $PHP_SELF; global $Page; global $CONNECTION; global $HTTPS; if ($Page) { $this->p_strPage = $Page; } else { $arrFileName = explode("/", $PHP_SELF); $this->p_strPage = $arrFileName[count($arrFileName) - 1]; } Next, the JavaScript file is loaded into a new template object. This object has two dynamic sections. The first is actually located inside the SCRIPT tag of the JavaScript file. This section, HelpArray, will dynamically populate a JavaScript array. The second section will generate the DHTML layers that will hold the help information. $objHelpTemplate = new o_Template; if (!isset($HTTPS)) { $objHelpTemplate->m_LoadFile("./secure/global/toolbox/t_help.jss"); } else { $objHelpTemplate->m_LoadFile("./global/toolbox/t_help.jss"); }; $strHelpArraySection = $objHelpTemplate->m_DefineSection("HelpArray"); $strHelpTextSection = $objHelpTemplate->m_DefineSection("HelpLayers"); The next section follows the typical pattern by retrieving the help information from the database and, using an index variable and a while loop, adds the dynamic content to the sections. When the content has been added, the result is added to the $p_strOutput property. This property can then be used to assign the JavaScript to any page in the interface. Here is the complete text of the o_Help class: class o_Help { var $p_strPage; var $p_strOutput;

    function o_Help() { global $PHP_SELF; global $Page; global $CONNECTION; global $HTTPS;

    if ($Page) { $this->p_strPage = $Page; } else { $arrFileName = explode("/", $PHP_SELF); $this->p_strPage = $arrFileName[count($arrFileName) - 1]; };

    $objHelpTemplate = new o_Template;

    if (!isset($HTTPS)) { $objHelpTemplate->m_LoadFile("./secure/global/toolbox/t_help.jss"); } else { $objHelpTemplate->m_LoadFile("./global/toolbox/t_help.jss"); };

    $strHelpArraySection = $objHelpTemplate->m_DefineSection("HelpArray"); $strHelpTextSection = $objHelpTemplate->m_DefineSection("HelpLayers"); $strHelpTable = "Help"; $strPageAssignmentTable = "PageAssignment"; $strHelpSQL = "SELECT ElementName, HelpContent FROM $strHelpTable LEFT JOIN $strPageAssignmentTable ON $strHelpTable.PageAssignmentKey = $strPageAssignmentTable.PageAssignmentKey WHERE $strPageAssignmentTable.FileName LIKE \"" . $this->p_strPage . "\""; $objHelpResult = mysql_query($strHelpSQL,$CONNECTION) or die("Couldn't get help items."); $intHelpCount = 0;

    while ($arrHelp = mysql_fetch_array($objHelpResult)) { $strHelpArray = $objHelpTemplate->m_AssignContent("INDEX", $intHelpCount, $strHelpArraySection); $strHelpArray = $objHelpTemplate->m_AssignContent("ITEM_NAME", $arrHelp['ElementName'], $strHelpArray); $strHelpArray = $objHelpTemplate->m_AssignContent("HELP_TEXT", $arrHelp['HelpContent'], $strHelpArray); $objHelpTemplate->m_AssignSection("HelpArray", $strHelpArray); $strHelpText = $objHelpTemplate->m_AssignContent("INDEX", $intHelpCount, $strHelpTextSection); $strHelpText = $objHelpTemplate->m_AssignContent("HELP_TEXT", $arrHelp['HelpContent'], $strHelpText); $objHelpTemplate->m_AssignSection("HelpLayers", $strHelpText); $intHelpCount++; };

    $objHelpTemplate->m_PrepTemplate(); $this->p_strOutput = $objHelpTemplate->p_strOutputContents; }

    }

    The client-side portion of the context-sensitive help system is included in the t_help.jss file. This file should also be placed in the toolbox folder. Much of the JavaScript code for this file is bogged down with browser-specific code or duplicates functionality you have already seen. The following is the complete file, with my comments throughout: t_help.jss

    {{section HelpLayers }}

    {{/section HelpLayers }}

    Transactions

    Imagine you won the lottery. After a short but heartfelt celebration you rush to the lottery commission and hand over the ticket. A technician runs it through a series of tests and declares that it is indeed a valid winning ticket. You sign all the paperwork and have your picture taken by the local press. Just as the disbursement manager is handing you the check a freak gust of wind picks it up and blows it directly into an incinerator chute that someone carelessly left open. The lottery official looks up at you and shrugs, “Sorry, but the rules state: one ticket, one check—no exceptions.” You could really use a transaction system right about now.

    A transaction system is built on one simple premise: the system environment is never static. These systems work functions together in groups called transactions. Each part of the transaction is made individually. If any errors are encountered the entire set is rolled back, or returned to the state it was before the transaction began.

    In this way a transaction system can protect data from the type of corruption that can occur when multiple users change the same information, data results are corrupted, the server crashes, or someone forgets to close an incinerator chute door. The traditional type of transaction system is the transactional database. In this paradigm the database does not automatically process queries. Instead queries are placed in a buffer until the go-ahead is given to perform the operation. Many relational databases, including MySQL, have built-in transactional functionality. In MySQL only the specific table types InnoDB or BDB can be used for these transactions. You can change a table type with the ALTER TABLE command. ALTER TABLE TableName TYPE=InnoDB; Transactional databases typically use the BEGIN, COMMIT, ROLLBACK syntax or an equivalent. The BEGIN command is used to designate the start of a transaction. Any queries that follow are attached to the transaction. You can perform updates, inserts, deletes, or any other SQL commands. When all the queries are completed, the COMMIT command causes the transaction to be processed. If for some reason the transaction should not be processed the ROLLBACK command cancels the transaction and reverts the data to its original state. BEGIN UPDATE table SET … DELETE FROM table … SELECT field FROM … COMMIT

    Transactional databases have a number of advantages. They are easy to program, they are far more secure than the alternative, and you can choose among a large number of products—both free and commercial—that use similar systems. On the other hand, queries using transactional tables are typically four to five times slower then nontransactional tables. Additionally, the files and system memory needed to maintain a transactional database are an order of magnitude larger than a standard database needs. For large datasets, this can require purchases of additional disk space or RAM. An alternative to transactional databases is termed atomic operations. With atomic operations you designate the tables that the transaction will affect and lock them. This prevents other users from modifying the tables until you have finished your transaction and unlocked them. This won’t protect the data if the server crashes halfway through the transaction, but it will prevent multiple users from changing a single record at the same time. For most Web applications of relational databases this is enough. MySQL uses LOCK TABLES and UNLOCK TABLES to produce atomic operations. If you set the lock value to "read," then only SELECT commands can be performed on the table. If the lock is set to "write," then only the current thread can update, insert, or delete data from the table. A table is automatically unlocked if the connection to the database is closed or if another LOCK statement is made. To lock a table, use the following notation: LOCK TABLES Table1 AS READ, Table2 AS WRITE UPDATE Table1 SET … DELETE FROM Table 2 … UNLOCK TABLES

    Most database queries do not require any kind of locking mechanism. If you are performing a single database query or a series of unrelated queries, then there is no reason to lock the tables. If, however, a sequence of queries requires that the data remain static throughout the transaction, you should consider locking the tables.

    Transactional databases and atomic operations are useful tools and have their place in Web forms, but multiuser form interfaces require a different type of transaction system. The problem with Web forms goes back to the fact that the client-server interaction is stateless. Picture a situation wherein the Web server transmits the same page filled with dynamic form content to ten different clients. Each modifies the information differently and at different rates. When the forms are submitted each consecutive post to the server will overwrite the changes made by the one that arrived before it.

    You could use a transactional database or atomic operations to confirm that the table data is still in the original state before making the changes, but this solution presents two new problems. First, you will have to create temporary tables or use a large number of hidden fields to store the original data so that the script can compare the values. Second, because the user connects with the server only when the form is submitted, you create the scenario where time and effort has been spent updating data in a form only to have the changes refused after the form is posted. This makes for unhappy users, as they can never know until after they have filled out a form whether someone else has already modified the data. To avoid these problems you can create a record locking system. As opposed to locking an entire table, record locking only prevents other users from accessing a single item. Actually, the user is only prevented from accessing the form page that changes a locked record. This is an important distinction. A user who encounters an unrestricted version of the form page or a direct connection to the database will still have the ability to update the record. Think of a couple sharing a single car. When one person is using the car, the other has a set of keys but can’t actually drive because the car is gone. If the second person finds the car parked somewhere, the keys will work just fine. You will hide the car, so to speak, using the m_isLocked() method. This is a new method for the o_Access class. The information systems interface will only use the record locking system for articles, but to make the m_isLocked() method as modular as possible the name of the database table and primary key column are passed as parameters along with the key value to check the results against. To check an Add/Edit Article page, for example, you would call $objRestrict->m_isLocked ($formArticleKey, "ArticleKey", "Article");. function m_isLocked($intPrimaryKey, $strKeyName, $strTableName) { … The m_isLocked() method begins with a query to the database. The DATE_FORMAT function is specific to MySQL, but other relational databases have an equivalent. DATE_FORMAT causes the database server to convert a date or timestamp value into another, more readable format. $strAuthUserTable = "AuthUsers"; $strLockedSQL = "SELECT DATE_FORMAT(LockedTime, '%W, %M %e, %Y, at %l:%i %p') AS LockedTime, $strAuthUserTable.RealName FROM $strTableName LEFT JOIN $strAuthUserTable ON $strTableName.LockedBy = $strAuthUserTable.UserKey WHERE LockedBy <> " . $GLOBALS["UserID"] . " AND LockedBy IS NOT NULL AND $strKeyName = $intPrimaryKey"; $objLockedResult = mysql_query($strLockedSQL,$CONNECTION) or die(mysql_error()); Find It Online You can find a list of all the DATE_FORMAT formatting options and information about other MySQL date and time functions at http://www.mysql.com/ doc/D/a/Date_and_time_functions.html. The last part of the m_isLocked() method checks whether the page has been locked. If it has, the p_strAccessDenied property is set to an error message. if (mysql_num_rows($objLockedResult) > 0 ) { $this->p_strAccessDenied = "This item was locked out by " . mysql_result($objLockedResult,0,"RealName") . " on " . mysql_result($objLockedResult,0,"LockedTime") . ".
    If you need to access this item, please contact this individual or the administrator."; }; Below is the complete m_isLocked() method. You will need to add it to the o_Access class in the i_access.php file. function m_isLocked($intPrimaryKey, $strKeyName, $strTableName) { global $CONNECTION; $strAuthUserTable = "AuthUsers"; $strLockedSQL = "SELECT DATE_FORMAT(LockedTime, '%W, %M %e, %Y, at %l:%i %p') AS LockedTime, $strAuthUserTable.RealName FROM $strTableName LEFT JOIN $strAuthUserTable ON $strTableName.LockedBy = $strAuthUserTable.UserKey WHERE LockedBy <> " . $GLOBALS["UserID"] . " AND LockedBy IS NOT NULL AND $strKeyName = $intPrimaryKey"; $objLockedResult = mysql_query($strLockedSQL,$CONNECTION) or die(mysql_error());

    if (mysql_num_rows($objLockedResult) > 0 ) { $this->p_strAccessDenied = "This item was locked out by " . mysql_result($objLockedResult,0,"RealName") . " on " . mysql_result($objLockedResult,0, "LockedTime") . ".
    If you need to access this item, please contact this individual or the administrator."; }; mysql_free_result($objLockedResult); } Now that you have created all of the tools and classes that you’ll need, you can head on to the next chapter. There you will put the whole information systems interface together by combining elements of the catalog and membership interfaces with the new functions.

    Chapter 16: Creating the Online Information System Itself

    In the first two projects the page descriptions were broken down by page type. By now you should have a handle on how the project files fit together in the file system. For the information systems interface, I’ll explain each file as part of a larger group of pages, all with a similar theme. Hopefully, this will give you a greater sense of how the files function as an entire interface, beyond just the single page level.

    Modifying Existing Pages As you’ve already learned, the information systems interface will be reusing a large number of the files used in the first two projects. These files, however, will require some modifications to bring them up to your current level of expertise. The membership pages, for example, don’t have any of the user interface elements you created in the catalog interface. At the same time, the catalog pages don’t use the tools that were developed for this project in the “The Web Form Toolbox” section of Chapter 14.

    The following section gives a brief overview of the changes you will need to perform for each page type.

    Global Files Two changes will be required in the global files from the catalog interface. The first involves the use of the context-sensitive help system that was created in Chapter 15. Previously, the m_AddElements() method of the o_Interface class replaced the marker tag "HELP_PAGE" with the name of the Help page file. The new context- sensitive help provides a better alternative. Now, rather than sending the user to another page, the system can display the help contents in the same window. To streamline the process of adding the help content, the m_AddElements() method should be modified to include the additional functionality. A new marker tag called "HELP_LINK" will be used to generate a check box and label. Using a check box rather than a link will make it easier for the user to recognize and use the help function. $this->m_AssignContent("HELP_LINK","

    Help
    ");

    Two other marker tags are also used with the context-sensitive help system. Both are used to fix problems encountered when using the help system. The “BUTTON_RETURN” tag is replaced with a variable check that determines whether the help button has been pressed. This tag should be applied to all submit and reset buttons like this: Without this tag, some browsers will process the submit or reset request before applying the help function. This causes the page to change and the help content is never displayed. The “NS_FOCUS” marker tag should be applied to the onFocus attribute of every text, password, textarea, file upload, or select box on any form that uses the context-sensitive help system. Netscape 4 does not register clicks on these elements properly, so for users of that system the marker tag is replaced by a reference to a function that serves the same purpose. Here is the complete text of the updated m_AddElements() method: function m_AddElements($strPageTitle) { $this->m_LoadFile("templates/navigation.tpl","NAVIGATION"); $this->m_LoadFile("templates/footer.tpl","FOOTER"); $this->m_AssignContent("TITLE","DWF - " . $strPageTitle);

    if (($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] < 5)) { $this->m_AssignContent("HELP_LINK","

    Help
    "); $this->m_AssignContent("BUTTON_RETURN","return !boolHelp;"); $this->m_AssignContent("NS_FOCUS","onFocus=\"f_doNSHelp(event)\""); } else if (($GLOBALS["Browser"] == "IE") || (($GLOBALS["Browser"] == "Netscape") && ($GLOBALS["Version"] >= 5))) { $this->m_AssignContent("HELP_LINK"," Help"); $this->m_AssignContent("BUTTON_RETURN","return !boolHelp;"); $this->m_AssignContent("NS_FOCUS",""); } else { global $HTTPS; $this->m_AssignContent("NS_FOCUS",""); $this->m_AssignContent("BUTTON_RETURN","//;");

    if (!isset($HTTPS)) { $this->m_AssignContent("HELP_LINK","Help"); } else { $this->m_AssignContent("HELP_LINK","Help"); };

    };

    }

    Other Pages

    The primary changes to the remaining pages are the addition of the user interface elements from the catalog interface and the new toolbox elements. The following list describes how you should apply these changes to the project files. Of course, if interface uniformity isn’t an issue, feel free to leave the files as they currently are. They will still function without these modifications. § Add a SCRIPT tag referencing the i_interface.jss file at the top of each page that includes form elements. § Add the “HELP” marker tag on each template page. For best performance in multiple browsers, this tag should be placed outside of any table, layer, or form tags. § Add label tags and the f_selectElement() function to the descriptive text of each form element. § Add the “NS_FOCUS” and “BUTTON_RETURN” tags as described earlier in this chapter. § Add the accesskey attribute to each form button. § Modify the a_login.php file to set long-term cookies rather than the session variables. § Change the references to the global files to point to the secure folder. § Modify any file or code that references a form by number. The help system uses a check box to activate the help functionality. In Netscape the check box will not appear without a form tag. This will cause the main form on the page to be number 1, rather than 0. § For the public restricted pages add a reference to the o_AccessPublic class: include_once("./secure/global/i_access.php"); $objRestrict = new o_AccessPublic; $objRestrict->m_doRestrict(); § For the private restricted pages add a reference to the o_Access class: include_once("global/i_access.php"); $objRestrict = new o_Access; § Add the o_Help class to each object file: include_once("./secure/global/toolbox/t_help.php"); $objHelp = new o_Help; $objTemplate->m_AssignContent("HELP", $objHelp->p_strOutput);

    User Pages The user pages will be used to create, delete, and modify user records. In addition, they also function as the method of assigning access privileges to individual users. Before you can begin to create the files, however, you will need to add a few records the database tables. First off, you need to enter the names and files for each restricted page into the PageAssignment table. Table 16.1 lists each of these pages and the recommended name to enter into the PageAssignment table. Table 16.1: Restricted Pages

    File Name userselect.php Select a User userupdate.php Update or Add a User userdelete.php Delete a User articleselect.php Select an Article articleupdate.php Add or Update an Table 16.1: Restricted Pages File Name Article sectionupdate.php Add or Update a Sectio n sectiondelete.php Delete a Sectio n articledelete.php Delete an Article featurearticles.php Assign Featur ed Article s

    Select and Delete User The Select and Delete pages for the information systems interface are virtually identical to the corresponding pages of the catalog interface. Rather than repeat the entire code for these pages, I’ll just cover the differences. If you would like to see the entire content of the project files, go to http://www.premierpressbooks.com, click on the link for this book, and download them.

    The first difference is that the information systems interface uses the i_info.jss file rather than i_catalog.jss to hold the interface-specific JavaScript. You will need to add a reference to this file on any page that requires client-side scripting. Many of the functions for this file are clones of similar functions from the i_catalog.jss file. In an environment where both interfaces are coexisting, you could avoid the repetition by consolidating the two files.

    Many of the select pages for this project also take advantage of the text-box searching tool. To add this functionality to the page you must first add a reference to the toolbox file at the top of the template page. Next, create the search box using a standard text input element. The f_searchSelect_x() function was described in Chapter 14. Finally, the Select and Delete pages use the variable access functionality of the o_Access class to determine both access to the page and the page content itself. For example, the Select User page only allows users who have permission set to 1 or greater to remain on the page.

    Creating the Online information system itself Chapter 16 if ($objRestrict->p_intAccess < 1) { session_register("sessErrors"); $GLOBALS["sessErrors"] = $objRestrict->p_strAccessDenied; header("Location: secure.php?Page=home"); exit(); }

    While the Select Article page allows users with limited access to view the page, specific content is not displayed unless the user has full access assigned. if ($objRestrict->p_intAccess > 0) { $strDelete = $objTemplate->m_AssignContent("DELETE", "formDel", $strDeleteSection); $objTemplate->m_AssignSection("Delete", $strDelete); };

    All of the Delete pages require full access.

    Update User The Update User page is the first to use the linked select box tool. Together with a set of three select boxes, the f_addOption_x() function allows the operator to assign or remove user access privileges for any of the pages. Additional fields also allow for the changing of a user name or password. Figure 16.1 shows the page layout.

    Figure 16.1 : The Update User page

    Below is an example of one of the add options buttons. This button will remove the selected option from the list of all pages and place it in the list of limited pages.

    The following code is used by the object page to generate the initial options based on the data in the database. Basically, it returns the records into a multi-dimensional array, then cycles through the array to populate the dynamic sections. The Permission field is used as the check value. $strPageAssignmentTable = "PageAssignment"; $strAccessTable = "Access"; $strAcessSQL = "SELECT $strPageAssignmentTable.PageAssignmentKey, PageName, $strAccessTable.Permission FROM $strPageAssignmentTable LEFT JOIN $strAccessTable ON $strPageAssignmentTable.PageAssignmentKey = $strAccessTable.PageAssignmentKey AND $strAccessTable.UserKey = $formUserKey"; $objAccessResult = mysql_query($strAcessSQL,$CONNECTION) or die("Couldn't select access information."); $intPageCounter = 0; while ($arrPageResult = mysql_fetch_array($objAccessResult, MYSQL_ASSOC)) { $arrPages['Permission'][$intPageCounter] = $arrPageResult['Permission']; $arrPages['Key'][$intPageCounter] = $arrPageResult['PageAssignmentKey']; $arrPages['Name'][$intPageCounter] = $arrPageResult['PageName']; $intPageCounter++; }; mysql_free_result($objAccessResult); for ($l = 0; $l < count($arrPages['Key']); $l++) { if ($arrPages['Permission'][$l] == 1) { $strFull = $objTemplate->m_AssignContent("FULL_PAGE", substr($arrPages['Name'][$l],0,35), $strFullSection); $strFull = $objTemplate->m_AssignContent("FULL_KEY", $arrPages['Key'][$l], $strFull); $objTemplate->m_AssignSection("Full", $strFull); } else if ($arrPages['Permission'][$l] == -1) { $strLim = $objTemplate->m_AssignContent("LIMITED_PAGE", substr($arrPages['Name'][$l],0,35), $strLimitedSection); $strLim = $objTemplate->m_AssignContent("LIMITED_KEY", $arrPages['Key'][$l], $strLim); $objTemplate->m_AssignSection("Limited", $strLim); } else { $strAll = $objTemplate->m_AssignContent("PAGE", substr($arrPages['Name'][$l],0,35), $strAllPagesSection); $strAll = $objTemplate->m_AssignContent("PAGE_KEY", $arrPages['Key'][$l], $strAll); $objTemplate->m_AssignSection("AllPages", $strAll); };

    }; When the user submits the page, the f_ValidateUserUpdate() function is called. This function, located in the i_info.jss file, combines the typical validation functions with some data preparation activity. The values from the limited and full access select boxes are transferred to hidden fields on the page. There is no need to transfer the select box that contains every page, as the database has this information already. function f_ValidateUserUpdate(objForm) { f_outputSelectBox(objForm.formFullPages, objForm.formFullAccess, ','); f_outputSelectBox(objForm.formLimPages, objForm.formLimAccess, ',');

    if ((!f_valName(objForm.formRealName)) || (!f_valLI(objForm.formUserName)) || (!f_valPassEq(objForm.formPassword, objForm.formPassword2))) { return false; } else { return true; } } When the data reaches the server it is validated and the user information is added or updated. Any current page access records for this user key are removed at this time. Now the access privileges can be set using the supplied values. The explode() function is used to create an array of values based on the comma-delimited strings from the text boxes. A foreach loop can then be used to insert each record. $strUserTable = "AuthUsers"; $strAccessTable = "Access"; if ($formUserKey > 0) { $strDeleteSQL = "DELETE FROM $strAccessTable WHERE UserKey = $formUserKey"; $objDeleteResult = mysql_query($strDeleteSQL,$CONNECTION) or die("Couldn't delete access privileges.");

    if ((isset($formPassword)) && ($formPassword != "")) { $strPassword = "Password = password(\"$formPassword\"),"; } else { $strPassword = ""; };

    $strSQL = "UPDATE $strUserTable SET UserName = \"$formUserName\", $strPassword RealName = \"$formRealName\" WHERE UserKey = $formUserKey"; } else { $strSQL = "INSERT INTO $strUserTable (UserName, Password, RealName) VALUES (\"$formUserName\", password(\"$formPassword\"), \"$formRealName\")"; };

    $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't update user information"); if ($formUserKey < 0) { $formUserKey = mysql_insert_id(); }; if ((isset($formFullAccess)) && ($formFullAccess != "")) { $arrFullPages = explode(",", $formFullAccess);

    foreach ($arrFullPages AS $PageKey) { $strFullSQL = "INSERT INTO $strAccessTable (PageAssignmentKey,UserKey,Permission) VALUES ($PageKey,$formUserKey,1)"; $objFullResult = mysql_query($strFullSQL,$CONNECTION) or die("Couldn't add full access pages"); };

    }; if ((isset($formLimAccess)) && ($formLimAccess != "")) { $arrLimPages = explode(",", $formLimAccess);

    foreach ($arrLimPages AS $PageKey) { $strLimSQL = "INSERT INTO $strAccessTable (PageAssignmentKey, UserKey, Permission) VALUES ($PageKey,$formUserKey,-1)"; $objLimResult = mysql_query($strLimSQL,$CONNECTION) or die("Couldn't add limited access pages."); };

    };

    The Help Page The Help page allows the user to update the context-sensitive help system by adding or changing help text. The template file for this page uses JavaScript to dynamically populate select boxes and text fields. The task of the object page is to ensure that the JavaScript elements that handle this functionality are properly populated. When the two are used together the result is a data package that can easily be entered into the appropriate table. Figure 16.2 shows the completed Help page.

    Figure 16.2 : The Help page The template page for the help maintenance system uses a dynamically populated array to perform the required functionality. Declaring a dynamic section inside a SCRIPT tag creates this array. The elements of the array can then be generated directly from the database by the object page. Note Strictly speaking, including JavaScript code in a template file violates the principle of separating function from form, but as the code block is short, an exception can be made. An alternative is to create a separate .jss file and use the m_LoadFile() method to generate the content in a manner similar to the t_help.jss file. As you can see, each item in the array is an object. By using a custom object you avoid having to use multi-dimensional or parallel arrays. The o_HelpItem() function is defined in the i_info.jss file. It simply takes the values of the included parameters and creates an object property for each. function o_HelpItem(intPageKey, intHelpKey, strElement, strContents) { this.pagekey = intPageKey; this.helpkey = intHelpKey; this.element = strElement; this.contents = strContents; } The real work begins when the user selects an option in the page select box. This onChange event of this element is assigned to call the f_makeHelpOptions_x() function. This function takes a reference to the current element and the output element as parameters. Before the destination select box can be filled with the appropriate information it has to be cleared of options. This is accomplished by the calling the f_removeAllOptions_x() function. function f_makeHelpOptions_x(objElement, objDest) { f_removeAllOptions_x(objDest); … After the options have been removed, the help items array is queried using a for loop to cycle through all the entries. For each item the pagekey property is checked against the value of the selected option. If the two values are equal, a new option is created in the destination select box. Actually creating the options is done in the same browser-specific manner as is the Linked Select Box tool.

    Here is the complete function: function f_makeHelpOptions_x(objElement, objDest) { f_removeAllOptions_x(objDest);

    for (l = 0; l < arrHelpItems.length; l++) { if (arrHelpItems[l]) { if (arrHelpItems[l].pagekey == objElement.value) { if (boolIE) { var objOption = new Option; objOption.value = arrHelpItems[l].helpkey; objOption.text = arrHelpItems[l].element; objDest.add(objOption); } else if (boolNN) { objDest.options[objTo.length] = new Option(arrHelpItems[l].element, arrHelpItems[l].helpkey); } else if (boolN6) { objOption = document.createElement("OPTION"); objOption.value = arrHelpItems[l].helpkey; objOption.text = arrHelpItems[l].element; objDest.add(objOption, null); } } } } } The f_removeAllOptions_x() function is nonspecific and useful enough to be placed in the t_toolbox.jss file. This function simply takes a reference to a select box and, using a loop, removes all the options. function f_removeAllOptions_x(objDest) { for (k = objDest.length - 1; k >= 0; k—) { if ((boolIE) || (boolN6)) { objDest.remove(k); } else if (boolNN) { objDest.options[k] = null; } }

    return true; } Once the current list of help items is displayed in the help item select box, the user can begin to make changes. Selecting a help item calls the f_addHelpItem_x() function. This function verifies that the user has not checked the new check box before adding the help item content to the form fields. This ensures that the user doesn’t submit existing values as a new item. Once this is confirmed, the help array is searched for the item that matches the values of the selected option. Once the array item is found, the properties are assigned to the text fields. function f_addHelpItem_x(objForm, objElement, formCheck) { if (!formCheck.checked) { for (l = 0; l < arrHelpItems.length; l++) { if (arrHelpItems[l]) { if (arrHelpItems[l].helpkey == objElement.value) { objForm.formHelpKey.value = arrHelpItems[l].helpkey; objForm.formElementName.value = arrHelpItems[l].element; objForm.formHelpContents.value = arrHelpItems[l].contents; }; }; } } } The last of the new functionality from this page occurs when the user clicks on the new check box. The onClick event of the check box calls the f_doNewHelp_x() function. This function sets the values of the text fields for a new item. function f_doNewHelp_x(objForm, objElement) { if (objElement.checked) { objForm.formHelpKey.value = -1; objForm.formElementName.value = ""; objForm.formHelpContents.value = ""; } else { return true; } } Note For users of Internet Explorer 5 and above, links can be assigned using the context-sensitive help system. Simply assign the link a name value such as this:

    You can then create a new help item that explains the link. This won’t work for users of other browsers, but it won’t produce an error, either.

    Article Display Pages

    The article display pages consist of three primary types of displays: a predefined listing of articles, a single article, or the result of a search. These pages don’t include many form elements or much new JavaScript, but they do present a few new programming wrinkles that I haven’t discussed yet.

    Article Listings

    Most of the article listing pages are very similar. These files present a list of articles sorted or limited by various parameters. The Articles by Author page shows only the articles that have been assigned a specific author. These results are then sorted by date. By contrast, the entire articles list displays the articles in alphabetical order. Each of these pages uses a very similar set of object and template pages.

    The template pages consist of a dynamic section surrounding a set of dynamic content tags that define the name of the article, the article link, and a description to display. {{section Articles }}

    {{/section Articles }} The object file in turn replaces the tag with the article data from the appropriate tables. The only significant difference between the pages is the SQL query used to fetch the results. Table 16.2 shows the SQL used for each of the article listing pages. Notice that each SQL statement only returns articles that are marked as active and that have an active date set before the current day. Table 16.2: Article Listing SQL

    Page SQL All SELECT $strArticleTable.ArticleKey, $strArticleTable.ArticleTitle, Articles $strArticleTable.Description, $strAuthorTable.RealName FROM $strArticleTable LEFT JOIN $strAuthorTable ON ($strArticleTable.Author = $strAuthorTable.UserKey) WHERE Active <> 0 AND ActiveDate < NOW() ORDER BY Table 16.2: Article Listing SQL Page SQL $strArticleTable.ArticleTitle Articles SELECT $strArticleTable.ArticleKey, $strArticleTable.ArticleTitle, By $strArticleTable.Description, $strAuthorTable.RealName Author FROM $strArticleTable LEFT JOIN $strAuthorTable ON ($strArticleTable.Author = $strAuthorTable.UserKey) WHERE Author = $ID AND Active <> 0 AND ActiveDate < NOW() ORDER BY $strArticleTable.ActiveDate Articles SELECT $strArticleTable.ArticleKey, $strArticleTable.ArticleTitle, By Date $strArticleTable.Description, $strArticleTable.ActiveDate, $strAuthorTable.RealName FROM $strArticleTable LEFT JOIN $strAuthorTable ON ($strArticleTable.Author = $strAuthorTable.UserKey) WHERE Active <> 0 AND ActiveDate < NOW() ORDER BY $strArticleTable.ActiveDate Featured SELECT $strArticleTable.ArticleKey, $strArticleTable.ArticleTitle, Articles $strArticleTable.Description FROM $strArticleTable RIGHT JOIN $strFeatureTable ON ($strArticleTable.ArticleKey=$strFeatureTable.ArticleK ey) WHERE Active <> 0 AND ActiveDate < NOW() ORDER BY $strFeatureTable.ArticleSort The Featured Articles page uses a second dynamic section to highlight a single article. In this case, as the results are returned the first value is assigned to the highlight section. This is achieved by the simple process of assigning a Boolean value before the loop and directly after the first process. Figure 16.3 shows the result.

    Figure 16.3 : The Featured Articles page $boolFirst = true;

    while ($arrArticles = mysql_fetch_array($objResult, MYSQL_ASSOC)) { if ($boolFirst) { $strArticle = $objTemplate->m_AssignContent("ARTICLE_KEY", $arrArticles['ArticleKey'], $strFirstSection); $strArticle = $objTemplate->m_AssignContent("ARTICLE_NAME", $arrArticles['ArticleTitle'], $strArticle); $strArticle = $objTemplate->m_AssignContent("DESCRIPTION", $arrArticles['Description'], $strArticle); $objTemplate->m_AssignSection("FirstArticle", $strArticle); $boolFirst = false; } else { …

    Article Page

    The Article page is more complicated than you might think. Data for this page comes from numerous tables and includes sections to navigate through the pages of the article and display any user feedback. The Article template page includes dynamic sections for the article page sections, the images, the previous and next page navigation, the feedback section, and any individual posted comment. All together, the sections make for a comprehensive yet flexible display system.

    {{ARTICLE_NAME}}
    by {{AUTHOR}}

    {{PAGE_TITLE}}

    {{section Sections }}

    {{section Image }}{{/section Image }} {{section SectionHeading }}{{HEADING}}
    {{/section SectionHeading }} {{CONTENT}}

    {{/section Sections }}

    {{section Left }}<— {{PREV_PAGE_TITLE} } {{/section Left }}   {{section Right }}{{NEXT_PAGE_TITLE}} —>{{/section Right }}


    User Comments: {{section Comment }}
    {{COMMENT}}
    —— posted by {{MEMBER}} on {{COMMENT_DATE}} {{section EditPost }}   Edit Post{{/section EditPost }}
    {{/section Comment }} The article.php object file handles populating these dynamic sections. This file uses the query string variable $Page to determine the correct page of the article to display. If no value is supplied, the first page in the sort order is used. After the basic article information has been added to the page, the code gathers the information for all the page sections assigned to the current page. The resulting values are then assigned to the dynamic sections. In the same way, the images are assigned to each page section. $strPageSectionSQL = "SELECT $strPageTable.PageTitle, $strPageTable.PageSort, $strSectionTable.Heading, $strSectionTable.ShowHeading, $strSectionTable.SectionKey, $strSectionTable.SectionContent FROM $strPageTable INNER JOIN $strSectionTable ON ($strPageTable.PageKey = $strSectionTable.PageKey) WHERE $strPageTable.PageKey = $Page ORDER BY $strSectionTable.SectionSort"; $objPageSectionResult = mysql_query($strPageSectionSQL,$CONNECTION) or die("Couldn't select section information."); while ($arrSection = mysql_fetch_array($objPageSectionResult, MYSQL_ASSOC)) { $objTemplate->m_AssignContent("PAGE_TITLE", $arrSection['PageTitle']); $intPageSort = $arrSection['PageSort']; $strSection = $objTemplate->m_AssignContent("CONTENT", $arrSection['SectionContent'], $strSecSection); $strSectionHead = $objTemplate->m_AssignContent("HEADING", $arrSection['Heading'], $strSecHeadSection); $strSection = $objTemplate->m_AssignSection("SectionHeading", $strSectionHead, $strSection); $strImageSQL = "SELECT * FROM $strImageTable INNER JOIN $strImageAssignTable ON $strImageTable.ImageKey=$strImageAssignTable.ImageKey WHERE ItemKey = " .$arrSection['SectionKey'] . " ORDER BY ItemKey"; … To display the next and previous page links, you must make another database query. This time you are returning the sort value rather than just ordering the results. Once the returned values have been assigned to an array, it is easy to check whether a page has been assigned a sort order just before or just after the current page. The corresponding information can then be assigned to the appropriate section. $intPageCounter = 0; while ($arrLinks = mysql_fetch_array($objLinksResult, MYSQL_ASSOC)) { $arrPages[$intPageCounter]['PageKey'] = $arrLinks['PageKey']; $arrPages[$intPageCounter]['PageSort'] = $arrLinks['PageSort']; $arrPages[$intPageCounter]['PageTitle'] = $arrLinks['PageTitle']; $intPageCounter++; }; mysql_free_result($objLinksResult); foreach ($arrPages AS $Page) { if ($Page['PageSort'] == $intPageSort - 1) { $strLink = $objTemplate->m_AssignContent("PREV_PAGE_TITLE", $Page['PageTitle'], $strLeftSection); $strLink = $objTemplate->m_AssignContent("PAGE_KEY", $Page['PageKey'], $strLink); $strLink = $objTemplate->m_AssignContent("ARTICLE_KEY", $ID, $strLink); $objTemplate->m_AssignSection("Left", $strLink); } else if ($Page['PageSort'] == $intPageSort + 1) { … Creating the feedback sections requires the use of the o_AccessPublic class. You don’t want to limit access to this page, but you do want to limit the display of the edit feedback links to the responsible member. This can be accomplished using the m_isMember() method. Simply pass the member key attached to each feedback item and examine the results to determine whether to display the edit link. The resulting feedback sections can be seen in Figure 16.4.

    Figure 16.4 : The Article page with feedback $strFeedBackSQL = "SELECT FeedbackKey, DATE_FORMAT(LastModDate, \"%m\%d\%Y\") AS Date, MemberKey, FeedBackContent, $strMemberTable.MemberName FROM $strFeedBackTable INNER JOIN $strMemberTable ON ($strFeedBackTable.MemberKey = $strMemberTable.MemberID) WHERE ArticleKey = $ID AND Active <> 0 ORDER BY LastModDate DESC"; $objFeedBackResult = mysql_query($strFeedBackSQL,$CONNECTION) or die("Couldn't select feedback information."); $objRestrict = new o_AccessPublic;

    while ($arrFeedback = mysql_fetch_array($objFeedBackResult, MYSQL_ASSOC)) { $strFeedBack = $objTemplate->m_AssignContent("COMMENT", $arrFeedback['FeedBackContent'], $strCommentSection); $strFeedBack = $objTemplate->m_AssignContent("MEMBER", $arrFeedback['MemberName'], $strFeedBack); $strFeedBack = $objTemplate->m_AssignContent("COMMENT_DATE", $arrFeedback['Date'], $strFeedBack);

    if ($objRestrict->m_isMember($arrFeedback['MemberKey'])) { $strPost = $objTemplate->m_AssignContent("FEEDBACK_KEY", $arrFeedback['FeedbackKey'], $strEditPostSection); $strFeedBack = $objTemplate->m_AssignSection("EditPost", $strPost, $strFeedBack); };

    $objTemplate->m_AssignSection("Comment", $strFeedBack); }; Article Search The Article Search page is the first that combines the functionality of an object page with the processing of an action page. In other words, the search page both submits the form data and processes the data. In this case, the same page also displays the results. Up until now, the action pages have passed the output data to a separate page, but the demands of a fulltext search require a different process. Most action pages perform some type of action on a table, whether it is an INSERT, UPDATE, or DELETE. Search pages, however, rarely perform more than read operations, such as with the SELECT command.

    Previously the action pages would pass a single short value in a query string or perhaps two or three small values. The results of a search are far too large to fit in a query string, however. You could pass the data using cookies or session variables, but the added overhead would make that process counterproductive. In the end, the only real choice is to muddy the previously clear distinction between action pages and form pages by combining the functionality into a single file.

    Another first for the Article Search page is the use of a fulltext index. If you haven’t created a fulltext index for the SectionContent field of the PageSection table you will need to do so before the Article Search page will function. The first of two queries on the page searches the PageSection table for results matching the search string. If the returned value is an empty data set, the NoResults section is populated. $strSearchSQL = "SELECT DISTINCT(PageKey) FROM $strSectionTable WHERE MATCH (SectionContent) AGAINST (\"$formSearch\")"; $objSearchResult = mysql_query($strSearchSQL,$CONNECTION) or die("Couldn't perform fulltext search."); if (mysql_num_rows($objSearchResult) == 0) { $strArticle = $objTemplate->m_AssignContent("RESULT", mysql_num_rows($objSearchResult), $strNoResultsSection); $objTemplate->m_AssignSection("NoResults", $strArticle); } else { … Once it has been determined that a page has been found, another query returns the article and page information and it is placed into the dynamic marker tags. Before this can happen, though, you need to limit the search to a specific maximum number of results per page. This value is multiplied by the query string variable $Page to determine the starting value and, if the maximum number is greater than the total results, the maximum number is added to the beginning value to determine the stop value. $intMaxResults = 10; if (isset($Page)) { $intBegin = $intMaxResults * $Page; } else { $intBegin = 0; };

    if ($intMaxResults > mysql_num_rows($objSearchResult)) { $intEnd = mysql_num_rows($objSearchResult); } else { $intEnd = $intBegin + $intMaxResults; }

    for ($l = $intBegin; $l < $intEnd; $l++) { $strArticleSQL = "SELECT $strArticleTable.ArticleKey, $strArticleTable.Author, $strAuthorTable.RealName, $strSectionTable.SectionContent, $strPageTable.PageTitle FROM $strArticleTable LEFT JOIN $strAuthorTable ON ($strArticleTable.Author = $strAuthorTable.UserKey) LEFT JOIN $strPageTable ON ($strPageTable.ArticleKey = $strArticleTable.ArticleKey) LEFT JOIN $strSectionTable ON ($strPageTable.PageKey = $strSectionTable.PageKey) WHERE Active <> 0 AND ActiveDate < NOW() AND $strSectionTable.PageKey = " . mysql_result($objSearchResult, $l, "PageKey"); … Once the results content has been displayed, you can display the next and previous links. As with the article page, these are contained within dynamic section tags. This ensures that they only appear when the results exceed the maximum value for the page. Figure 16.5 shows the completed Search Results page.

    Figure 16.5 : The Search Results page if (mysql_num_rows($objSearchResult) > $intEnd) { $strNext = $objTemplate->m_AssignContent("NEXT_PAGE", $Page + 1, $strNextSection); $strNext = $objTemplate->m_AssignContent("MAX", $intMaxResults, $strNext); $strNext = $objTemplate->m_AssignContent("SEARCH", urlencode($formSearch), $strNext); $objTemplate->m_AssignSection("Next", $strNext); }; if ($Page > 0) { $strNext = $objTemplate->m_AssignContent("NEXT_PAGE", $Page - 1, $strPrevSection); $strNext = $objTemplate->m_AssignContent("MAX", $intMaxResults, $strNext); $strNext = $objTemplate->m_AssignContent("SEARCH", urlencode($formSearch), $strNext); $objTemplate->m_AssignSection("Prev", $strNext); };

    The following is an example of how the next and previous links sections can be created in the template file.

    {{section Prev }}<— Last {{MAX}} Results{{/section Prev }}   {{section Next }}Next {{MAX}} Results —>{{/section Next }}

    Feedback Pages

    The feedback pages provide a way for your visitors to contribute to the quality of your interface by adding their own comments and opinions—their feedback. Visitor contributions to an information system are nothing new on the Web. Many sites have used user contributions to great benefit. Often Web site managers, while they appreciate the benefits of creating a “community” of returning visitors, are reluctant to allow the general public to post comments on the site, even in a limited way such as the feedback section.

    Some site administrators can only see the possibility of a backlash of flame posts by unhappy customers, the massive effort needed to moderate the system, or the ten-year- old Cartmans who insist on trying to post profanity or other disturbing material. But most sites with user contribution systems never see these problems in any serious form. The fact is, giving customers a way to complain in an organized manner can lead to product improvements—and a complaining customer is one who is still involved with the product enough to want to give an opinion, which means there’s a chance to turn that customer into a strong supporter. Most product-based information sites can expect the majority of comments to be positive or neutral. Even if the majority of comments are negative, this is still the type of market research for which companies pay thousands of dollars.

    The requirements of moderating the commenting system are often overstated. Only the most popular online communities get enough posts to require a dedicated moderation staff. Even several hundred posts a week can be reviewed in a few hours by a single person. Most sites would be lucky to get that many. Even really large communities can be handled easily by allowing them to self-moderate. Just offer to give an individual with an active history of responsible posts access to the moderation controls. Most are more than happy to officially become part of the team.

    Finally, there is the worry that all the little $%!&ers out there will $%!&@^* ruin the whole @^# &^%* thing by posting nonstop profanity. Or maybe someone will post Hitler’s mother’s dress size or some other equally disastrous content. Truthfully, you can expect that to happen. It will, eventually. So what can you do about it? If you see it, take it down. If someone complains, take it down. There are any number of community-creation products, both commercial and free, that attempt to filter out profanity or insensitive content, and you could use one of these systems. The reality is, though, very few users will post such content and even fewer will hold a company responsible for the comments that show up in an obvious user forum. Note If you’re looking for more information about building user- contributed online communities, pick up a copy of Philip Greenspun’s Philip and Alex’s Guide to Web Publishing. Don’t let the name or the full-color travel photos inside fool you. This is one of the best books on the market dealing with the principles of creating online communities.

    Submit/Edit Feedback The files that allow the user to submit feedback aren’t much different from any other Web form. The Submission page looks for two variables in the query string. If the $ID variable is encountered the post is considered an update of an existing feedback item. If the value of $Article has been passed, the code knows that the post is a new comment assigned to the identified article. Figure 16.6 shows the Feedback page being used to add a new comment.

    Figure 16.6 : Submitting a comment One important element of the feedback system to include is the processing of the users’ comments to avoid potential abuse of the Web site. Without processing it is possible for a user to post SQL, JavaScript, or HTML code that could disrupt the systems. To prevent this you can use the strip_tags() and addslashes() functions together. The result is post data that has any dangerous characters removed or escaped. As you learned in Chapter 2, all form input by the general public should be processed in this manner. Up until now this processing has been left out for simplicity’s sake, but it becomes all the more important when large blocks of text such as the comment field are used. $strSQL = "INSERT INTO $strTable (MemberKey, FeedbackContent, LastModDate, Active, ArticleKey) VALUES (" . $GLOBALS['MemberID'] . ", \"" . strip_tags(addslashes($formComments)) . "\", NOW(), 1, $formArticleKey)"; Update Feedback The Update Feedback page is another of those pages, similar to the Article Search page, where the action and form pages are combined. Complicating the situation is that the results of the search are also part of the form. The Update Feedback page returns the search results as well as the current active state in the form of a radio button. To deactivate a post the user simply checks the inactive button and submits the form. Figure 16.7 shows the results of a search on the Update Feedback page.

    Figure 16.7 : Deactivating a comment

    Each search result produces another instance of the comments section. This section contains the marker tags allowing the user to locate the offending post. The radio button group name is set in the array format with brackets. This allows you to use the feedback key value for each result as part of the radio button name. {{section Comments }}

    Active | Inactive
                  Member: {{MEMBER}}
    {{COMMENT}}
    Last Updated on: {{COMMENT_DATE}}
    {{/section Comments }}

    Processing the deactivations and reactivations is as simple as looping through the posted form values and extracting the array index and the corresponding value (0 for inactive, 1 for active). else if ((isset($formDel)) && (isset($formDeactive))) { foreach ($formDeactive AS $CommentKey=>$Value) { $strUpdateSQL = "UPDATE $strFeedBackTable SET Active=$Value WHERE FeedBackKey = $CommentKey"; $objUpdateResult = mysql_query($strUpdateSQL,$CONNECTION) or die("Couldn't update feedback"); };

    }; More interesting than the deactivation processing is the technique used to generate the search results in the first place. A switch command is used to compare the value of the $formType value sent by the search option buttons. Each value produces a different SQL query, but the results are handled the same. If the user has chosen to search by date, then the search value is validated for format. switch ($formSearchType) { case "Name": $strSearchSQL = "SELECT FeedbackKey, Active, DATE_FORMAT(LastModDate, \"%m\%d\%Y\") AS Date, MemberKey, FeedBackContent, $strMemberTable.MemberName FROM $strFeedBackTable INNER JOIN $strMemberTable ON ($strFeedBackTable.MemberKey = $strMemberTable.MemberID) WHERE MemberName LIKE \"%$formSearch%\" ORDER BY MemberName"; break;

    case "ID": $strSearchSQL = "SELECT FeedbackKey, Active, DATE_FORMAT(LastModDate, \"%m\%d\%Y\") AS Date, MemberKey, FeedBackContent, $strMemberTable.MemberName FROM $strFeedBackTable INNER JOIN $strMemberTable ON ($strFeedBackTable.MemberKey = $strMemberTable.MemberID) WHERE MemberKey = \"$formSearch\" ORDER BY MemberID"; break;

    case "Date": $strSearchSQL = "SELECT FeedbackKey, Active, DATE_FORMAT(LastModDate, \"%m\%d\%Y\") AS Date, MemberKey, FeedBackContent, $strMemberTable.MemberName FROM $strFeedBackTable INNER JOIN $strMemberTable ON ($strFeedBackTable.MemberKey = $strMemberTable.MemberID) WHERE (UNIX_TIMESTAMP(LastModDate) - " . strtotime($formSearch) . " < 86400) AND UNIX_TIMESTAMP(LastModDate) > " . strtotime($formSearch) . " ORDER BY LastModDate"; $strDateError = "The active date must be in the format 12/31/2002."; $objValidate = new o_Validate; $objValidate->m_isFormatDate($formSearchType,"mm/dd/yyyy","/",$strDateError); $strErrors = $objValidate->m_Alert("
    ");

    if ($strErrors != "") { header("Location: feedbackupdate.php"); exit(); };

    break;

    case "Text": $strSearchSQL = "SELECT FeedbackKey, Active, DATE_FORMAT(LastModDate, \"%m\%d\%Y\") AS Date, MemberKey, FeedBackContent, $strMemberTable.MemberName FROM $strFeedBackTable INNER JOIN $strMemberTable ON ($strFeedBackTable.MemberKey = $strMemberTable.MemberID) WHERE MATCH (FeedbackContent) AGAINST (\"$formSearch\")"; break; };

    $objSearchResult = mysql_query($strSearchSQL,$CONNECTION) or die("Couldn't perform search.");

    Article Management Pages

    By far, the most complicated pages in this interface are the article management pages. The bulk of the complexity comes from the update pages. Both the update section and Update Article page introduce new features and difficulties. When the pages are used together, however, the intricacy and interdependence of the article management system is multiplied. Because these pages rely so heavily on JavaScript, I don’t recommend trying to create a solution that accounts for non-JavaScript-enabled browsers while maintaining the JavaScript functionality. In this case, it’s one or the other. Note If you truly require the combination of JavaScript functionality and backward support I suggest you create an entirely different set of parallel files for each user group. A simple cookie or session variable could be set for JavaScript-enabled users that would redirect them to the pages with added functionality.

    Update Article The article update process consists of three primary pages. The template file contains links to some very complicated JavaScript procedures. The object file performs a number of functions, including locking and restricting each article record. Finally, the action page must process information in varied formats on a number of different database tables. Figure 16.8 shows the completed page.

    Figure 16.8 : The Update Article page One of the recurring themes in the Update Article files is the use of delimited strings to represent a sequence of values. You’ve seen this before with the comma-delimited strings of the f_outputSelectBox() function, but the update article pages contain more complex uses.

    For example, one JavaScript function outputs a series of page key–page title pairs. Each pair is separated by a tilde (~), while the pairs themselves are split by a bar (|). These characters were chosen because they were unlikely to appear in a page titles. The resulting string might look like this: 4|This is a page title. Yippie!~7|Another page title, yes!~22|OK, still more page titles …

    Using delimited strings in this way, it is possible to pass array -like information from the client to the server. Without this ability, the functionality of the form decreases significantly. Update Article Page The Update Article page (updatearticle.php) receives a single variable in the query string from the Article Selection page—$formArticleKey. From this value and the user key stored in the UserID cookie, the object page can determine whether the article is currently locked. In Chapter 15 you saw how this can be accomplished using the m_isLocked() method of the o_Access class. If the article exists and the user hasn’t been locked out, the article item is locked. If the article key is not passed, then the page assumes the user is creating a new article. if (isset($formArticleKey)) { $objRestrict->m_isLocked($formArticleKey, "ArticleKey", $strArticleTable); };

    if ($objRestrict->p_strAccessDenied) { session_register("sessErrors"); $GLOBALS["sessErrors"] = $objRestrict->p_strAccessDenied; header("Location: secure.php?Page=home"); exit(); } else { if (isset($formArticleKey)) { $strLockSQL = "UPDATE $strArticleTable SET LockedBy = " . $GLOBALS["UserID"] . ", LockedTime = NOW() WHERE ArticleKey = $formArticleKey"; $objLockResult = mysql_query($strLockSQL,$CONNECTION) or die("Couldn't lock table."); }; …

    After the article has been locked, a series of three queries pull the article information from the database. The first query gathers the general article information from the Article table. $strArticleSQL = "SELECT * FROM $strArticleTable WHERE ArticleKey = $formArticleKey";

    The next query selects the author list to populate the author select box. $strAuthorSQL = "SELECT RealName, UserKey FROM $strAuthorTable ORDER BY RealName";

    The final query pulls the article page and page section information. $strPageSQL = "SELECT PageTitle, $strPageTable.PageKey, $strSectionTable.SectionKey, $strSectionTable.Heading FROM $strPageTable LEFT JOIN $strSectionTable ON $strPageTable.PageKey = $strSectionTable.PageKey WHERE $strPageTable.ArticleKey = $formArticleKey ORDER BY $strPageTable.PageSort, $strSectionTable.SectionSort";

    Together with a few arrays and the form memory mechanism, this completes the object page for the article update process. The only other code block of note is the section that generates a series of comma-delimited section keys from each page of the article. These values will be essential to the JavaScript functionality of the completed page. foreach ($arrSection['PageKey'] AS $Index=>$PageSecKey) { if ($PageSecKey == $PageKey) { $strSectionVal = $strSectionVal . $arrSection['SectionKey'][$Index] . ","; };

    }; if ($strSectionVal != "") { $strSectionVal = substr($strSectionVal, 0, -1); };

    $strPage = $objTemplate->m_AssignContent("SECTION_KEYS", $strSectionVal, $strPage);

    Here is the complete articleupdate.php file: articleupdate.php

    $objRestrict = new o_Access; session_start(); $strArticleTable = "Article"; $strAuthorTable = "AuthUsers"; $strPageTable = "ArticlePage"; $strSectionTable = "PageSection"; if (isset($formArticleKey)) { $objRestrict->m_isLocked($formArticleKey, "ArticleKey", $strArticleTable); }; if ($objRestrict->p_strAccessDenied) { session_register("sessErrors"); $GLOBALS["sessErrors"] = $objRestrict->p_strAccessDenied; header("Location: secure.php?Page=home"); exit(); } else { if (isset($formArticleKey)) { $strLockSQL = "UPDATE $strArticleTable SET LockedBy = " . $GLOBALS["UserID"] . ", LockedTime = NOW() WHERE ArticleKey = $formArticleKey"; $objLockResult = mysql_query($strLockSQL,$CONNECTION) or die("Couldn't lock table."); };

    $strFileName = "templates/articleupdate.tpl"; $objTemplate = new o_Template; $objTemplate->m_LoadFile($strFileName); $strAuthorSection = $objTemplate->m_DefineSection("Authors"); $strPageSection = $objTemplate->m_DefineSection("Pages"); $strSecSection = $objTemplate->m_DefineSection("Sections"); $strDeletePageSection = $objTemplate->m_DefineSection("DeletePage"); $strDeleteSecSection = $objTemplate->m_DefineSection("DeleteSection");

    if ($sessErrors) { $objTemplate->m_AssignContent("ERRORS", $sessErrors); } else { $objTemplate->m_AssignContent("ERRORS", ""); };

    session_unregister("sessErrors"); $objHelp = new o_Help; $objTemplate->m_AssignContent("HELP", $objHelp->p_strOutput);

    if ($objRestrict->p_intAccess > 0) { $strDeletePage = $objTemplate->m_AssignContent("DELETEPAGE", "formPageDel", $strDeletePageSection); $objTemplate->m_AssignSection("DeletePage", $strDeletePage); $strDeleteSec = $objTemplate->m_AssignContent("DELETESECTION", "formSecDel", $strDeleteSecSection); $objTemplate->m_AssignS ection("DeleteSection", $strDeleteSec); };

    if (isset($formArticleKey)) { $objTemplate->m_AddElements("Update Article Information"); $objTemplate->m_AssignContent("ARTICLE_KEY", $formArticleKey); $objTemplate->m_AssignContent("PROCESS", "Update");

    if (isset($sessMultiform)) { $objTemplate->m_AssignContent("ARTICLE_TITLE", $HTTP_SESSION_VARS["formTitle"]); $objTemplate->m_AssignContent("ACTIVE_DATE", $HTTP_SESSION_VARS["formActiveDate"]); $objTemplate->m_AssignContent("ARTICLE_DESCRIPTION", $HTTP_SESSION_VARS["formDescription"]);

    if ($HTTP_SESSION_VARS["formActive"]) { $objTemplate->m_AssignContent("ACTIVE", "CHECKED"); } else { $objTemplate->m_AssignContent("ACTIVE", ""); };

    session_unregister("formTitle"); session_unregister("formDescription"); session_unregister("formActiveDate"); session_unregister("formActive"); session_unregister("formReset"); session_unregister("formSubmit"); session_unregister("formPageSections"); session_unregister("formDeletedPages"); session_unregister("formPageTitles"); session_unregister("formSections"); session_unregister("formArticleKey"); session_unregister("formPages"); session_unregister("sessMultiform"); } else { $strArticleSQL = "SELECT * FROM $strArticleTable WHERE ArticleKey = $formArticleKey"; $objArticleResult = mysql_query($strArticleSQL,$CONNECTION) or die("Couldn't select article information.");

    $arrArticle = mysql_fetch_array($objArticleResult, MYSQL_ASSOC); $objTemplate->m_AssignContent("ARTICLE_TITLE", $arrArticle['ArticleTitle']); $objTemplate->m_AssignContent("ACTIVE_DATE", $arrArticle['ActiveDate']); $objTemplate->m_AssignContent("ARTICLE_DESCRIPTION", $arrArticle['Description']);

    if ($arrArticle['Active']) { $objTemplate->m_AssignContent("ACTIVE", "CHECKED"); } else { $objTemplate->m_AssignContent("ACTIVE", ""); };

    mysql_free_result($objArticleResult); };

    $strAuthorSQL = "SELECT RealName, UserKey FROM $strAuthorTable ORDER BY RealName"; $objAuthorResult = mysql_query($strAuthorSQL,$CONNECTION) or die("Couldn't select author information.");

    while ($arrAuthors = mysql_fetch_array($objAuthorResult, MYSQL_ASSOC)) { $strAuthor = $objTemplate->m_AssignContent("AUTHOR_KEY", $arrAuthors['UserKey'], $strAuthorSection); $strAuthor = $objTemplate->m_AssignContent("AUTHOR_NAME", $arrAuthors['RealName'], $strAuthor);

    if ($arrAuthors['UserKey'] == $arrArticle['Author']) { $strAuthor = $objTemplate->m_AssignContent("AUTHOR_SELECTED", "SELECTED", $strAuthor); } else { $strAuthor = $objTemplate->m_AssignContent("AUTHOR_SELECTED", "", $strAuthor); };

    $objTemplate->m_AssignSection("Authors", $strAuthor); };

    mysql_free_result($objAuthorResult); $strPageSQL = "SELECT PageTitle, $strPageTable.PageKey, $strSectionTable.SectionKey, $strSectionTable.Heading FROM $strPageTable LEFT JOIN $strSectionTable ON $strPageTable.PageKey = $strSectionTable.PageKey WHERE $strPageTable.ArticleKey = $formArticleKey ORDER BY $strPageTable.PageSort, $strSectionTable.SectionSort"; $objPageResult = mysql_query($strPageSQL,$CONNECTION) or die("Couldn't select page and section information."); $intPageCounter = 0;

    while ($arrPageResult = mysql_fetch_array($objPageResult, MYSQL_ASSOC)) { $arrPages[$arrPageResult['PageKey']] = $arrPageResult['PageTitle']; $arrSection['PageKey'][$intPageCounter] = $arrPageResult['PageKey']; $arrSection['SectionKey'][$intPageCounter] = $arrPageResult['SectionKey']; $arrSection['Heading'][$intPageCounter] = $arrPageResult['Heading']; $intPageCounter++; };

    mysql_free_result($objPageResult);

    for ($l = 0; $l < count($arrSection['PageKey']); $l++) { if ($arrSection['Heading'][$l] != "") { $strSection = $objTemplate->m_AssignContent("SECTION_KEY", $arrSection['SectionKey'][$l], $strSecSection); $strSection = $objTemplate->m_AssignContent("SECTION_NAME", $arrSection['Heading'][$l], $strSection); $objTemplate->m_AssignSection("Sections", $strSection); };

    };

    if ($arrPages) { foreach ($arrPages AS $PageKey=>$PageName) { if ($PageName != "") { $strPage = $objTemplate->m_AssignContent("PAGE_KEY", $PageKey, $strPageSection); $strPage = $objTemplate->m_AssignContent("PAGE_NAME", substr($PageName,0,35), $strPage);

    $strSectionVal = "";

    foreach ($arrSection['PageKey'] AS $Index=>$PageSecKey) { if ($PageSecKey == $PageKey) { $strSectionVal = $strSectionVal . $arrSection['SectionKey'][$Index] . ","; };

    };

    if ($strSectionVal != "") { $strSectionVal = substr($strSectionVal, 0, -1); };

    $strPage = $objTemplate->m_AssignContent("SECTION_KEYS", $strSectionVal, $strPage); $objTemplate->m_AssignSection("Pages", $strPage); };

    };

    }; } else { $objTemplate->m_AddElements("Add a New Article"); $objTemplate->m_AssignContent("ARTICLE_KEY", "-1"); $objTemplate->m_AssignContent("PROCESS", "Add a New "); $objTemplate->m_AssignContent("ARTICLE_TITLE", $HTTP_SESSION_VARS['formTitle']); $objTemplate->m_AssignContent("ACTIVE_DATE", $HTTP_SESSION_VARS['formActiveDate']); $objTemplate->m_AssignContent("ARTICLE_DESCRIPTION", $HTTP_SESSION_VARS['formDescription']);

    if ($HTTP_SESSION_VARS["formActive"]) { $objTemplate->m_AssignContent("ACTIVE", "CHECKED"); } else { $objTemplate->m_AssignContent("ACTIVE", ""); }; session_unregister("formTitle"); session_unregister("formDescription"); session_unregister("formActiveDate"); session_unregister("formActive"); session_unregister("formReset"); session_unregister("formSubmit"); session_unregister("formPageSections"); session_unregister("formDeletedPages"); session_unregister("formPageTitles"); session_unregister("formSections"); session_unregister("formPages"); session_unregister("formArticleKey"); session_unregister("sessMultiform"); $strAuthorSQL = "SELECT RealName, UserKey FROM $strAuthorTable ORDER BY RealName"; $objAuthorResult = mysql_query($strAuthorSQL,$CONNECTION) or die("Couldn't select author information.");

    while ($arrAuthors = mysql_fetch_array($objAuthorResult, MYSQL_ASSOC)) { $strAuthor = $objTemplate->m_AssignContent("AUTHOR_KEY", $arrAuthors['UserKey'], $strAuthorSection); $strAuthor = $objTemplate->m_AssignContent("AUTHOR_NAME", $arrAuthors['RealName'], $strAuthor); $strAuthor = $objTemplate->m_AssignContent("AUTHOR_SELECTED", "", $strAuthor); $objTemplate->m_AssignSection("Authors", $strAuthor); };

    mysql_free_result($objAuthorResult);

    };

    $objTemplate->m_PrepTemplate(); $objTemplate->m_Output(); }; ?> Update Article Template Page

    The template page for the article update system (updatearticle.tpl) is very simple in itself. The JavaScript functions that it references are not, however. At the beginning of the template file two global variables are set. The first is the core variable for all the JavaScript functions on the page. The second is an index value that will allow your script to keep track of the number of new pages that have been created. You saw in the help pages how dynamic sections could be used to generate a JavaScript array. The Article Update page uses a different tactic. This page will generate the array itself based on the values and text of the page section select box. The onLoad event of the page is assigned to call a function named f_loadSections(). This function reads each option of the select box, creates a new option object for the array, and removes the option from the select box. The result is an empty page section select box, accompanied by a very useful ordered list of all the sections for the article. The fact that the list is properly ordered is critical to the later functionality. function f_loadSections(objDest) { for (l = objDest.length - 1; l >= 0; l—) { if (boolIE) { arrOptions[l] = new Option; arrOptions[l].value = objDest.options[l].value; arrOptions[l].text = objDest.options[l].text; objDest.remove(l); } else if (boolNN) { arrOptions[l] = new Option(objDest.options[l].text, objDest.options[l].value); objDest.options[l] = null; } else if (boolN6) { arrOptions[l] = document.createElement("OPTION"); arrOptions[l].value = objDest.options[l].value; arrOptions[l].text = objDest.options[l].text; objDest.remove(l); } } } Once the page has been prepared, the next function that the user is likely to encounter is the f_updateSections_x() function. This process is called from the onChange event of the article page select box. The key to this function is the values assigned to the article page options. Rather than just page keys, the values contain a delimiting character followed by a sequence of numbers representing the section keys of the page keys assigned to each section. For example, if page 9 had three sections numbered 12, 6, and 34, the value of the option for this page would be 9|12,6,34. Here is the dynamic section that will generate these values: {{section Pages }} {{/section Pages }} The f_updateSections_x() function generates the list of the page section options for the appropriate select box. The first line of the function calls the f_clearSection_x() function. I’ll cover this in detail in a few moments, but the important thing to know right now is that this function removes any existing options from the select box. After the options have been cleared, the page and section keys are assigned to an integer and array respectively. The split() function is used to generate an array from the section keys. function f_updateSections_x(objElement, objDest) { f_clearSection_x(objDest); var strSections = objElement.options[objElement.selectedIndex].value; var intDel = strSections.indexOf('|'); strSections = strSections.substring(intDel + 1, strSections.length); var arrSections = strSections.split(","); …

    The function then goes through two nested loops. Th e outer loop is the options array that was generated when the page loaded. The inner loop is the array that was just created from the section keys in the selected option value. Using two loops allows you to search one array for a matching value in another, but the sequence is critical. If you loop first through the values obtained from the selected option you will still produce the same apparent effect, but you will be unable to reorder the sections, as the section key sequence in the option value never changes. For example, if you had clicked on that page option example and the order of the loops was wrong, then section number 12 would always appear before section 6, no matter what order you assigned them using the move option buttons. Once a matching value has been found, an option is created in the page section select box. for (j = 0; j < arrOptions.length; j++) { for (l = 0; l < arrSections.length; l++) { if (arrSections[l] == arrOptions[j].value) { if (boolIE) { … The code to add the option for Netscape users is worth noting. With the other browser you can assign the option directly by attaching the options created when the page loaded to the select box. This creates a symbolic link between the array options and the real ones in the select box. When one is moved up or down in the order, the other is as well. This is a big help for keeping the sections in the proper order, but don’t try it in Netscape 4. It seems Netscape 4 takes the link a little far, and when you remove an option from the select box, as with the f_clearSection_x() function, it removes the option from the array as well. As a result, you have to generate a new option based on the text and value properties of the matching array value. objDest.options[objDest.length] = new Option(arrOptions[j].text, arrOptions[j].value);

    Here’s how the complete function should look: function f_updateSections_x(objElement, objDest) { f_clearSection_x(objDest); var strSections = objElement.options[objElement.selectedIndex].value; var intDel = strSections.indexOf('|'); strSections = strSections.substring(intDel + 1, strSections.length); var arrSections = strSections.split(",");

    for (j = 0; j < arrOptions.length; j++) { for (l = 0; l < arrSections.length; l++) { if (arrSections[l] == arrOptions[j].value) { if (boolIE) { objDest.add(arrOptions[j]); } else if (boolNN) { objDest.options[objDest.length] = new Option(arrOptions[j].text, arrOptions[j].value); } else if (boolN6) { objDest.add(arrOptions[j], null); } } } } } The f_clearSection_x() function would be very simple if it wasn’t for the workaround required to get the same functionality for Netscape 4. Basically, this function locates each option in the select box and removes it. But for the system to work in older Netscape browsers each option must be compared to the elements in the options array. else if (boolNN) { for (k = 0; k < arrOptions.length; k++) { if (objElement.options[h].value == arrOptions[k].value) { var arrNewOptionSet = arrOptions.splice(k,1); arrOptions.unshift(arrNewOptionSet[0]); } }

    objElement.options[h] = null; } If a match is encountered, the array item is removed from the array with the splice() function and placed at the front of the array list with the unshift() function. By removing and prepending each option as it is encountered, you can ensure that the order of options in the select box matches the order of the options array. Here is the complete f_clearSection_x() function: function f_clearSection_x(objElement) { if (objElement.length > 0) { for (h = objElement.length - 1; h >= 0; h—) { if ((boolN6) || (boolIE)) { objElement.remove(h); } else if (boolNN) { for (k = 0; k < arrOptions.length; k++) { if (objElement.options[h].value == arrOptions[k].value) { var arrNewOptionSet = arrOptions.splice(k,1); arrOptions.unshift(arrNewOptionSet[0]); } }

    objElement.options[h] = null; } } } } The functions to rename, add, or delete a page use the built-in modal dialog boxes to gather user input rather than going to a separate page. The rename function takes the input from a prompt box and assigns it to the text of the selected option. The add function also uses a prompt box, but instead it creates a new option with the value of the new pages counter and adds a bar (|) to keep the format consistent. The delete function removes the option from the page select box and also assigns the value of the page to a hidden field. This will be used by the action page to delete these pages from the database.

    Here are the rename, add, and delete page functions. Like the others, these were intended to be run from the i_info.jss file, but you can place them anywhere that is convenient. function f_doRenamePage_x(objElement) { var strQuestion = "Type the new name for the page and press \"OK\". To keep the old page title, press \"Canc el\"";

    if (objElement.selectedIndex >= 0) { var strNewTitle = prompt(strQuestion, objElement.options[objElement.selectedIndex].text); objElement.options[objElement.selectedIndex].text = strNewTitle; } else { alert("Please select a page to edit."); } } function f_doAddPage_x(objElement) { var strPrompt = "Type the name for the new page and press \"OK\"."; var strNewTitle = prompt(strPrompt, "");

    if ((strNewTitle != "") || (strNewTitle != null)) { if (boolIE) { var objNewPage = new Option(); objNewPage.value = intNewPages + "|"; objNewPage.text = strNewTitle; objElement.add(objNewPage); } else if (boolNN) { objElement.options[objElement.length] = new Option(strNewTitle, intNewPages + "|"); } else if (boolN6) { var objNewPage = document.createElement("OPTION"); objNewPage.value = intNewPages + "|"; objNewPage.text = strNewTitle; objElement.add(objNewPage, null); }

    intNewPages—; } } function f_doDelPage_x(objElement, objDest) { if (objElement.selectedIndex >= 0) { var objOption = objElement.options[objElement.selectedIndex]; var strQuestion = "Are you sure that you want to delete this page? This will delete all the sections for this page as well. Press \"Cancel\" if you would like to keep: '" + objOption.text + "'."; var boolDelete = confirm(strQuestion);

    if (boolDelete) { intPageKey = objOption.value.substring(0,objOption.value.indexOf("|"));

    if (objDest.value == "") { objDest.value = intPageKey; } else { objDest.value = objDest.value + "~" + intPageKey; }

    if ((boolIE) || (boolN6)) { objElement.remove(objElement.selectedIndex); } else if (boolNN) { objElement.options[objElement.selectedIndex] = null; } }

    f_clearSection_x(objElement.form.formSections); } else { alert("Please select a page to delete."); } }

    The add, edit, and delete section functions are nearly clones of each other. Each grabs the value from a select box and passes that as part of the URL in a new window. The edit and delete sections use the section select box, while the add section function ensures that the value of the current page is passed. Here are the section functions: function f_doAddSec(objElement) { if (objElement.selectedIndex >= 0) { strPageSections = objElement.options[objElement.selectedIndex].value; arrPageSections = strPageSections.split('|'); strPage = 'sectionupdate.php?Page=' + arrPageSections[0]; f_openWin_x(strPage,500,350); } else { alert("You must first select a page, before you can add a section."); } } function f_secEdit(objElement) { if (objElement.selectedIndex >= 0) { intSection = objElement.options[objElement.selectedIndex].value; strPage = 'sectionupdate.php?Section=' + intSection; f_openWin_x(strPage,500,350); } else { alert("Please select a section to edit."); } } function f_secDel(objElement) { if (objElement.selectedIndex >= 0) { intSection = objElement.options[objElement.selectedIndex].value; strPage = 'sectiondelete.php?formItemKey=' + intSection; f_openWin_x(strPage,500,350); } else { alert("Please select a section to delete."); } } When the user returns from the section subpages, the onFocus event assigned to the body tag activates the f_checkNewSection() function. This function checks for the existence of cookie variables that are set only on the update section action page. These variables include the section key, the new or updated name of the section, and the type of change—new (1) or update (0). The first part of the function gathers the cookie data by calling the f_cookieValue() function. When the cookie values have been assigned, the page key and section key values are returned from the selected page value. var objPages = objElement.form.formPages; if ((strSectionKey != "") && (objPages.selectedIndex >= 0)) { strSectionName = f_fixCookie(strSectionName); var strSelectedPage = objPages.options[objPages.selectedIndex]; var arrPageSections = strSelectedPage.value.split("|");

    if (boolType > 0) { … If the section is new, then the value and text is added to the options array and to the end of the appropriate page option value. If the section is an update, then the value and text are added to the options array of the page sections select box by IE and Netscape 4, respectively. The most important part of the function comes last. This code block calls the f_updateSections_x() function and clears the cookies. This ensures that the section updates have been added to the option array and that the user won’t accidentally create new options by refocusing the browser window. The f_cookieValue() function is a modification of the cookie value grabber described in Chapter 11. Here how both functions should look: function f_checkNewSection(objElement, intKey, strName, strType) { if (document.cookie) { var strSectionKey = f_cookieValue(intKey); var strSectionName = f_cookieValue(strName); var boolType = f_cookieValue(strType); var objPages = objElement.form.formPages;

    if ((strSectionKey != "") && (objPages.selectedIndex >= 0)) { strSectionName = f_fixCookie(strSectionName); var strSelectedPage = objPages.options[objPages.selectedIndex]; var arrPageSections = strSelectedPage.value.split("|");

    if (boolType > 0) { if (boolIE) { arrOptions[arrOptions.length] = new Option; arrOptions[arrOptions.length - 1].value = strSectionKey; arrOptions[arrOptions.length - 1].text = strSectionName; } else if (boolNN) { arrOptions[arrOptions.length] = new Option(strSectionName, strSectionKey); } else if (boolN6) { arrOptions[arrOptions.length] = document.createElement("OPTION"); arrOptions[arrOptions.length - 1].value = strSectionKey; arrOptions[arrOptions.length - 1].text = strSectionName;

    }

    if (arrPageSections.length > 1) { strSelectedPage.value = strSelectedPage.value + "," + strSectionKey; } else { strSelectedPage.value = strSelectedPage.value + strSectionKey; } } else { var arrSections = arrPageSections[1].split(",");

    if ((boolIE) || (boolN6)) { for (l = 0; l < arrOptions.length; l++) { if (arrOptions[l].value == strSectionKey) { arrOptions[l].text = strSectionName; } } } else if (boolNN) { for (l = 0; l < objPages.length; l++) { if (objPages[l].value == strSectionKey) { objPages[l].text = strSectionName; } } } }

    f_updateSections_x(objPages, objElement); document.cookie = intKey + "="; document.cookie = strName + "="; document.cookie = strType + "="; } } } function f_cookieValue(strName) { var strCookie = document.cookie; var intCookieLength = strCookie.length; var intNameLength = strName.length;

    if (strCookie.indexOf(strName) >= 0) { var intStart = strCookie.indexOf(strName) + intNameLength + 1;

    if (strCookie.indexOf(";", intStart) != -1) { var intEnd = strCookie.indexOf(";", intStart); } else { var intEnd = intCookieLength; }

    return unescape(strCookie.substring(intStart,intEnd)); } else { return false; } } When some browsers read a cookie using JavaScript, they place plus signs (+) where spaces should be. The f_fixCookie() function is used to remove this addition. It looks at each character in the cookie and replaces "+" with " ". function f_fixCookie(strCookie) { for (l = 0; l < strCookie.length; l++) { strCookie = strCookie.replace("+"," "); }

    return strCookie; } When the user is ready to submit the page the onSubmit attribute of the form tag causes the f_ValidateArticle() function to fire. This function calls a number of validation functions, but the important bit is that it calls two separate functions that output the section and page values. These functions are called f_outputArticle() and f_outputPageTitles(). The simpler of the two output functions, f_outputPageTitles(), generates an array from the page keys and text of the article page select box. Once the array has been created it can be output as a delimited string using the join() method. function f_outputPageTitles(objFrom, objTo, strDelim, strDelim2) { var arrOutPut = new Array(); var arrValues = new Array();

    if (!strDelim) { strDelim = '|'; }

    for (l = 0; l < objFrom.length; l++) { arrValues = objFrom.options[l].value.split("|"); arrOutPut[l] = arrValues[0] + strDelim + objFrom.options[l].text; }

    if (!strDelim2) { strDelim2 = '~'; }

    objTo.value = arrOutPut.join(strDelim2); } To say the f_outputArticle() function is a little more complicated is an understatement. In total, it uses six arrays and five loops—including one set nested three deep. The end result is to output the current page and sections in order in a delimited string. The first loop gathers the page and section information from the values of the page options. These are assigned to a set of parallel arrays. for (l = 0; l < objElement.length; l++) { var strPageSections = objElement.options[l].value; arrPageSections = strPageSections.split("|"); arrPages[l] = arrPageSections[0]; arrSectionString[l] = arrPageSections[1]; } The next loop moves through each set of section keys. There should be one set for each page. For each set of sections a new array is created to hold the individual array items. Each item is then compared to the options array values in the same manner as the f_updateSections_x() function, with the option array being on the outside of the loop pair. for (k = 0; k < arrSectionString.length; k++) { arrSections[k] = arrSectionString[k].split(","); arrSectionsOut[k] = "";

    for (j = 0; j < arrOptions.length; j++) { for (h = 0; h < arrSections[k].length; h++) { …

    If a matching value is found, the output array is populated with the updated set of sections for the page. if (arrSections[k][h] == arrOptions[j].value) { arrSectionsOut[k] = arrSectionsOut[k] + arrOptions[j].value + ","; } Finally, another loop moves through the page array and creates each of the output records. A join() function attaches the records together. for (g = 0; g < arrPages.length; g++) { arrPageSectionsOut[g] = arrPages[g] + "|" + arrSectionsOut[g].substring(0, arrSectionsOut[g].length - 1); } objDest.value = arrPageSectionsOut.join("~");

    This is how it looks when it’s all put together: function f_outputArticle(objElement, objDest) { if (boolNN) { f_updateSections_x(objElement, objElement.form.formSections); }

    var arrPages = new Array(); var arrSectionString = new Array(); var arrSectionsOut = new Array(); var arrSections = new Array(); var arrPageSections = new Array(); var arrPageSectionsOut = new Array();

    for (l = 0; l < objElement.length; l++) { var strPageSections = objElement.options[l].value; arrPageSections = strPageSections.split("|"); arrPages[l] = arrPageSections[0]; arrSectionString[l] = arrPageSections[1]; }

    for (k = 0; k < arrSectionString.length; k++) { arrSections[k] = arrSectionString[k].split(","); arrSectionsOut[k] = "";

    for (j = 0; j < arrOptions.length; j++) { for (h = 0; h < arrSections[k].length; h++) { if (arrSections[k][h] == arrOptions[j].value) { arrSectionsOut[k] = arrSectionsOut[k] + arrOptions[j].value + ","; } } } }

    for (g = 0; g < arrPages.length; g++) { arrPageSectionsOut[g] = arrPages[g] + "|" + arrSectionsOut[g].substring (0, arrSectionsOut[g].length - 1); }

    objDest.value = arrPageSectionsOut.join("~"); } Update Article Action Page The action page for the article update system (a_updatearticle.php) is only slightly easier than the form or template pages. After the data is validated, the basic article information is added or updated to the Article table. If a new article is created, the article key of the new article is assigned to $formArticleKey. This way you don’t have to keep referring to two separate variables. $strArticleTable = "Article"; $strPageTable = "ArticlePage"; $strSectionTable = "PageSection"; if (!isset($formActive)) { $formActive = 0; }; if ($formArticleKey > 0) { $strArticleSQL = "UPDATE $strArticleTable SET ArticleTitle=\"$formTitle\", Author=$formAuthor, Description=\"$formDescription\", Active=$formActive, ActiveDate=\"$formActiveDate\", LastModBy=" . $GLOBALS["UserID"] . ", LastModDate=Now() WHERE ArticleKey = $formArticleKey"; $objArticleResult = mysql_query($strArticleSQL,$CONNECTION) or die("Couldn't update article information."); } else { $strArticleSQL = "INSERT INTO $strArticleTable (ArticleTitle, Author, Description, Active, ActiveDate, LastModBy, LastModDate) VALUES (\"$formTitle\", $formAuthor, \"$formDescription\", $formActive, \"$formActiveDate\", " . $GLOBALS["UserID"] . ", Now())"; $objArticleResult = mysql_query($strArticleSQL,$CONNECTION) or die("Couldn't insert article information."); $formArticleKey = mysql_insert_id(); }; Next, the deleted page values are extracted from the hidden field where the JavaScript f_pageDel_x() function has placed them. A looped query can then remove the records. if ((isset($formDeletedPages)) && ($formDeletedPages != "")) { $arrDeletedPages = split(",",$formDeletedPages);

    foreach ($arrDeletedPages AS $PageKey) { $strDelPageSQL = "DELETE FROM $strPageTable WHERE PageKey = $PageKey"; $strDelPageResult = mysql_query($strDelPageSQL,$CONNECTION) or die("Couldn't delete from $strPageTable"); $strDelSecSQL = "DELETE FROM $strSectionTable WHERE PageKey = $PageKey"; $strDelSecResult = mysql_query($strDelSecSQL,$CONNECTION) or die("Couldn't delete from $strSectionTable"); };

    }; The same process is repeated to update the page titles from the $formPageTitles variable and the section order from the $formPageSections variable. After the updates have been made, the article record is unlocked by assigning the value of the LockedBy record to null. Here is the complete text of the a_articleupdate.php file: a_articleupdate.php

    $objRestrict = new o_Access; session_start(); $strTitleError = "The title must be at least 3 characters long and less than 90."; $strDescError = "You must enter a description that is more than 3 and less than 200 characters."; $strAuthorError = "You must enter an author for this article."; $strDateError = "The active date must be in the format 12/31/2002."; $objValidate = new o_Validate; $objValidate->m_isRangeLength($formTitle,90,3,$strTitleError); $objValidate->m_isRangeLength($formDescription,200,3,$strDescError); $objValidate->m_Exists($formAuthor,$strAuthorError); $objValidate->m_isFormatDate($formActiveDate,"mm/dd/yyyy","/",$strDateError); $strErrors = $objValidate->m_Alert("
    "); if (($strErrors != "") || (isset($formReset))) { session_register("sessMultiform"); $GLOBALS["sessMultiform"] = "1";

    foreach($HTTP_POST_VARS as $FormName => $FormValue) { if (($FormName != "") && ($FormValue != "")) { session_register($FormName); $HTTP_SESSION_VARS[$FormName] = $FormValue; }; };

    if ($formArticleKey > 0) { header("Location: articleupdate.php?formAritlceKey=$formArticleKey"); exit(); } else { header("Location: articleupdate.php"); exit(); };

    } else { $strArticleTable = "Article"; $strPageTable = "ArticlePage"; $strSectionTable = "PageSection";

    if (!isset($formActive)) { $formActive = 0; };

    if ($formArticleKey > 0) { $strArticleSQL = "UPDATE $strArticleTable SET ArticleTitle=\"$formTitle\", Author=$formAuthor, Description=\"$formDescription\", Active=$formActive, ActiveDate=\"$formActive Date\", LastModBy=" . $GLOBALS["UserID"] . ", LastModDate=Now() WHERE ArticleKey = $formArticleKey"; $objArticleResult = mysql_query($strArticleSQL,$CONNECTION) or die("Couldn't update article information."); } else { $strArticleSQL = "INSERT INTO $strArticleTable (ArticleTitle, Author, Description, Active, ActiveDate, LastModBy, LastModDate) VALUES (\"$formTitle\", $formAuthor, \"$formDescription\", $formActive, \"$formActiveDate\", " . $GLOBALS["UserID"] . ", Now())"; $objArticleResult = mysql_query($strArticleSQL,$CONNECTION) or die("Couldn't insert article information."); $formArticleKey = mysql_insert_id(); };

    if ((isset($formDeletedPages)) && ($formDeletedPages != "")) { $arrDeletedPages = split(",",$formDeletedPages);

    foreach ($arrDeletedPages AS $PageKey) { $strDelPageSQL = "DELETE FROM $strPageTable WHERE PageKey = $PageKey"; $strDelPageResult = mysql_query($strDelPageSQL,$CONNECTION) or die("Couldn't delete from $strPageTable"); $strDelSecSQL = "DELETE FROM $strSectionTable WHERE PageKey = $PageKey"; $strDelSecResult = mysql_query($strDelSecSQL,$CONNECTION) or die("Couldn't delete from $strSectionTable"); };

    };

    if ((isset($formPageTitles)) && ($formPageTitles != "")) { $arrPageTitles= split("~",$formPageTitles); $intPageCounter = 1;

    foreach ($arrPageTitles AS $PageTitles) { $arrPageTitle = split("\|",$PageTitles);

    if ($arrPageTitle[0] < 0) { $strPageSQL = "INSERT INTO $strPageTable (ArticleKey, PageTitle, PageSort) VALUES ($formArticleKey, \"" . $arrPageTitle[1] . "\", $intPageCounter)"; $strPageResults = mysql_query($strPageSQL,$CONNECTION) or die("Couldn't insert page information."); $intPageKey = mysql_insert_id(); $strSectionSQL = "UPDATE $strSectionTable SET PageKey=$intPageKey WHERE PageKey=$arrPageTitle[0]"; $strSectionResults = mysql_query($strSectionSQL,$CONNECTION) or die("Couldn't update section information."); } else { $strPageSQL = "UPDATE $strPageTable SET PageTitle=\"" . $arrPageTitle[1] . "\", PageSort=$intPageCounter WHERE PageKey = $arrPageTitle[0]"; $strPageResult = mysql_query($strPageSQL,$CONNECTION) or die("Couldn't update page titles."); $intPageKey = $arrPageTitle[0]; };

    $intPageCounter++; };

    $arrPageSections = split("~",$formPageSections);

    foreach ($arrPageSections AS $PageSections) { $arrPageSection = split("\|",$PageSections); $arrSection = split(",",$arrPageSection[1]); $intSectionCounter = 1;

    foreach ($arrSection AS $SectionKey) { if ($SectionKey) { $strSecSortSQL = "UPDATE $strSectionTable SET SectionSort= $intSectionCounter WHERE SectionKey=$SectionKey"; $strSecSortResult = mysql_query($strSecSortSQL,$CONNECTION) or die("Couldn't update section sort info."); $intSectionCounter++; };

    };

    }; };

    $strUnLockSQL = "UPDATE $strArticleTable SET LockedBy = NULL, LockedTime = NULL WHERE ArticleKey = $formArticleKey"; $objUnLockResult = mysql_query($strUnLockSQL,$CONNECTION) or die("Couldn't unlock table."); header("Location: articleselect.php"); exit(); };

    ?>

    Update Section

    The Update Section page may be a lot simpler to code than the Update Article page, but it has a few vital elements that give the system the functionality you’re looking for. For example, the template page has three elements that are required to generate the DHTML WYSISYG controls. First, a dynamic section must be created to hide the namespace function from non-IE browsers. {{section IEHead }} {{/section IEHead }}

    Second, the “IE_DISPLAY” content area should be set to “display:none” for IE users only.

    Third, the validation function that runs when the form is submitted must transfer the contents of the WYSIWYG editor to the hidden text area. function f_ValidateSection(objForm, objElement) { if (f_valHeader(objElement)) { if (boolIE) { objForm.formContent.value = xsEditor.content; }

    return true; } else { return false; } }; Figure 16.9 shows the completed Update Section page

    Figure 16.9 : The Update Section page

    Meanwhile, the form page will need to populate the IE -specific content. if ($GLOBALS["Browser"] == "IE") { $strIE = $objTemplate->m_AssignContent("IE", "IE", $strIESection); $objTemplate->m_AssignSection("IEHead", $strIE); $objTemplate->m_AssignContent("IE_DISPLAY", "display:none;"); } else { $objTemplate->m_AssignContent("IE_DISPLAY", ""); }

    And the action page will need to generate the cookie variables that will be used by the article update page to generate the new option. if ($formSectionKey < 0) { $strSQL = "INSERT INTO $strTable (PageKey, Heading, ShowHeading, SectionContent, SectionSort) VALUES($formPageKey, \"$formHeading\", $formShowHeading, \"$formContent\", 99)"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't insert section data."); $formSectionKey = mysql_insert_id(); $strCookieType = "1"; } else { $strSQL = "UPDATE $strTable SET Heading=\"$formHeading\", ShowHeading=$formShowHeading, SectionContent=\"$formContent\" WHERE SectionKey=$formSectionKey"; $objResult = mysql_query($strSQL,$CONNECTION) or die("Couldn't update section data."); $strCookieType = "0"; };

    setcookie("NewSectionType", $strCookieType); setcookie("NewSectionKey", $formSectionKey); setcookie("NewSectionName", $formHeading); echo " "; exit();

    Feature Articles The last new page for the information systems interface is the Feature Articles page. This page will allow authorized users to assign articles to the article showcase display. This page combines the Linked Select Box and Reorder Select Box tools. As you can see in Figure 16.10, every article in the system is listed in the left select box. Moving the options to the right box causes the section page to assign the article as a feature. The reorder buttons allow the user to assign the sort order.

    Figure 16.10: The Feature Articles page The code for the files that make up this page follow the same pattern that has been used throughout this project: the object file loads the values into a select box, the template page outputs the edited select box to a hidden form element, and the action page reads the item keys from the text field to generate the SQL commands. By now, you should be more than equipped to generate this page, so I’ll leave it up to you. Of you get stuck, check out the completed project files at http://www.premierpressbooks.com, on the page for this book.

    Chapter 17: Completing the Information System Interface

    The information systems project has given you a good overview of functional design, and I hope it’s spurred your imagination for other types of value-added content. In this chapter, I’ll review some of the pages from the project and describe some ways that you could add more functionality. Look over these suggestions and decide what additional features should be in your information system interface. See if you can’t come up with a few ideas on your own, but remember that added value is just that: added content. It shouldn’t take precedence over bug-free operation. You need to perform the same types of testing you learned in the first two projects.

    Adding Functionality to Article Display Pages

    The article display pages give the user a lot of flexibility in how the articles are listed, but you may want to add a few more options. The article search in particular could present the user with more choices. You saw in the Update Feedback page how to give a user search options. You could apply the same method to the article search. Specifically, you could add a search by date, a search that only looks at the article or page titles, or a search that finds articles by author. This last option may seem the same as the Articles by Author page, but the current interface only has links to this page from the article page. By providing a search tool, you can help the users avoid a few steps and improve the chances that they will find the articles for which they are looking.

    Another change to the article search function is to display section headings rather than page titles. With this functionality, the user would be taken to the same article page, but would then be directed to an interior link on the page. For this to work, you will need to modify the SQL search query and update the article page to create the anchor tags. For example, say you have a page that includes a page section numbered 27 and named “Holy Cows: The Alien Animal Mutilation Cover-Up.” You could attach the anchor tag like this: Holy Cows: The Alien Animal Mutilation Cover-Up

    The search results would then send the user directly to the section: Holy Cows: The Alien Animal Mutilation Cover- Up

    Displaying section results does more than just increase the number of results; it allows the users to find the exact information for which they are searching. The trade-off is that the results are likely to be skewed toward the article that deals most directly with the search topic. A search for “aliens” may display a dozen results for an article on crop circles before a single result from an article on immigration services. One possible resolution is to display the search results categories by type. When creating this functionality you could place the article matches at the top of the results, followed by the section matches, or you could show the article results with a link to the more detailed section results. This categorized functionality is the type used by Google.com, shown in Figure 17.1.

    Figure 17.1: Categorized search results from Google.com

    Besides changing the search options, you can also change the functionality of the article page itself. The current interface uses a system of next and previous links to navigate through an article. Another navigation technique is to provide a table of contents area that will allow the user to skip from one page to any other in the article. Additionally, if your articles typically contain a large number of sections, you could provide a set of shortcut links to each interior section.

    Adding Functionality to Feedback Pages

    The feedback pages can also benefit from the addition of added functionality. In particular, the public parts of the feedback system can be improved. Many online communities find that providing activity profiles of the members can improve the community atmosphere. Activity profiles are visible to all members and can include stats on the total number of comments that a member has posted, the date the member joined the site, and even a user-contributed ranking system.

    Allowing visitors to rank each other’s comments allows you to program a comment designation system. For example, a member who has acquired a large number of positive ranks can be designated as “popular.” Future comments by this member could be designated with a special symbol or other notation. This lets the readers easily gauge the expected quality of a particular comment, based on the ranking of the member who posted it. As far as the actual comments are concerned, the primary type of functionality that could be included is the use of special formatting tags. You’ve stripped the HTML tags from the posts for security reasons, but you can still allow the user to add simple formatting techniques with these special tags. A good example of a community system that uses special tags is the BBCode system used in the popular phpBB application. BBCode allows the user to enter tags such as [b]bold text[/b] or [i]italic text[/i]. As Figure 17.2 shows, phpBB also replaces text-based emoticons with graphic smilies.

    Figure 17.2: How are you feeling today?

    Looking at the feedback management system, a couple of added features can make your users’ tasks a little easier. First, you could add a new search option that will return all of the feedback items for a single article based on the title supplied. The results could be displayed in the same order as in the article. This would make the moderator’s task of finding a specific comment quite a lot simpler.

    The other addition could be the inclusion of a tool that would automatically mark all returned comments for inactivation. This could save the user a number of clicks if a lot comments must be deactivated at once—say, because some joker is sending the community off the rails.

    Adding Functionality to Help Pages

    The help system already provides most of what the interface users need, but the management team that keeps the system up to date would appreciate some added features. The help items would primarily benefit from the use of descriptive names. The current help management system uses the name of the form element to designate each help item. These names can be a little confusing and may not be descriptive of the element the help item covers. Not only will allowing the management team to create descriptive plain-text titles for each help item increase their ability to locate a particular item, but such titles would also help the users if they were displayed in the context-help layer itself. This would more clearly designate the element that the help content describes.

    Another thing that the management team would appreciate is a way to delete help items. As the site changes and pages are redesigned or become obsolete, the need to remove obsolete help items becomes apparent. Also, the risk of accidentally duplicating a help item increases. A duplicate help item can do more than just make management difficult; it will generate errors in the context-sensitive display system. You should consider developing a validation function that will ensure that no two help items on the same page share the same title.

    One feature that you could add to the help display system is the condition display of the help function. It’s possible that many of the pages in the interface won’t have any help items assigned. The current system still puts the help check box on these pages. You could avoid user confusion by scripting a conditional statement that will only display the help check box if the current page has help items.

    Adding Functionality to Article Management Pages The article management system is chock-full of functionality, but even these pages can benefit from a few added features. For example, if you remember, the Article Update page automatically updated the page section select box with new or updated sections. One thing it doesn’t do, however, is remove deleted sections. You could easily add this by modifying the f_checkNewSection() function and the Remove Section action page. The action page would send the same set of cookies as the Update Section page, but the NewSectionType cookie could be set to –1. The JavaScript would then recognize this value and remove the corresponding option. Another feature that could be added to the Article Update page is tied to the article locking system. Currently, if a user leaves the page—say by clicking a link or using the back button—without clicking Submit, all other users are still locked out of the item. To help keep users from forgetting to click Submit, a function can be assigned to the onUnload event of the page. This function would check whether the form has been submitted and, if it hasn’t, will display a message that will alert the user. This same function could be used on other long forms or anywhere the user is likely to forget to click the Submit button. The current image tables in the database only support one type of item per image. If you tried to use the existing ImageAssignment table to assign both sections and pages you would be likely to create a duplicate ItemKey record. This would produce unpredictable results in the article display pages. If you would like to add the functionality to assign images to individual article pages or to the articles themselves, you will need to modify the table. A simple solution would be to add a type field that would allow you to designate the type of item to which the item key refers. This way, when you display a list of articles you could sort the image results by Type=’article’.

    Project Summary

    In this project you created a complex control interface for Website maintenance. You learned how to create a series of interconnected forms that will allow you to create and modify dynamically generated Web pages. Each element on the page can be controlled from the forms covered in this project—including page titles, headings and images.

    Part V: Beyond the Lab

    Chapter List Chapter 18: XML and XForms Chapter 19: Embedded Web Form Technologies

    Chapter 18: XML and XForms In Chapter 1 I described Web forms as a type of information exchange between a computer system and a human element. Most of the problems that arise from the use of Web forms are related to the fact that these two resources are so very different. To process information, computer systems require input to be submitted in a standardized format, structured according to the acceptable parameters, and semantically labeled. HTML Web forms were designed for the general populace. Specific attention was given to making form elements easy to understand, simple to use, and uncluttered with technical details. As a result, developers were left with a system wherein the user communicates data in a real-world context, and the accompanying computer system must convert this information to acceptable values before processing.

    The end result is that Web form developers must produce a large amount of server-side code to validate, process, and format form data before the information can be transmitted. Attempts to perform the same functions on the client require JavaScript. As you’ve seen from the projects, this alternative is often complicated, cumbersome, and ultimately unreliable. Jumping to the rescue is a new technology, based on the XML standard: XForms.

    XML If you’ve been coding in an ice cave on the cold side of Antarctica, you might not have heard about eXtensible Markup Language (XML), but knowing about XML and actually knowing XML are two different things. As you are likely aware, XML is a formatting structure that defines how information can be schematically labeled. It might not seem that important to be able to label, for example, car information as car information, but computers don’t have the same associative powers as the human brain.

    A person can look at a piece of paper with columns containing values like “Ford,” “BMW,” “Honda,” and “Yugo” and assume the rest of the information deals somehow with automobiles. A more complete review of the information can lead to even more associations. You might be able to determine that the paper holds information about auto sales in the state of California. Using the information you have gleaned, you can properly file the paper.

    Computer systems, however, look at a line of values as just that, a line of values. It takes a significant amount of work just to strip out each individual value; automatically associating the information is practically impossible. Enter XML. This technology does two important things. It establishes—for any computer system that can read XML—the association relating to the data, and it provides a simple way to divide the individual value elements. From a usage standpoint XML looks similar to HTML. In fact, the latest HTML recommendation from the W3C is a direct subset of XML called XHTML. The difference between the two is that HTML has a predefined set of tags. XML allows the developer to create any tags that are needed. Display and style elements are added using CSS or the XML style language (XSL). Here is an example of a small block of XML: Sunday 0

    Nodes and Nodesets XML code is defined using entities or nodes. A node is an XML tag set, any attributes associated with the tag, and the content it encapsulates. References to a node are accomplished using the slash notation to navigate through nested nodes in a document. For example, to reference the name node from the preceding code example, you would use week/day/name. This would give you access to the content of the node. To access an attribute you can use the @ (at) symbol. week/day/name@type = Sunday A nodeset is a collection of nodes. In the example, day represents a nodeset. An entire nodeset can be referred to using the same notation used for single nodes. Additionally, you can reference a particular node within a nodeset by using the bracket [] notation. The child nodes of a nodeset can be referenced by number or name using this method. week/day[1] = week/day/name week/day[index] = week/day/index When creating references to nodes, the ref attribute is used. References to nodesets are accomplished using the nodeset attribute. The bind attribute can be used to reference both nodes and nodesets.

    Namespaces The basic XML specification created by the W3C has no predefined tags or attributes. This allows XML documents to be incredibly flexible. As an XML developer you would never need to worry about using a reserved word or special case tag in the wrong place. However, a number of technologies related to XML do use special tags and attributes. To access these, you will need to declare that you are using a specific XML technology. Each XML specification has been assigned a URL, called a namespace, that allows you to declare the type and recommendation version of the technology being used.

    Each XML document must have a root tag that encapsulates the entire document. It is usually in this tag that the namespaces are declared. Of course, the first namespace that you must declare is the XML Namespace specification itself.

    Every time a namespace is used for a tag or attribute the assigned abbreviation should be prepended like so: Table 18.1 lists the namespaces and specifications used in this chapter. Table 18.1: Namespaces

    Abbreviation Specification Namespace xmlns XML http://www.w3.org/1999/xhtml Namespaces xforms XForms http://www.w3.org/2002/01/xforms Table 18.1: Namespaces Abbreviation Specification Namespace

    XLink http://www.w3.org/1999/xlink xsd XML Schema http://www.w3.org/2001/XMLSchema ev XML Events http://www.w3.org/2001/xml-events

    What Are XForms?

    The XForms project is a companion to the XML standard developed by the W3C. The goal behind the XForms recommendation is to develop a protocol that will benefit both users and developers by increasing the accessibility and functionality of the form tools available to users, defining a powerful and standardized structure for the display and transmission of form data, and integrating the validation, formatting, and processing tools typically only available on the server with the client system.

    The XForms recommendation defines the XForms Model. This model is used to define the content and function of XForms elements based on the XML standard. The XForms Model is divided into two parts, the XForms User Interface and the XForms Submit Protocol. The XForms User Interface provides the interactive form elements. This set of uniform input tools is intended to expand functionality and usability of XForms interfaces beyond the HTML form elements available now. The XForms Submit Protocol defines how the form data is sent and received from the server. This protocol ensures that data is processed as XML instance data on both the client and server.

    The XForms Advantage

    XForms introduces a number of advantages for both the end user and the developer. First, by using applying the XML recommendation to Web forms, XForms more easily allows for the separation of content and presentation. XForms user interface controls simply define the type of user data that is expected. The actual display or presentation of the controls is left up to the client machine. Style elements, such as CSS or XSL, can be used to give display hints or declare presentation preferences, but the ultimate decision of how XForms controls interact with the user is determined by the configuration of the client system. This built-in accessibility feature allows the XForms developer to concentrate on the data content rather than worry about the way the form elements will appear to the user.

    XML instance data

    This is a representation of the names and values of the form elements expressed as XML. Keeping it in a standardized XML format allows the data to be immediately available to a large number of different data systems. For example, XML instance data could be inserted directly into a relational database via an XML import application, or parser, without the need of server-side

    Another advantage of XForms is that it is a strongly typed language. This means that you can define the specific format or range that is acceptable for a form input field. With the current HTML form system a single text field will accept string data, a number, a date, or any combination of keys and special characters. To get the user to enter only the desired data type you must create a script that watches the form field, compares the data against some validation function, and returns an error message if the result is a bad match. With XForms you can simply declare that an input field accepts only integers and the browser will perform the validation and error messages automatically.

    Additionally, XForms allows the developer to reuse existing XForms schema. The modular template system used in the projects of the book has been an attempt to separate the content of a form from how the form is displayed so that scripts, styles, and HTML code could be reused throughout the interface. The XForms standard creates this same separation automatically. The result is a model that reduces duplication and ensures that updating or modification of an XForms interface does not require the wholesale recreation of validation or processing functions. Yet another advantage of XForms is the sophistication of the data processing available on the client system. In Chapter 2 I discussed the usability advantages of performing validation on the client. Besides eliminating the need for extra calls to the server, client- side data processing reduces the computational load on the server and places the validation functions closer in time to the data input itself. With standard HTML forms, client-side processing is limited and time-consuming. The XForms Model demands an increase in the form processing capabilities of the client while simultaneously decreasing the programming burden of the Web form interface developer.

    Finally, the XForms Submit Protocol ensures that the data received from the client is in XML format, which is quickly becoming the standard for data transmission between computer systems. Producing user-submitted data as XML means that custom server- side scripts are no longer required to marshal the form data to the intended storage medium. Using an XML parsing program or the XML import tools available with many application servers, posted XML data an be directly inserted into the data structure.

    The Catch: Browser Support

    XForms sounds like great advantage to users and developers alike. So why isn’t everyone using XForms? The problem is that XForms capability hasn’t yet been implemented in any of the most popular browsers. In fact, as of this writing, the XForms specification has only just come out of the working draft stage; the actual recommendation is not expected for many months to come. It is far too early to implement XForms for an interface intended for the general Web public. There are, however, a number of products that will allow you to practice using the XForms protocol or even develop full-scale XForms interfaces for targeted audiences, such as intranet or extranet communities.

    The following products include some degree of XForms implementation: § X-Smiles. This open-source, Java-based XML browser is at the forefront of XML browser community. Currently, the beta browser supports 75 percent of the XForms specification with much of the remaining functionality expected to be included before the final release. X-Smiles also supports the XML implementation of JavaScript called EMCAScript. Figure 18.1 shows the X-Smiles browser in action. You can download the X-Smiles browser for free at http://www.xsmiles.org/.

    Figure 18.1: The X-Smiles browser § Chiba. Another open-source Java browser, Chiba provides a working implementation of much of the XForms specification. Information and installation files can be found at http://chiba.sourceforge.net/. § XML WebAccess. Mozquito’s XML user interface enhances standard Web browsers to allow for the integration of XForms and other XML specifications. XML WebAccess is a commercial product available both for IIS and Apache. Information and trial software is available at http://www.mozquito.com/. § LiquidOffice. While the LiquidOffice eForm Management System from Cardiff doesn’t yet include the XForms user interface elements, it does integrate many of the XML form definitions and XML data interchanges from the XForms Submit Protocol into the application. Information on this product can be found at http://www.cardiff.com/LiquidOffice/. § TIBET. An ambitious project, the TIBET application is intended not only to implement the latest XML and XForms standards, but also (in the form of a patch) to allow users of older browsers the same functionality. The TIBET software with XForms implementation should become available for purchase as the XForms specification is finalized.

    XForms Basics After using the standard HTML form elements for any length of time, you’ll discover the difficulties inherent in the system. Primary among them is that to present the form data to the user, a different structure is used for each element type. For example, the value of a text field is assigned using the value attribute, the value of a multi-line text area is included between a tag set, and a select box is assigned values using the option tags. Radio buttons and check boxes use the checked declaration while select boxes or multiple select areas use selected. This unnecessary disparity adds a degree of complexity that can be avoided by using the XForms User Interface.

    For example, consider creating a short form to allow users to submit e-mail addresses for an online newsletter. Using standard HTML the form would be coded like this:

    E-mail Address: Send me a newsletter Select a preferred newsletter format:

    Notice that each form element is handled a little differently. Here’s how the same form appears using XForms: E-mail Address Send me a newsletter Select a preferred newsletter format 1 HTML 2 Rich text 3 Plain text Submit

    Notice the following differences between the two models: § Form controls must have captions associated with them. The display and appearance of these captions is left up to the client system. § The FORM tag is no longer needed. Form elements can appear anywhere on the page. For pages with multiple forms, each form element can be assigned to a specific form regardless of its location. § The actual appearance of the form control is not explicitly defined. The form control can be rendered differently depending on the system. § Markup for all form elements has been standardized.

    XForms Form Elements XForms form elements are created using the standard XML notation. The id, class, and accesskey attributes should be familiar. The navIndex attribute replaces tabindex, while the :lang attribute replaces the language attribute of the FORM tag. You can assign an XForms form element to a specific form with the model attribute. The ref attribute is used to assign constraints and validation functions to a form element.

    The following is a list of the form elements currently in the XForms specification: § input. This is a free-form data entry element that roughly corresponds to the text input element. § secret. This element replaces the old password type field. § textarea. Another free-form data entry field, this area corresponds to the traditional TEXTAREA tag. § output. This element presents form data to the user, but does not provide a way to directly change the value. This element can be used similarly to the text fields with the disabled declaration. § upload. This element replaces the older file upload element. In addition to simple file uploads, this element is intended to include functionality for gathering information from input devices such as digital cameras and microphones. § range. This element has no current form equivalent. It will allow the user to select a value from a preset range of options. An example of this in operation is the volume control on a stereo, which allows the user to slide or turn a control left or right to indicate the desired volume. § button. This control encapsulates the button and reset control elements of the current HTML form. Buttons allow the user to activate predefined actions. § submit. The submit control allows the user to send the form data to the server. § selectOne. This element allows the user to select a single option from a list of choices. This element encapsulates the radio button group, a single check box, and the select box. § selectMany. This element allows a user to select multiple options from a list of choices. This element encapsulates a check box list and a multiple select box.

    XForms Child Elements Many of the elements in the XForms User Interface can function with nothing but the parent tag and the caption node. Other form elements such as the select tag require additional child nodes. In addition to these required tags, XForms allows for a large number of optional nodes designed to improve usability and accessibility. Each of these elements is included inside the parent entity; some of these child nodes can have children themselves: Select an option 1 First choice 2 Second choice 3 Third choice

    The following is a list of the most common child elements. § choice. This tag allows you to designate a series of options as a group. How this group is presented to the user is left up to the client. § item. This represents an individual option. § itemset. From a user’s perspective this entry appears the same as the choice node. The itemset node, however, unlike the choice node, allows the client to dynamically determine the item groupings. § value. This tag assigns a value to the corresponding item. § caption. This required tag attaches a label to a form element. The format and appearance of the caption is left to the client. § help. This tag allows a developer to attach context-sensitive help information directly to a form control. § hint. Tool tips or bubble help are common ways to give the user short informative messages about program elements. The hint node allows you to define the text of these messages. § alert. This tag allows you to affix error or alert messages to a form element.

    Creating XForms Pages

    XForms form elements can be placed on any XML page that conforms to the current XML standard. The first step in creating an XForms interface is to define an XForms namespace. The namespace used here is the current namespace identifier for the XForms specification. At each stage in the recommendation process a new identifier will be designated until a final identifier is published prior to the release of the full recommendation. model and submitInfo Each form on the page is declared using the model tag. The optional id attribute of this element allows you to create multiple forms on a single page. A form without the id attribute will automatically handle all form elements on the page that have not been assigned a model value. Unlike conventional HTML forms, XForms allow the developer to designate the specific form elements that should be submitted when the form is processed. Here is the format for the model tag: The submitInfo tag has a number of attributes. method and action should be familiar to you from the HTML FORM tag. The ref attribute allows you to designate a specific node for the submission process. In place of the ref attribute you could instead use the bind attribute to reference the id value of a bound node. instance The instance tag allows you to designate the starting values of any form elements. To use the instance tag you create an arbitrary set of XML nodes that you will use to reference the initial values. The ref attribute of the each form element should match the node for the initial value. For example, to create a text box with the initial value of "Happy Text", you could use the following: Happy Text

    Text?

    Constraining XForms Data One major advantage of using XForms is the ability to intrinsically define the acceptable values of a form element. For example, a date field would only accept date values, while a name field would need to accept any string, but not a file. To apply constraints on XForms form elements, you use the bind tag. The bind tag goes inside the model node. Each form element that has a constraint must be assigned a separate bind tag. To attach a constraint to a specific bind you assign the ref attribute to the id value of the bind. John

    The bind tag accepts a number of constraint attributes. These attributes define the limitations of the bound element. One attribute—type—defines the constraint by assigning an XML Schema value to a bound element.

    XML Schema Constraint types use preset type definitions based on the XML Schema specification. By defining types, a developer can limit input to specific number ranges, formatting requirements, or lists of acceptable values. To use the type attribute you will need to define an XML Schema namespace and add the schema tag inside the model node.

    The schema tag has a number of child nodes. The element tag allows you to define a constraint based on an XForms element, while the simpleType tag allows you to create a data type without declaring a specific element. The complexType tag allows you to assign a set of required elements and attributes for a constraint type.

    XML Schema namespace To use the XML Schema tag, you must declare the XML Schema namespace somewhere near the top of the document. The current XML Schema namespace is http://www.w3.org/2001/XMLSchema.

    element The element tag sets the requirements of a specific form element. The element tag has a number of optional attributes. The name attribute allows you to specify the name of the form element that the element tag constrains. The type attribute allows you to define an element using another schema. The minOccurs and maxOccurs attributes are used to define the required number of occurrences of the element. Since the default for both minOccurs and maxOccurs is 1, simply declaring a form element by name in the schema makes it a required element. Setting the minOccurs value to 0 makes the form element optional. simpleType The simpleType tag allows you to create a constraint type without declaring an element. This type can then be assigned to any form element. The simpleType tag uses child nodes to define the constraints. The restriction tag defines the base value of the type while additional child tags refine the constraint. The following child nodes are available to the restriction tag: § length. This tag defines the length in characters of the constraint type. § minLength. This tag defines the minimum length in characters of the constraint type. § maxLength. This tag defines the maximum length in characters of the constraint type. § pattern. This tag declares a regular expression that must be matched by the constraint type. § enumeration. This tag defines an acceptable value for the constraint type. By declaring multiple enumeration tags you can restrict the input to a defined set of values. § whiteSpace. This tag does not constrain a type, but instead affects the white space characters inside it. A value of "preserve" leaves the string alone, "replace" causes all instances of the tab or carriage return character to be replaced by single space characters, and "collapse" causes all occurrences of multiple continuous spaces to be collapsed into a single space and preceding and trailing white space to be removed. § maxInclusive. This tag defines the uppermost acceptable value for the constraint type. It will only work for numbers. § minInclusive. The reverse of maxInclusive, this tag sets the lowest acceptable value for the constraint type. § maxExclusive. This tag defines the nonInclusive uppermost value for the constraint type. It will only work for numbers. § minExclusive. The reverse of maxInclusive, this tag sets the nonInclusive lowest value for the constraint type. § totalDigits. This tag defines the maximum number of numerical digits that can occur in the constraint type. § fractionalDigits. This tag defines the maximum number of numerical digits that can occur after the decimal point in the constraint type.

    Thus, to define a constraint that will only accept integers larger than 50, the following code would be used: complexType The complexType tag allows the developer to define a predetermined set or sequence of elements and attributes. Simply declaring elements and attributes inside the complexType tag makes them required unless the minOccurs value is set to 0. Creating the nodes inside a sequence tag forces the elements to appear in a specific sequence. For example, to create a complexType constraint that requires three elements in a specific order and a fourth element anywhere in the nodeset, the following tags would be used: The element and attribute nodes inside a complexType constraint can be given any of the standard properties of the element tag. Additionally, you can assign an element to a simpleType or another complexType by name. Predefined Types

    The XML Schema has a number of predefined data types that have been designed to handle almost any user input value. Using these types as base values, you should be able to create constraints for any possible situation. The following predefined data types are defined in the XML Schema: § string. Any value containing letters, numbers, punctuation, and white space. § normalizedString. Any string value that does not contain carriage returns, tab characters, or linefeeds. § token. A normalized string that contains no leading or trailing whitespace characters or multiple continuous spaces. § decimal. Any numerical value. § integer. Any decimal that does not include a decimal point or fractions. § byte. Any integer between -128 and 127 inclusive. § unsignedByte. Any integer between 0 and 255 inclusive. § positiveInteger. Any integer larger than 0. § negativeInteger. Any integer less than 0. § nonNegativeInteger. Any integer 0 or larger. § nonPositiveInteger. Any integer 0 or less. § long. Any integer between 9223372036854775807 and - 9223372036854775808 inclusive. § int. Any integer between 2147483647 and -2147483648 inclusive. § unsignedInt. Any integer between 0 and 4294967295 inclusive. § unsignedLong. Any integer between 0 and 18446744073709551615 inclusive § short. Any integer between 32767 and -32768 inclusive. § unsignedShort. Any integer between 0 and 65535 inclusive. § float. A single-precision 32-bit floating point number not more than 2^24 or NaN. § double. A double-precision 64-bit floating point number not more than 2^53 or NaN. § boolean. A value equal to true, false, 1, or 0. § time. A value representing a time of day. The format of this value is dependent on the client. § dateTime. A value representing a date and time of day. The format of this value is dependent on the client. § date. A value representing a date. The format of this value is dependent on the client. § gMonth. A value representing a Gregorian month. § gYear. A value representing a Gregorian year. § gYearMonth. A value representing a Gregorian year and month combination. § gDay. A value representing a Gregorian day. § gMonthDay. A value representing a Gregorian month and day combination. § anyURI. A Web site address or relative URI value. These predefined types can be used by the type attribute of the element tag or assigned using the base attribute of the restriction node. readOnly Besides the type attribute, it is also possible to assign other constraint attributes directly to a bind tag. These values apply to every element attached to the bind node and override any equivalent schema assignments. The first attribute is readOnly. As you might expect, this attribute makes the form value noneditable by the client. This value does not affect the navigation order or the ability to change the focus to the element. Acceptable values are true and false. The default value is false. required This required attribute allows the developer to designate that a form element must have a value before the form can be submitted. A value of true will cause the browser to alert the user that required information has not been entered and the submission process will be halted. The default value is false. relevant The relevant attribute is used to define whether the associated form element data should be submitted with the rest of the form. Assigning a value of false to the relevant attribute will cause the related form data to be purged from the data stream before the information is posted to the server. The default value is true. If the relevant and required attributes are both set to false the form element should be hidden. calculate The calculate attribute allows you to perform mathematical, logical, or string manipulation on an element value. For example, you could have a form field labeled "tax" that always contains the value of the "subtotal" field multiplied by 0.0725. You can also perform simple logical calculations based on a Boolean value and some simple string manipulations, like this:

    Here are a few of the calculation methods possible with the calculate attribute: § if(boolean, value, value). This method takes three parameters, the Boolean value to check, the value to return if the value equates to true, and the value to return if the value equates to false. § string(value). This method converts a value to a string data type. § concat(string, string). This method appends the second parameter to the end of the first. § starts-with(string, string). This method returns a value of true if the first parameter begins with the second. § contains(string, string). This method returns a value of true if the first parameter contains the second. § substring(string, int, int). This method returns a substring from the first parameter starting at the position declared by the second and ending at the optional third. § string-length(string). This method returns the number of characters in a string. § boolean(value). This method converts a value into a Boolean data type. § not(Boolean). This method returns a value of true if the value supplied is false. § number(value). This method converts a value into a number data type. § sum(nodeset). This method returns the sum of the values for the specified nodeset. § floor(number). This method returns the value of the largest integer that is not larger than the number provided. § ceiling(number). This method returns the value of the smallest integer that is not smaller than the number provided. § round(number). This method returns the closest integer to the number. § avg(nodeset). This method returns the average of the values in a nodeset. § min(nodeset). This method returns the lowest value of the values in a nodeset. § max(nodeset). This method returns the highest value of the values in a nodeset. § count-non-empty(nodeset). This method returns the number of non-empty values in a nodeset. § now(). This method returns the current system date and time. isValid The isValid attribute allows you to tie the constraint of one form element to the validation of another. By applying calculation methods to the isValid attribute you can cause a form element to be considered false regardless of the element value if the calculation assigned to the isValid attribute equates to false.

    XForms Events The XForms specification allows a developer to define events directly in the XML code rather than relying on JavaScript or another client-side scripting mechanism. The code of the event model is based on the XML Event specification. This specification describes how events can be captured using the listener tag or assigned directly using the event attribute. Note To use the listener tag, or any of the standard XML Events, you will need to define the XML Events somewhere near the top of the document. The current namespace for XML Events is http://www.w3.org/2001/xml-events. The listener element has eight attributes. Together these determine the functionality of the event. The following is a list of the attributes available for the listener element: § event. This is the name of the event that should activate the listener. § observer. This is an optional attribute that assigns the id value of the parent element to which the event should be assigned. All events qualifying in the observer element will cause the event to process. For example, you could capture all mouse movements in a form using the observer attribute. § target. This is another optional attribute that assigns the id value of the specific element the event was activated from. For example, to capture clicks on a button named clickme, you would assign the target value to clickme. Only events that match the event and target attributes will cause the listener to process the event. § handler. This attribute specifies the location of the action that should occur when the listener captures an appropriate event. This value is in the form of a URI, so actions defined on the same page will require the pound symbol (#). § phase. XML actions occur in two phases. First, the event moves down from the top of the document to the element that is attached to a listener tag in the capture phase. Then it moves back up, or bubbles, to the top of the document. The phase attribute determines the phase in which the event should be captured. A value of "capture" sets the event in the capture phase, "default" places the event in the bubble phase. § propagate. This attribute determines whether event bubbling should continue after the event is activated. The default is "continue". "Stop" causes bubbling to cease. § defaultAction. This handy action defines whether the standard action of a form element should be activated. For example, by setting the defaultAction value of a submit button to "cancel" the code would prevent the form from being submitted. The default value is "perform". § id. This attribute allows you to assign a unique identifier to the listener. You could then reference this value in a form element using the event attribute. The following listener tag would capture the focus event of a text field and activate the "doName" action. You can also capture events directly on the element by using the event attribute. You may also need to assign the other listener attributes such as observer and handler. Of course, in this case, the target value is automatically set to the form element to which the event attribute is assigned.

    Standard Events Whether in a listener tag or directly on the form element, the event attribute takes the name of a defined event. You can create these events using XForms actions, or you can use one of the standard event actions available in the XML Event specification. The following is a list of the events already available in the XML Event specification: § focusIn. This event corresponds to the onFocus event. This event is intended to replace the older focus event. § focusOut. This event corresponds to the onBlur event. This event is intended to replace the older blur event. § activate. This event occurs when an element has been activat ed, whether through a mouse click or a key press. § click. This event corresponds to the onClick event. § mousedown. This event corresponds to the onMouseDown event. § mouseup. This event corresponds to the onMouseUp event. § mouseover. This event occurs when the mouse pointer is moved over the element. § mousemove. This event occurs when the mouse pointer position changes. § mouseout. This event occurs when the mouse pointer moves away from the element. § characterDataModified. This event occurs when the form element’s value has been changed, regardless of the source. § load. This event corresponds to the onLoad event. § unload. This event corresponds to the onUnLoad event. § abort. This event occurs when the loading of the page is stopped prior to completion. § error. This event occurs when an error is encountered in the execution of a client script or when a dependent file such as an image fails to load. § select. This event occurs when an option is selected in the form element. § change. This event corresponds to the onChange event. § submit. This event corresponds to the onSubmit event. § reset. This event corresponds to the onReset event. § focus. This event corresponds to the onFocus event. § blur. This event corresponds to the onBlur event. § resize. This event occurs when the size of the document is resized. § scroll. This event occurs when the user navigates the document using the scrollbars. Initialization Events

    In addition to the events from the XML Event specification, XForms introduces a number of form-specific events. Most of these XForms-specific events fall into two categories: initialization events and interaction events. Initialization events usually occur only once— as the form is loading and the client is creating the XForms elements. The following is a list of the XForms initiation events: § modelConstruct. This event occurs when the client processor creates a form based on the schema defined in the model tag. § modelInitialize. This event occurs when the default values of the form elements are assigned from the instance tags. § initializeDone. This event occurs to indicate that all the default values for the form elements have been assigned. § UIInitialize. This event occurs when a user interface element has caused a form to reinitialize to the default values. § formControlInitialize. This event occurs for each form element that is affected by a UIInitialize event.

    Interaction Events

    The interaction events in the XForms specification allow a developer to fine-tune the event control specifically related to form elements. Many of these events can be simulated using the standard XML Events syntax. Using the XForms interaction events, however, provides a simple shortcut and decreases the amount of additional programming needed. The following is a list of the XForms interaction events: § previous. This event occurs when the client receives a request to move the focus to the preceding element on the page. § next. This event occurs when the client receives a request to move the focus to the next element on the page. § valueChanging. This event occurs when a request is made to change the value of a form element. § valueChanged. This event occurs when the value of a form element is actually changed. § insert. This event occurs when the XForms insert action is activated. § delete. This event occurs when the XForms delete action is activated. § deselect. This event occurs when an option in a select element is deselected. § help. This event occurs when the user requests help information. § hint. This event occurs when the user activates hint information. § alert. This event occurs when the value of a form control fails a validation check. § valid. This event continuously occurs while the value of a form meets the assigned validation specifications. § invalid. This event continuously occurs while the value of a form fails the assigned validation specifications. § refresh. This event occurs when a request is received to update the form elements with the most current information. § revalidate. This event occurs when a request is received to revalidate the form elements based on the current validation specifications. § recalculate. This event occurs when a request is received to recalculate the values of the form elements.

    Error Events

    The XForms specification also includes a few error events that indicate that an error has occurred in the form. These events can be used to provide assistance to the user, for error reporting, or as a method of tracking down a problem in a development environment. The following is a list of the error events defined in the XForms specification: § schemaConstraintsError. This event occurs when an element value falls outside of the constraints defined in the schema tag. § traversalError. This error occurs when a referenced external link is not found. § invalidDatatypeError. This error occurs when an improper data type is used in a calculation function. For example, trying to multiply two string values will result in an invalidDatatypeError event.

    XForms Actions

    Now that you know how to capture events, you can describe the accompanying actions. XForms actions include all the form functionality developers have only been able to emulate using client-side scripts. XForms actions are a set of generic but powerful controls that can be assigned to any form element on the page. You can assign actions to events using the handler attribute or you can attach an event directly to an action using the event attribute. < xforms:setValue>You're here! Where are you? You're gone!" dispatch The dispatch action can be used to send events to other form elements or event nodes. For example, in a particular processing event you need to suspend the default action of a select box when the user clicks on the element. When the processing is done, you can allow the user to continue as if the processing never occurred by sending the click event to the select element or the assigned listener event. The name attribute is the name of the event to pass, and the affected node is defined by the target attribute. You can also define the properties of the event by using the Boolean bubbles and cancelable attributes. refresh, recalculate, and revalidate Each of these actions calls the event of the same name. These actions operate on the form as a whole and thus have no attributes of their own. Usually these actions are limited to use with form buttons or called as a result of a reset or submit event. setFocus The setFocus action allows you to move the insertion point from its current position to a specific form element. You can define the targeted element by assigning its id value to the idref attribute. For example, to move the focus to a text field identified as thisText, you would assign the idref value as follows: loadURI This action causes the XForms interface to load a new document. The href attribute is used to define the location of the new file. You can also specify how the new document should be presented by using the show attribute. The "new" value will cause the document to be loaded in a new window while the "replace" value will cause the document to override the current document. A value of "embed" will cause the document to be included in the current document. Note The loadURI action is based on the XLink specifications. To use this function you will have to designate the XLink namespace somewhere prior to the action tag. The current namespace for XLink is http://www.w3.org/1999/xlink. setValue Probably the most useful XForms action is setValue. This action allows you to change the value of a form element or node to any value. You could assign a specific form element or node the value of another element, the result of a calculation, or a string literal value. To assign the value of a node or a calculation you use the value attribute. String value can be assigned by including the text between the setValue tags. If the setValue action is located anywhere other than inside the element declaration you must include a ref or bind attribute to assign the target node. String text. submitInstance and resetInstance These two actions allow you to programmatically submit or reset an XForms page. The submitInstance action can perform a standard submit event or you can designate the id value of a submitInfo node using the submitInfo attribute. The only attribute of the resetInstance action is the id value of the model to reset. message This action allows you to present a message to the user. The exact appearance and format of the message is left up to the client. You can define the message inline by encapsulating it inside the message tags, attaching an XLink URI via the href attribute, or by pointing to the value of another node using the ref or bind attributes. You can also define the character of the message by declaring the level. Possible values are "ephemeral", "modeless", and "modal". What do you think you are doing? action The action tag is a little different from the other action elements. Rather than producing a specific action, this tag allows a developer to group multiple actions into a named set. This set can then be referenced by a listener tag or a form element with an event assigned.

    The XForms User Interface

    All of the XForms elements described so far are treated as unconnected units as far as the display interface is concerned. The XForms User Interface, however, provides a way to aggregate form controls into semantic groups. How these groups are displayed is left to the discretion of the client machine. For example, a Web browser might display a group of elements with a common background color or a border, while a PDA might divide the form into pages based on the defined groupings. group The group tag is used as a container to hold related form elements. How the elements are related is left up to the developer, but typically groups are used to designate form elements that should appear close to one another or within a similar display framework. It is also possible to nest groups within other groups. This allows the developer to categorize the form elements for an entire interface. switch The switch is used to define areas of content that may or may not be displayed. Each switch tag contains one or more case elements surrounding XML code. Each code block can be turned on and off using a special action called toggle. The case attribute of the toggle action refers to the id value of the case tag. Each time a toggle action is called for, a specific case tag the display of the enclosed XML code is changed from visible to invisible or vice versa. When a specific case tag is toggled on, any other case elements are automatically turned off. The selected attribute can be used to designate the case element to display when the page loads. Turn on the selections. repeat Often form interfaces contain sets of similar elements that only differ in name and value. Most select boxes, for example, have a list of items that are nearly identical. The XForms User Interface provides a way for these repeating elements to be designated using the repeat tag. The initial population of a repeated nodeset is defined by the instance tag in the model declaration.

    Select an Item List Item List Item Value Once a repeating nodeset has been created, special XForms actions can be used to create or remove individual nodes. To add a node use the insert action; to remove a node, use delete. Each of these actions includes attributes to define the nodeset of the repeating item and an at attribute that defines the location of the node to affect. In addition, the insert action has a position attribute that allows you to determine whether the inserted node will appear "before" or "after" the affected node. Insert bookmark

    Delete bookmark The cursor() method returns an index number representing the current position within the repeating nodeset. You can change the position of the insertion point by using the setRepeatCursor action. To use this action, you must designate the name of the repeat tag, and the index number of the position to which the insertion point should move. The XForms Submit Protocol

    XForms sends form data information to the server in an XML format. While this does allow for improved globalization of the form data, it also makes the processing of the information a bit trickier. Most of the XML processing functions available to server-side scripts is not at the same level of simplicity as standard form processing. For example, in PHP each name-value pair in a traditional form is automatically added to the global variable array. With XML data only a single variable will be created automatically and this will contain the entire XML data set.

    Instance Data XML instance data is modified as the user enters data into an XForms interface. When the form is submitted to the server, this data is transferred in a single XML block. Each element name becomes a wrapper tag for the element value. The format of the instance tag in the model declaration determines the format of the XML output. For example, if you had three XForms elements, each attached to instance nodes named name, email, and newsletter, respectively, the instance data that would be sent to the server might look like this: Dan Ransom [email protected] true

    Accessing XML with PHP The latest version of PHP uses a program called Expat, developed by James Clark, to parse XML into usable values. You can read the PHP documentation for the specifics about the PHP XML parser functions at http://www.php.net/manual/ en/ref.xml.php. Using the instructions located there, I’ve created an XML parsing class. This class will allow you to parse XML post data or a designated file. After running the class, the content data is available in an associative array. $xml_p_parser = new o_ParseXML(); $xml_p_parser->m_Parse("HipHop"); echo $xml_p_parser->p_arrXML["b"]; // results in "Hip"; echo $xml_p_parser->p_arrXML["c"]; // results in "Hop"; XForms instance data that is passed to a PHP page is not held in the $HTTP_POST_VARS array; instead, the entire XML collection is in $HTTP_RAW_ POST_DATA. To parse the results of an XForms submit, you would use the following code: $xml_p_parser = new o_ParseXML(); $xml_p_parser->m_Parse($HTTP_RAW_POST_DATA);

    Here is the XML parser class: class o_ParseXML { var $p_objParser; var $p_arrXML = array(); var $p_strParentTag;

    function o_ParseXML() { $this->p_objParser = xml_parser_create(); }

    function m_Parse($strData) { xml_set_object($this->p_objParser, $this); xml_set_element_handler ($this->p_objParser, "m_OpenTag", "m_CloseTag"); xml_set_character_data_handler ($this->p_objParser, "m_ContentData"); xml_parse($this->p_objParser, $strData); }

    function m_OpenTag($intParser, $strTag, $strAttributes) { $strTag = strtolower($strTag); $this->p_arrXML[$strTag] = ""; $this->p_strCurrentTag = $strTag; }

    function m_ContentData($intParser,$strContentData) { $this->p_arrXML[$this->p_strCurrentTag] = $strContentData; }

    function m_CloseTag($intParser, $strTag) { //; }

    }

    Putting It All Together

    In the Chapter 18 folder on the Website for this book you will find a file called articleupdate.xml. This file attempts to use the XForms specification to simulate the functionality though not the appearance of the article update page from the information systems interface. Keep in mind this file conforms to the XForms standards as they are at the time of this writing. Changes to the specification can invalidate parts of the code. Additionally, none of the current XForms browsers support enough of the specification for this file to work properly. I include it simply as an example of how, in the future, you will be able to create complex and functional form interfaces using only XML and XForms. articleupdate.xml

    Information System Maintenance - {{TITLE}}

    {{ARTICLE_KEY}} {{ACTIVE}} {{ARTICLE_TITLE}} {{section Authors }} {{/section Authors }} {{AUTHOR_SELECTED}} {{ACTIVE_DATE}} {{ARTICLE_DESCRIPTION}} {{section Pages }} {{/section Pages }} {{section SectionInstance }} {{section Sections }} {{/section Sections }} {{/section SectionInstance }}

    What is the new page name? What is the new page name?

    ID Active Title You must enter a title between 3 and 90 characters. Author You must select an author. Active Date You must enter an activation date. Description You must enter a description between 3 and 200 characters.

    /\ Move Up \/ Move Down Article Pages Add Page Edit Page Delete Page

    Page Sections {{section PageSections }} {{/section PageSections }}

    /\ Move Up \/ Move Down Add Section Edit Section Delete Section

    Submit Article Reset Form

    Chapter 19: Embedded Web Form Technologies

    Overview

    As you’ve learned, combining server-side scripts with the built-in dynamic form capabilities of the browser allows a developer to create modular, usable, and functional Web interfaces. This process isn’t for everyone, though. Some developers won’t be able to get a handle on the special considerations required for JavaScript development. Others will simply prefer to leverage their knowledge of another technology rather than learning DHTML. Sometimes the project itself has usability requirements or desired functionality that simply cannot be simulated effectively with client-side scripts. In these cases, developers can turn to embedded Web form technologies. An embedded technology, as it relates to the Web, is an application that requires a program or plug-in outside the standard client software. Modern browsers have specific HTML tags that allow a Web developer to insert a reference to an external application. Typically, this is accomplished using the EMBED or OBJECT tags. The client browser that encounters these tags will attempt to start the new process and pass any included parameters or files. If the application requested cannot be found, the results will depend on the configuration of the client and the specific application desired. The browser may present the user an error message, an option to download the needed files, the designated alternative HTML, or nothing at all.

    Embedded Web forms rely on embedded technologies to present a form interface to the user. This chapter details three of the most prominent embedded Web form technologies and the advantages and disadvantages of each.

    Flash Forms Many developers would be surprised at the amount of control that a Flash-based form interface can offer. Flash files, called movies, are developed using Macromedia’s proprietary development environment. The latest version, Flash 5, includes extensive support for ActionScript, a scripting language similar in format to JavaScript. Of all the embedded technologies, Flash is the easiest for a beginner to learn. At the same time, the wizards and gadgets make it the most frustrating for developers experienced in more traditional programming environments.

    Flash gives developers a free hand to control the display and functionality of an entire interface. The frame, layer, scene methodology in Flash allows any movie file to easily include multi-page forms, dragable elements, custom buttons, pop-up messages, and subpages. The trade-off is that most flash interfaces require a degree of aptitude in graphic arts and presentation. This requirement can pose a serious limitation for the average Web programmer.

    Following are the advantages of using Flash: § Flash is easy to learn. It has a simple, user-friendly interface and the basics can be learned in a single weekend. § ActionScript is very similar to JavaScript and the two can communicate directly with modern browsers. § Flash files are typically very small and run quickly. § Most users already have Flash plug-ins installed. The most popular browsers come with Flash included. § Flash’s graphical environment allows for a great deal of flexibility in the display and format of a Web form. § Flash interfaces are not platform- or client -specific. A Flash interface designed for a Flash 5 plug-in will work exactly the same on any browser that has that plug-in installed. § Most browsers will automatically prompt the user to download the Flash plug- in if it is not found. § Following are the disadvantages of using Flash: § Flash is difficult to master. The Flash scripting environment can be finicky, requiring extensive use of the guess-and-check method of programming. Additionally, Flash scripting requires the use of environmental elements that may be awkward for traditional developers, including the use of movie- clips, scenes, and frames. § The Flash development environment is not free. As of this writing, Flash 5 retails for about $400. Of course, the Flash player plug-in is free. § Flash’s server connectivity, as it relates to form elements, is limited and awkward. File uploads are not possible and creating dynamic elements based on database information is problematic. § Flash form controls can be difficult to understand and apply. § Flash development requires a degree of both programming and graphical skills. It can be difficult to achieve this mix in a single individual—and cooperative Flash development is extraordinarily difficult and often ungainly.

    Here’s a typical example of how a Flash movie might be added to a Web page:

    ActiveX Forms

    ActiveX controls have a bad reputation in the Internet development community—and for good reason. The fact that the technology was created and is backed by Microsoft doesn’t help either. ActiveX is a set of controls and development tools that developers can use to apply Widows-based applications to a Web interface. This is a good thing from the point of view that basically any application that will run in Windows can be embedded inside Internet Explorer. On the other hand, ActiveX controls limit the interface to a specific audience and open the client to serious security vulnerabilities. Both of these things are big no-nos for most Internet developers.

    Internet Explorer for Windows gives the developer access to a number of ready-made ActiveX controls, but the majority of interfaces will require a custom-built control. When one of these browsers encounters custom-built ActiveX controls it will present the user with a security warning that is hard to match for intimidation and obt use verbiage. Having the ActiveX application signed by a CA can reduce the brashness of the message, but can’t get rid of it completely.

    Most developers of ActiveX controls come from a Windows programming background. The ease that this technology can be implemented using the tools and environment available from Microsoft makes it an obvious choice for these programmers. Additionally, the package of included form tools and controls available in ActiveX is unmatched for both volume and capability.

    Following are the advantages of using ActiveX controls: § A large number of ready -made ActiveX form controls already exist. For the most part, these can be implemented easily in a typical interface. § ActiveX controls allow you to use real programming languages such as C+ or Visual Basic. This increases the potential power and flexibility of your interface. § ActiveX controls can take advantage of virtually any process available on the client system. § Once an ActiveX control has been installed on the client, integration with the browser is seamless. § Following are the disadvantages of using ActiveX controls: § ActiveX controls only work natively for users of Internet Explorer who have a Microsoft operating system. Plug-ins are available to allow some ActiveX controls to function in some versions of Netscape, but support on other operating systems or browsers is nonexistent. § Custom ActiveX controls require the user to view and accept a large and intimidating security warning before they can be run the first time. § ActiveX controls can be a security hazard for clients. Buggy or malicious code can trash a client system faster than most viruses. § Implementing ActiveX controls on a Web page requires the use of a specific large, unintelligible registration code. This unnecessarily complicates a process that is relatively simple for every other embedded technology. § Developing custom ActiveX controls requires the use of large and expensive development environments, such as Visual Studio. § Microsoft has a history of repeatedly altering the terminology and back-end technology associated with ActiveX controls. This makes it difficult for a developer to keep up-to-date when using ActiveX controls.

    Here’s an example of how you might add an ActiveX control to a Web page:

    Java Applet Forms

    To be honest, I’m not a fan of Java on the Web. Sun introduced Java with the goal of putting an end to client-specific programming. Java was supposed to open the Web to greater functionality and accessibility without compromising security, but it has been only marginally successful in this regard. The Java language is based on C, but rather than being precompiled, Java code sits in simple text files that are compiled at runtime. This is both the primary advantage of using Java and the reason many developers are leaving the Java applet community. To run a Java applet the client must have a Java compiler, called a runtime engine, installed. Many operating systems come with a Java runtime engine, but not all. Clients that don’t have the engine or that require an upgrade will have to manually download and install the files. By providing a free runtime engine for virtually every operating system, Sun ensures that the simple text code of a Java applet can be read and implemented regardless of the client’s configuration.

    On the other hand, the small size and simplicity of Java files is offset by the fact that they must be compiled at the client. Here’s the basic program flow: First, the client encounters the code that embeds a Java applet in a Web page. Second, the Java runtime engine is started. Then, the engine requests the Java file from the server. Next, the runtime engine compiles the Java code. Finally, the results are sent to the browser for display. You can see the added processing and overhead required to run even a simple Java applet. Complex or large Java applets can be a big drag on client resources and can be unbearably slow to initialize and run.

    Following are the advantages of using Java applets: § Java applets can be run on virtually any operating system and most modern browsers. Applets will work exactly the same regardless of the client configuration. § Java applets use Java, a very portable and well-documented object-oriented language. § Most Java development environments are free or very inexpensive. Java code can be written in any text editor. § The application that runs Java applets, the Java runtime environment, has a large installation base. § Java applets run in a protected environment, called a sandbox, that prevents applets from seriously damaging a client system. § Because applets are simple text files, Java’s server connectivity is outstanding. Combining Java applets with a JSP server creates an even more dynamic interface. § Following are the disadvantages of using Java applets: § The Java runtime environment is very large. For clients that do not have it installed, the download and installation can be problematic. § Because Java applets must be compiled at runtime, they tend to be relatively slow and resource-heavy compared to other embedded technologies. § The sandbox environment prevents Java applets from taking advantage of other applications or files on the client system. § Because Java applets share a single set of predefined form controls, applet interfaces all tend to look very similar. That can make it difficult to distinguish your application from the rest of the crowd. § Java is not the easiest language to learn and beginners often get bogged down in the complexities.:

    Here’s an example of how a Java applet could be added to a Web page:
    Your browser is not Java capable or Java has been disabled.

    A Final Word

    By now you should have all of the skills you need to create your own dynamic Web forms. If you want to use the code from this book for your projects, feel free to do so. That’s how it is intended. But, even if you don’t want to use the code or techniques, I hope this book has given you a few worthwhile suggestions or some new ideas that you can use to improve your next Web interface. Just keep in mind the fundamentals of dynamic Web forms: modularity, usability, and functionality and you’ll do fine.

    Part VI: Appendices

    Appendix List Appendix A: HTML Form Elements Reference Appendix B: Recommended Naming Conventions Appendix C: Resources for Web Forms

    Appendix A: HTML Form Elements Reference Table A.1: HTML Form Elements Element Require Attributes Defaul Events d Optional t

    Form action Name onSubmit

    Id onReset target application
    title /x-www- accept- form- charset urlencoded enctype GET method

    Submit type name onClick

    [*] notab onBlur tabindex Table A.1: HTML Form Elements Element Require Attributes Defaul Events d Optional t accesske y

    Reset type name onClick id onMouseOver value onFocus notab onBlur tabindex accesske y

    Button type name onClick id onMouseOver notab onBlur tabindex accesske y

    Image type name onClick src id onMouseOver value onFocus alt onBlur align width height hspace vspace border usemap notab tabindex accesske y

    Single-line Text type id onClick

    size onChange maxlengt onFocus h onBlur disabled notab tabindex

    Password type id onClick

    name h onFocus disabled onBlur notab tabindex

    Multi-line Text name id onClick title onMouseOver value onMouseOut cols onChange Table A.1: HTML Form Elements Element Require Attributes Defaul Events d Optional t rows onFocus wrap onBlur notab tabindex

    Radio Button name id onClick

    type=“radio”> value onMouseOut checked onChange notab onFocus tabindex onBlur accesske y

    Checkbox name id OnClick title onMouseOver notab onFocus tabindex onBlur accesske y

    Select Box name id onClick title onMouseOver notab onChange tabindex onFocus accesske onBlur y

    Select Box value id onSelect title Option selected

    Hidden type id

    [!] FileUpload type id onClick name title onMouseOver notab onChange tabindex onFocus accesske onBlur y [*]This attribute is not available in some browsers.

    [!]This form element requires method="post" and enctype="multipart/form-data" in the FORM tag.

    Appendix B: Recommended Naming Conventions Table B.1: Variable Naming Conventions Data Type Prefix Example

    byte byte byteTool

    short shrt shrtField

    integer int intLocation

    long long longMole

    float flt floatPi

    double dbl dblMolePi

    character char charFirstLetter

    Boolean bool boolTrue

    date date dateYesterday

    point pnt pntLocation

    time time timeNoon

    void or null void voidNull

    string str strName

    hexadecimal hex hexRedColor

    octal decimal oct octIPAddress

    array arr arrFormElements

    object obj objTextBox

    variant or unknown var varUserInput Table B.2: Object and File Naming Conventions Type Extension Example

    Class or Object o_ o_Validate

    Function f_ f_valName()

    Property p_ p_strOutputContents

    Method m_ m_LoadFile()

    External Output _x f_showHelp_x() Included File i_ i_connect.php Action Page File a_ a_login.php Toolbox File t_ t_help.jss PHP File .php home.php JavaScript File .jss i_info.jss CSS File .css style.css Template File .tpl catalog.tpl HTML File .html index.html

    Appendix C: Resources for Web Forms

    Usability and Accessibility Web Sites useit.com: Jakob Nielsen’s Web site http://www.useit.com/

    Jacob Nielsen’s site allows you to learn from his many years of experience in information technology usability. He has a large collection of articles, advice, and interviews about usability. This should be the first stop for anyone serious about usability on the Web. User Interface Engineering http://world.std.com/~uieweb/

    This group provides courses, publications, reports, and conferences about User Interface Engineering. Developers serious about usability design will find this the best place for advanced training. Free usability articles are also available on the site. Yale Web Style Guide http://info.med.yale.edu/caim/manual/

    This site attempts to bring to the Web the same kind of uniformity seen in newspapers, magazines, and books by presenting a recommended style guide. The guide covers everything from typography to graphic standards. AskTog http://www.asktog.com/

    Bruce Tognazzini is an author, usability designer, and lecturer. His site includes a monthly column on user interface design. He also posts his answers to user-submitted e- mail questions. Usable Web http://usableweb.com/

    This site has collected hundreds of links to usability Web sites, articles, books, and events. Section 508 and the Rehabilitation Act http://www.section508.gov/

    Section 508 and the Rehabilitation Act establishes accessibility requirements for any electronic documents or applications developed, maintained, procured, or used by the federal government. Web Accessibility Initiative http://www.w3.org/WAI/

    The World Wide Web Consortium has begun the Web Accessibility Initiative (WAI), which is designed to promote acceptable Internet functionality for everyone, regardless of disability.

    Security Web Sites CERT Coordination Center http://www.cert.org/

    This research and development center handles all manner of Internet security issues. They provide information, assistance, and training. Privacy.net http://www.privacy.net/

    This consumer information organization includes a number of links and tools relating to issues of privacy, including banner ad tracking, encryption, and virus control. SecurityFocus http://www.securityfocus.com/ SecurityFocus is one of the leading providers of security intelligence products and services for businesses. SecurityFocus also hosts a popular security community mailing list, and publishes original security content on its Web site. TRUSTe http://www.truste.com/

    TRUSTe is a privacy mediator that produces products designed to establish trusting online relationships based on the design, sharing, and use of acceptable policies. VeriSign http://www.verisign.com/

    Perhaps the most popular of the commercial Certificate Authorities on the Web, VeriSign provides SSL certificates, server security solutions, and a host of other development tools. SSL 3.0 Specification http://home.netscape.com/eng/ssl3/index.html

    Here you will be able to find detailed technical information about how the SSL protocol functions. OpenSSL http://www.openssl.org/

    This freeware SSL program allows a server to create a secure connection using the SSL protocol. Mod_ssl http://www.modssl.org/

    This is an Apache mod that allows the server to use OpenSSL. Client Authentication with SSL http://www.freebsddiary.org/openssl-client-authentication.php

    This article covers the concepts of client authentication and the SSL protocol in more depth.

    Version Control Web Sites Concurrent Version System (CVS) http://www.cvshome.org/

    CVS is the most popular version control application available today. This freeware product includes both concurrent versioning and revision control and works on many platforms including Windows, Linux, and Macintosh. CVS runs from the command line, but third-party products are available that allow for a GUI interface. Microsoft Visual Source Safe http://msdn.microsoft.com/ssafe/

    Developers familiar with Microsoft’s Visual Studio family of products will appreciate the integration of this version control system with the development tools with which they are already familiar. Source Safe can also work with an SQL database to provide some version control functions for your database. Code Co-op http://www.relisoft.com/co_op/index.htm

    Code Co-op is a low-cost Windows version control system that doesn’t require a central server. This may make it easier for inexperienced developers to set up and manage than the server-based version control alternatives. SourceJammer http://sourcejammer.org/

    SourceJammer is a new freeware source control and versioning system written in Java. It consists of both server-side components and client-side components. Because the program is written in Java it should work on any system that has the Java virtual machine installed. Alienbrain http://www.nxn-software.com/7.0/main.html

    Alienbrain calls itself a “digital media management” system. It combines such functions as file management, version control, process automation, and project tracking. An important difference between this product and other version control systems is the amount of focus it gives to images and other media files. Multi- project development teams that include many designers may find the media aspects of this application worth the added expense.

    JavaScript Web Sites SiteExperts http://www.siteexperts.com/

    This site contains a wealth of JavaScript and DHTML information, including tutorials, code examples, and support forums. JavaScript Source http://javascript.internet.com/

    The JavaScript Source is an excellent JavaScript resource with tons of free cut-and- paste JavaScript examples for your Web pages. Bookmarklets http://www.bookmarklets.com/

    Bookmarklets are JavaScript commands saved as bookmarks in the browser. Common uses for bookmarklets include resizing your browser, navigating to pages in the browser’s history, or changing the appearance of the page. XS DHTML Editor http://sourceforge.net/projects/xsdheditor/

    This open source tool allows developers to include a rich-text editor on any Web page.

    PHP Web Sites PHP Source Files and Documentation http://www.php.net/

    This is the home page for the PHP distribution files. The Zend Engine http://www.zend.com/

    Here you can find out how PHP works and information about future developments involving the PHP Zend Engine. PHP Builder http://www.phpbuilder.com/

    This site is the first place to look for PHP articles and support forums. Browscap.ini http://www.aspsimply.com/info/infoserver.asp If you will be relying on the get_browser() function you will need to keep the browscap.ini file up to date with all the latest browsers. Here you can download a recently updated file.

    Database Web Sites MySQL http://www.mysql.com/

    Here you can find information, documentation, and downloads for this popular freeware program. SQL Tutorial http://www.sqlcourse.com/

    This site gives a brief tutorial on SQL. The site also provides links to in-depth coverage for more advanced students.

    XForms Web Sites XForms Specification http://www.w3.org/MarkUp/Forms/

    This page, maintained by the W3C, holds all sorts of information and links for the XForms specification. XForms 1.0 Reference http://www.zvon.org/xxl/XForms1.0/Output/

    This site contains a detailed outline of the XForms tag library. Here you can look up any XForms tag, action, or event and find the associated child nodes, attributes, and relationships. X-Smiles http://www.xsmiles.org/

    Here, you can download this open-source, Java-based XML browser. Currently, the beta browser supports 75 percent of the XForms specification with much of the remaining functionality expected to be included before the final release. XML WebAccess http://www.mozquito.com/

    Mozquito’s XML user interface enhances standard Web browsers to allow for the integration of XForms and other XML specifications. XML WebAccess is a commercial product available for both IIS and Apache. LiquidOffice http://www.cardiff.com/LiquidOffice/

    The LiquidOffice eForm Management System from Cardiff includes many of the XML form definitions and XML data interchanges from the XForms Submit Protocol.

    Other Useful Web Sites The World Wide Web Consortium (W3C) http://www.w3.org/

    The World Wide Web Consortium is an international committee of programmers, developers, information architects, and Internet leaders who develop standardized protocols for Internet-based technologies. By creating a standard code base they promote interoperability, expandability, and accessibility throughout the Web. Most famous for recommendations of HTML, the W3C also is the leader in standardizing XML, CSS, PNG, and DOM. Browser Evolt http://browsers.evolt.org/

    This site holds a treasure trove of old, international, and rare Web browsers. Forms Tutorials http://www.htmlgoodies.com/tutors/fm.html

    This site includes a number of tutorials to help the beginner understand and implement simple form interfaces.

    Books Jacob Nielsen, Designing Web Usability, Indianapolis, IN: New Riders Publishing, December 1999.

    This book collects the best information from Jacob Nielsen’s Website into one source. Nielsen provides his insights into page design, site design, accessibility, internationalization, and more. Jennifer Fleming, Web Navigation: Designing the User Experience, Sebastopol, CA: O’Reilly, September 1998.

    This book, written for the experienced developer, takes you through the basics of user- centered design. Fleming gives numerous tips and examples of how to improve site usability. Patrick J. Lynch and Sarah Horton, Web Style Guide: Basic Design Principles for Creating Web Sites, New Haven, CT: Yale University Press, 1999.

    Lynch and Horton have compiled here the most important information from their online Web style resource. Philip Greenspun, Philip and Alex’s Guide to Web Publishing, San Francisco, CA: Morgan Kaufmann Publishers, 1999.

    This book provides invaluable advice on producing usable, functional, and enjoyable Websites. Greenspun shares numerous insights about users and their desires that he has gained from his many years as a site developer. This full color book is a must for any Web developer whether beginner, intermediate, or advanced. Danny Goodman, JavaScript Bible, 4th Edition, New York, NY: Hungry Minds, 2001.

    This book is the most comprehensive book on JavaScript available today. Goodman includes hundreds of working code examples. Jesus Catagnetto, Harish Rawat, Sascha Schumannm Chris Scollo, Deepak Veliath, Professional PHP Programming, Birmingham, UK: Wrox Press, 1999.

    This is a good book for developers wishing to learn more about PHP.

    List of Figures Chapter 2: Dynamic Web Form Concepts and Considerations Figure 2.1: Windows 2000 and MacOS X Help screens Figure 2.2: Everyone’s best friend, Clippy Chapter 3: HTML Forms Figure 3.1: A mailto form security warning Figure 3.2: Textarea-form.html on different browsers Chapter 4: JavaScript and Web Forms Figure 4.1: Mosaic displays JavaScript comments as though they were uncommented. Chapter 5: Server-Side Scripting and Relational Databases Figure 5.1: What kind of name is “Submit” anyway? Chapter 7: Project 1 Preparation: Designing for Modularization Figure 7.1: The contents of the membership table Figure 7.2: The combined contents of the membership and memberDemographics tables Chapter 8: Creating the Forms Figure 8.1: The Login page Figure 8.2: The Registration page Figure 8.3: The Member Update page Figure 8.4: The Password Reminder page Figure 8.5: The Home page Figure 8.6: The Account Display page Figure 8.7: The Password Confirmation page Figure 8.8: The Help page Chapter 10: User Interface Design Figure 10.1: The Darwin terminal for MacOS X Figure 10.2: The Google Advanced Search page Figure 10.3: The Windows 2000 GUI Figure 10.4: A form interface from Netscape Navigator Figure 10.5: The fieldset and legend tags in action Chapter 11: Designing a User Interface Figure 11.1: Netscape 6 security warning Figure 11.2: Internet Explorer 6 security warning Figure 11.3: Opera 5 security warning Figure 11.4: IE mixed security warning Chapter 12: Creating the Online Catalog Interface Figure 12.1: The Login page Figure 12.2: The Item Selector page Figure 12.3: The Item Add/Edit page Figure 12.4: The Item Delete page Figure 12.5: The Image Selector subpage Figure 12.6: The Image Add/Edit subpage Figure 12.7: The Image Delete subpage Figure 12.8: The Category Selector “Lone” page Figure 12.9: The Add/Edit Category “Lone” page Figure 12.10: The Category Delete “Lone” page Figure 12.11: The Shopping Cart page Figure 12.12: The Checkout page Figure 12.13: The Delete Confirmation page Figure 12.14: The Catalog page Figure 12.15: The Category page Figure 12.16: The Item page Figure 12.17: The Order Confirmation page Chapter 14: Functional Design Figure 14.1: Select box searching Figure 14.2: The XS DHTML Editor Figure 14.3: The Calendar tool Chapter 16: Creating the Online Information System Itself Figure 16.1: The Update User page Figure 16.2: The Help page Figure 16.3: The Featured Articles page Figure 16.4: The Article page with feedback Figure 16.5: The Search Results page Figure 16.6: Submitting a comment Figure 16.7: Deactivating a comment Figure 16.8: The Update Article page Figure 16.9: The Update Section page Figure 16.10: The Feature Articles page Chapter 17: Completing the Information System Interface Figure 17.1: Categorized search results from Google.com Figure 17.2: How are you feeling today? Chapter 18: XML and XForms Figure 18.1: The X-Smiles browser

    List of Tables Chapter 2: Dynamic Web Form Concepts and Considerations Table 2.1: Variable Prefixes Chapter 4: JavaScript and Web Forms Table 4.1: Comparison Operators Table 4.2: Mathematical Operators Table 4.3: Assignment Operators Table 4.4: Boolean Operators Table 4.5: Special String Characters Chapter 5: Server-Side Scripting and Relational Databases Table 5.1: PHP File Open Modes Table 5.2: PHP-Oracle Connectivity Table 5.3: PHP-Informix Connectivity Table 5.4: PHP-PostgreSQL Connectivity Table 5.5: PHP-ODBC Connectivity Table 5.6: PHP-MySQL Connectivity Chapter 8: Creating the Forms Table 8.1: Form Pages Table 8.2: Action Pages Table 8.3: Display Pages Chapter 10: User Interface Design Table 10.1: JavaScript Navigator Properties Chapter 12: Creating the Online Catalog Interface Table 12.1: Form Pages Table 12.2: Action Pages Table 12.3: Display Pages Chapter 13: Completing the Catalog Interface Table 13.1: Client Testing Configurations Chapter 15: Designing a Functional Interface Table 15.1: Information Systems Action Pages Chapter 16: Creating the Online Information System Itself Table 16.1: Restricted Pages Table 16.2: Article Listing SQL Chapter 18: XML and XForms Table 18.1: Namespaces Appendix A: HTML Form Elements Reference Table A.1: HTML Form Elements Appendix B: Recommended Naming Conventions Table B.1: Variable Naming Conventions Table B.2: Object and File Naming Conventions

    List of Sidebars Introduction New Term Definition Chapter 2: Dynamic Web Form Concepts and Considerations Interface User Interface Chapter 3: HTML Forms SSL Chapter 4: JavaScript and Web Forms Bookmarklets Chapter 5: Server-Side Scripting and Relational Databases W3C Chapter 7: Project 1 Preparation: Designing for Modularization instantiate Chapter 8: Creating the Forms session timestamp Chapter 11: Designing a User Interface stateless Chapter 14: Functional Design WYSIWYG Chapter 18: XML and XForms XML instance data XML Schema namespace