Client Code

This section contains the complete code listing for the Golf Reservation System client application. Every line of code is included here except for the autogenerated code created by Visual Studio.NET. Take special note of the code listing for CourseDetail.aspx.cs because this is the only listing that includes this autogenerated code, and it is necessary in creating Web forms. The .aspx files (our result XML) contains only one line of autogenerated code and is here in its complete form.

All files in Golf Reservation System Client follow this convention: the Class-behind file is named identical to the .aspx file with ".cs" appended. This format is not required, but it is standard and recommended. The templates, if you use Visual Studio .NET as an IDE, will default the names in this pattern.

As we begin to cover the code, note that each file is listed with its .aspx.cs file first, followed by its .aspx file. In a few cases no .aspx.cs file is necessary. Together, an .aspx.cs file and an .aspx file of the same name are called a "Web form" by the .Net Framework. Without the controller design pattern, each page viewed by the user is the server-side generated result of the Web form files.

The controller design pattern adds one more server-side step to the cycle. In this pattern the .aspx result returned is well-formed XML. The Web browser (or other Internet device) receives the result of this XML transformed by the XSLT skin. In the Golf Reservation System client application, ie5.xsl is responsible for transforming every page. A production application could have multiple skins, based on user preferences, target devices, and so forth.

Web Form Code

If you are familiar with ASP, you should also be familiar with most of the base syntax and method calls in this application. Reviewing the robust functionality of ASP is outside the scope of this book, but we will go into detail in the places where we use specific ASP functionality. Note that nearly all ASP functions are available, and in many cases extended in the .NET Framework development language, C#.

This first file we will look at is CourseDetail. CourseDetail is responsible for retrieving a GolfCourse object from the database given the id in the Request.QueryString object. The Request.QueryString object is a C# object that contains namevalue pairs present in the requested URL. Therefore, CourseDetail expects a URL similar to

 http://localhost/TeeTimes/courseDetail.aspx?id=10001 

Among the methods provided by the Request.QueryString object is the Get method in which you provide a value of type string which corresponds to the name in the namevalue pair expected in the URL. The Get method returns the value portion of the namevalue pair if the pair exists. Otherwise, the method returns a null response. Note that this method always returns a string because arguments present in the URL are always handled as strings, regardless of their form. The id in this example, although an integer, will be cast as a string until the program casts it otherwise. If you want to use a URL value as a datatype other than string, you must explicitly cast it as such.

Redundant code created by Visual Studio .NET has been removed, specifically all using declarations and the private methods Page_Init and InitializeComponent. CourseDetail.aspx.cs, contains these items, but the rest of the example code does not. While these portions are omitted here, the source files in their entirety can be found on the accompanying CD. These samples will not function without applying the generated code.

 using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Drawing; using System.Web; using System.Web.SessionState; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.HtmlControls; 

The using statements are autogenerated by Visual Studio. NET and are required by the .NET Framework. They are present at the top of every .cs file in the Golf Reservation System client application.

 namespace TeeTimesClient.content {  public class courseDetail : System.Web.UI.Page {  // provide a GolfCourse object to each instance protected localhost.GolfCourse gc; 

In this example "localhost" is the name of the machine hosting the GolfCourseService Web Service. As long as a Web reference exists for the Web Service, this machine can be physically located anywhere on the Internet or local network.

This present scope represents all the variables (if defined other than private) that will be made available to instances of this class, specifically, CourseDetail.aspx. CourseDetail.aspx receives from this file a GolfCourse object named gc for manipulation. If the variable gc is not defined in this scope, the .aspx file will not have access to its properties and methods.

The public constructor method with the same name as its file is autogenerated by the IDE and is required by the .NET Framework. This method initializes an instance of an EventHandler to handle user events such as a mouse-click. The Golf Reservation System client application does not take advantage of the EventHandler, but the constructor is nonetheless required as part of the file.

 public courseDetail() { Page.Init += new System.EventHandler(Page_Init); } 

The Page_Load method is also autogenerated by the IDE. This scope includes all the page load functionality you want to create. That is, all logic not called from client event handlers should be implemented in this scope.

 private void Page_Load(object sender, System.EventArgs e) { 

CourseDetail .aspx expects a variable named id to exist in the Request.QueryString and to be formatted correctly. As stated before, Golf Reservation System Client contains virtually no error checking. A production implementation would, of course, contain rigorous validation and error checking.

 // get the id from the url string _id = Request.QueryString.Get("id"); 

This is our first example of how a variable is declared in C#, so let's take a closer look. The first portion of code casts the variable as a string. All variables in C# must have a type.

Following the cast, the name of the variable is declared, in this case _id. The variable name can be any alphanumeric string. These two portions alone are enough to declare a variable. Without the equal sign, though, no value is assigned to the variable and it is only a null reference. As you can't manipulate a null reference, you must assign a value to your variable before you call methods. Calling methods on a null reference results in a compile time error.

The far right side of the variable declaration can be an object or an expression that reults in an object, as long as the object type is the same as the variable to which it is being assigned. That is, you cannot assign an integer value to a variable declared as a string, and you cannot assign a string value to a variable declared as, say, a GolfCourse object. This type of error will also result in a compile time error.

The following line instantiates the GolfCourseService, which in this case exists on a networked computer named localhost. The localhost reference will need to be changed to the name of the computer where the Golf Reservation System server application lives. The local variable gs is now an instance of the GolfCourseService, and all methods in the GolfCourseService are now available in this scope of CourseDetail.aspx.cs.

 // instantiate the Web service localhost.GolfCourseService gs = new localhost.GolfCourseService(); 

The variable gc now contains the golfCourse object returned from the call to the GetCourseDetail Web method.

 // assign gc to the appopriate GolfCourse object gc = gs.GetCourseDetail(_id);  } 

The Page_Init function is autogenerated by Visual Studio .NET and should not be modified by the developer.

 private void Page_Init(object sender, EventArgs e) { // // CODEGEN: This call is required by the ASP.NET Web Form Designer. // InitializeComponent(); } #region Web Form Designer generated code /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> private void InitializeComponent() { this.Load += new System.EventHandler(this.Page_Load); } #endregion } } 

Like all .aspx files in Golf Reservation System Client, CourseDetail.aspx is responsible for returning well-formed XML to controller.asp. Notice the extensive XSLT in ie5.xsl used to create a rich UI for the functionality in this page. The result is a page providing general course information (see Figure 10-3) a detailed course description (see Figure 10-4), and a form to search for available tee times (see Figure 10-5).

The <%@ Page> declaration is a page definition required at the top of every .aspx file and is the only autogenerated code we leave there. Modifying this code out of its autogenerated format is unnecessary. Its purpose is to set page-specific parameters such as the language used, the Class-behind or .aspx.cs file, and the class from which the page inherits functionality. Again, this line is autogenerated by Visual Studio .NET and editing its content is unnecessary.

 <%@ Page language="c#" Codebehind="courseDetail.aspx.cs" Auto
EventWireup="false" Inherits="TeeTimesClient.content.courseDetail" %>

Listing 10-1 is based on an agreed upon format between the XSL developer and the ASPX developer. The initial portion of the XML provides general page layout direction while the core of the document provides object-specific data—in this case, courseDetail information.

Notice that, until this point, the XML is layout-specific, that is, it describes the layout of the page instead of the object (or group of objects) to be formatted by the XSLT. This, again, is defined by an agreement between the XSLT (UI) and the ASPX (client application logic) developer. Because gc is defined as public scope in the aspx.cs class, this object is available for manipulation by the .aspx file. This is another area where the line between developer and UI designer is blurred.

Listing 10-1 CourseDetail.aspx: Provides an XML representation of the given course compliant with the XSLT model.

 <document> <header> <title>Course Detail</title> </header> <section> <header> <title>Course Detail</title> </header>  ... </section> </document> 

Figure 10-3 CourseDetail.aspx.cs displays GolfCourse object data given a URL variable named id.

Figure 10-4 CourseDetail.aspx.cs breaks the tee groups into individual holes, providing distance, par, and handicap for each. This is a good example of a functionally rich interface developed from object-based XML.

Figure 10-5 CourseDetail.aspx.cs also allows users to search for a tee time by date on this specific course.

The developer of the ASPX page must know what objects are available, what their names will be, and how those objects are defined. In most cases an ASPX page will receive proprietary data types or common data types such as arrays, and the ASPX developer must know how to handle these objects.

Also take note of the C# syntax used to display dynamic content within the .aspx file. ASP developers new to the .NET Framework and Web Services will be happy to know the syntax for such operations is the same as it is for an ASP application. To dynamically display a value, the syntax is

 <%= objectReference.variableName %>  

where variableName is a valid field name for the objectReference.

 <!--- Display general golf Course Information ---> <courseDetail> <id> <%= gc.id %> </id> <name> <%= gc.name %> </name> <description> <%= gc.description %> </description> 

CourseDetail.aspx is split into unique content sections, each defined in the XML as a courseDetailBlock. This particular courseDetailBlock displays the general course information: name, price, and, later, address. Distinguishing between different content sections through XML is necessary to provide the XSLT developer distinct relationships between different blocks of content.

 <courseDetailBlock description="Course Information"> <courseItem type="courseInfo"> <section> <header> <title> <%= gc.name %>: Course Information </title> </header> </section> <price> <%= gc.price %> </price> 

The address object is included as part of the gc GolfCourse object, referred to by gc.address, and contains street, city, state, and postalCode variables. Obviously, the ASPX developer must have a solid understanding of the object types available.

  <!--- Display address object --->  <address>  <address1>  <%= gc.address.street %>  </address1> <city> <%= gc.address.city %> </city> <state> <%= gc.address.state %> </state> <zip> <%= gc.address.postalCode %> </zip> </address> </courseItem> </courseDetailBlock> <!--- Display in another content section specific course information 
including individual tee and hole descriptions ---> <courseDetailBlock description="Course Description"> <courseItem type="holeInfo"> <section> <header> <title> <%= gc.name %>: Course Description </title> </header> </section> <tees>

The tees object returned with gc is an array of the tees associated with the given GolfCourse. A tee object also contains an array of holes, each having a par, distance, and number value. Notice in ie5.xsl that this becomes a grouped list with extremely rich UI functionality. The code here is used to loop over the array of tees and to access and display specific information about each one.

  <% for (int i=0; i < gc.tees.Length; i++) { %> 

To include C# programming blocks, like the for loop, the syntax requires encapsulating the logic between <% and %>. This syntax will be familiar to developers with experience in ASP, but might look awkward to others.

 <tee> <slope> <%= gc.tees[i].slope %> </slope> <description> <%= gc.tees[i].description %> </description> <id> <%= gc.tees[i].id %> </id> <%   // determine complete distance of a tee  // through the sum of it's holes length values int teeDistance = 0; for (int j=0; j < gc.tees[i].holes.Length; j++) { teeDistance = teeDistance + gc.tees[i].holes[j].length; } %> <distance> <%= teeDistance %> </distance>  <!--- Display information about each hole object  in the tee ---> <holes> <% for (int j=0; j < gc.tees[i].holes.Length; j++) {%> 

Each Tee object contains an array of hole objects, as defined by the object schema for the Golf Reservation System Server. We iterate over the array of holes to display each one's significant variables. This loop results in numerous hole nodes, specifically the length of the hole array for this particular tee.

 <hole number="<%=gc.tees[i].holes[j].hole %>"> <par> <%= gc.tees[i].holes[j].par %> </par> <handicap> <%= gc.tees[i].holes[j].handicap %> </handicap> <distance> <%= gc.tees[i].holes[j].length %> </distance> </hole> <% } %> 

Flow control and reading developer intent can be difficult to follow if you have never developed an ASP Web application, but it's not difficult to learn. Notice that we have to wrap our statement-ending } in <% and %>.

 </holes> </tee> <% } %> </tees> </courseItem> </courseDetailBlock>  <!--- Display form for searching tee times ---> <courseDetailBlock description="Schedule A Tee Time"> 

This courseDetailBlock is responsible for providing a form that allows the user to schedule a tee time (should any be available) for this specific course. All forms in the Golf Reservation System client application post to controller.asp, as defined by the controller design pattern. The view argument required by controller.asp is provided by a hidden variable later in the form. The XSLT developer depends on the ASPX developer to signify those inputs that are required by including the mandataory="yes" attribute. Notice also how the XML briefly returns to a layout-specific XML inside the <section> nodes.

 <form action="controller.asp"> <section> <header> <title> <%= gc.name %>. : Schedule a Tee Time </title> </header> <view> <properties> <property description="Course Name"> <text> <%= gc.name %> </text> </property> <property description="Month"> 

The <choice> node is an excellent example of how the ASPX developer can use the agreed-upon DTD to transfer the responsibility of formatting the display of data to the XSLT developer. Of course, no HTML choice node exists. Instead, a few nodes prompt the user for a choice with radio buttons, check box sets, and select boxes. Each of these nodes has distinct characteristics, both in functionality and in aesthetics. The end results, however, are more or less the same: the user is prompted to choose one or more selections from a list of possible options. Therefore, creating a <choice> node in the ASPX XML allows the XSLT developer to choose which HTML form input is most appropriate for the situation.

This instance of the <choice> node prompts the user to choose a month, day, and later a year for reserving a tee time.

  <choice name="month" mandatory="yes"> <option value="01">January</option> <option value="02">February</option> <option value="03">March</option> <option value="04">April</option> <option value="05">May</option> <option value="06">June</option> <option value="07">July</option> <option value="08">August</option> <option value="09">September</option> <option value="10">October</option> <option value="11">November</option> <option value="12">December</option> </choice> </property> <property description="Day"> <choice name="day" mandatory="yes"> <% for(int i=1;i<=31;i++) { %> <option value="<%=i%>"><%=i%></option> <% } %> </choice> </property> <property description="Year"> <%  System.DateTime theYears = System.DateTime.Now;  int thisYear = theYears.Year;  theYears = theYears.AddYears(1);  int nextYear = theYears.Year; %> <choice name="year" mandatory="yes"> <option value="<%=thisYear%>"><%=thisYear%></option> <option value="<%=nextYear%>"><%=nextYear%></option> </choice> </property> 

The mandatory attribute is another example of utilizing the skills of the XSLT developer to implement intended functionality. Using the mandatory attribute informs the XSLT developer that a specific <input> node is required to submit the form. The XSLT developer can then generate whatever form validation code is necessary to ensure that the requirement is met. And given that each Internet device has its own implementation of validation, the responsibility should be left in the hands of the XSLT developer to determine the appropriate method.

The hidden fields in each form are submitted to the action page unbeknownst to the user. Hidden fields are not available for editing to the user, but instead are used as flags to the controller. This form provides a view hidden variable to tell the controller which view should be processed at form submission and also a submitted variable that lets the controller know that this request is a form submission. The courseID is also expected by the viewTeeTimes page, and is another example of the ASPX developer requiring knowledge of the application architecture and each individual file.

 <property description="Username">  <input type="text" name="username" mandatory="yes" /> </property> <property description="Password">  <input type="text" name="password" mandatory="yes" />   <input type="hidden" name="submitted" value="true" /> <input type="hidden" name="view" value="viewTeeTimes" />  <input type="hidden" name="courseId" value="<%=gc.id%>"/> </property> </properties> </view> </section> </form> </courseDetailBlock> </courseDetail> </section> </document> 

Listing 10-2, CourseSearch.aspx is the search form that allows the user to find a course based on any combination of Name, City, State, Country, or Postal Code. CourseSearch has no Class-behind file as it's not a user-defined page. That is, CourseSearch.aspx requires no user information and displays the same for every user. This code only requires a form prompting the user for valid search fields. The form XML is in turn transformed by the skin—ie5.xsl—into a valid HTML form.

Listing 10-2 CourseSearch.aspx: Provides a form prompting the user for course search parameters.

 <%@ Page %> <document> <header> <title>Search For A Golf Course Near You</title> </header> <form action="controller.asp"> <section> <header> <title>Course Search</title> </header> <view> <properties> <property description="Course Name"> <input type="text" name="name" /> </property> <property description="City"> <input type="text" name="city" /> </property> <property description="State"> <input type="text" name="state" /> </property> <property description="Country"> <input type="text" name="country" /> </property> <property description="Postal Code"> <input type="text" name="postalCode" /> </property> <property> <input type="hidden" name="view" value="CourseSearchResult" /> </property> </properties> </view> </section> </form> </document> 

Listing 10-3, CourseSearchResult.aspx.cs, receives form data from CourseSearch.aspx and returns an array of GolfCourses satisfying the provided search parameters. The search parameters are provided by the user in the courseSearch.aspx form. The possible search parameters are Course Name, City, State, Country, and Postal Code.

Listing 10-3 CourseSearchResult.aspx.cs: Provides an array of Golf Course Objects satisfying given search parameters.

 namespace TeeTimesClient { public class CourseSearchResult : System.Web.UI.Page { protected localhost.GolfCourse[] results; private void Page_Load(object sender, System.EventArgs e) {  // instantiate the GolfCourseService Web service localhost.GolfCourseService gs = new localhost.GolfCourseService(); 

The FindGeneric method expects an array of fields and an array of values, with the name and value variables occupying corresponding positions. Position X in the fields array is required to be a valid field for searching (such as name), while the same position in the values array needs to be its corresponding value (such as Wildwood Green). The FindGeneric method provides much stronger functionality than exact text searching and the ASPX developer needs to be aware of this functionality to take advantage of it.

This type of functionality, which is not immediately apparent given the name and method signature, is expected to be well-documented by the server-application developer. In this case FindGeneric accepts the string * as a wild card search parameter and will also accept part of a search string followed by *. The user can provide Wild* as a search parameter and the method will return, for example, a course with the name Wildwood Green. The same convention is applied to all search parameters in this method.

In this code we pull each of the expected arguments out of the Request.QueryString and insert their names into the fields array and their value into the values array. If the value in the Request.QueryString object is null, we place a * in the values array at the appropriate position, which represents a wild card and will satisfy all courses in the database. Thus, an empty search will result in a record set consisting of every course in the database.

 // prepare the variables for calling FindGeneric String[] fields = new String[5]; String[] values = new String[5];  // populate the fields and values arrays fields[0] = "name"; string nameValue = Request.QueryString.Get("name"); // if the value is null, set to `*' if (nameValue == null || nameValue == "")  nameValue = "*"; values[0] = nameValue; fields[1] = "city"; string cityValue = Request.QueryString.Get("city"); if (cityValue == null || cityValue == "")  cityValue = "*"; values[1] = cityValue;   fields[2] = "state"; string stateValue = Request.QueryString.Get("state"); if (stateValue == null || stateValue == "")  stateValue = "*"; values[2] = stateValue; fields[3] = "country"; string countryValue = Request.QueryString.Get("country"); if (countryValue == null || countryValue == "")  countryValue = "*"; values[3] = countryValue; fields[4] = "postalCode"; string postalCodeValue = Request.QueryString.Get("postalCode"); if (postalCodeValue == null || postalCodeValue == "")   postalCodeValue = "*"; values[4] = postalCodeValue; results = gs.FindGeneric(fields, values); } 

Listing 10-4, CourseSearchResult.aspx, iterates over an array of GolfCourse objects and displays them. The array of GolfCourse objects, named results, is provided by the CourseSearchResult.aspx.cs file. The XSLT for this XML provides links to the CourseDetail page for each item in the array.

Listing 10-4 CourseSearchResult.aspx: A loop over the GolfCourse objects which satisfied search parameters and provide links to their appropriate CourseDetail pages.

 <%@ Page language="c#" Codebehind="CourseSearchResult.aspx.cs" 
AutoEventWireup="false" Inherits="TeeTimesClient.WebForm1" %> <document> <header> <title>Course Search Results</title> <description> </description> </header> <section> <header> <title>Search Results</title> </header> <courseList> // for each Golf Course, provide id and general information <% for(int i=0; i < results.Length; i++) { %> <courseListItem> <name> <%= results[i].name %> </name> <id> <%= results[i].id %> </id> <city> <%= results[i].address.city %> </city> <state> <%= results[i].address.state %> </state> </courseListItem> <% } %> </courseList> </section> </document>

Figure 10-6 shows the results of Listing 10-4.

Figure 10-6 The GolfCourse objects that satisfied the search parameters.

Listing 10-5, login.aspx, like CourseSearch.aspx, contains no Class-behind file and is only responsible for prompting input from the user. This file contains two forms, one for a registered user in the database to log into the Golf Reservation System client application, and one for a new golfer to register with Golf Reservation System by providing a username, password, and general user information. This file is only responsible for creating two valid forms prompting the user for input, either a username/password combination or enough data to create a new user record.

Listing 10-5 login.aspx: Provides a form for a user to sign in if they are a registered user or to register if they are not.

 <%@ Page language="c#" Codebehind="login.aspx.cs" 
AutoEventWireup="false" Inherits="TeeTimesClient.content.
myTeeTimes" %> <document> <header> <title>My Tee Times</title> </header> <form action="controller.asp"> <section> <header> <title>Please Log In</title> </header> <view> <description> Log in to to view your saved Tee Times </description> <properties> <property description="Username"> <input type="text" name="username" mandatory="yes" /> </property> <property description="Password"> <input type="text" name="password" mandatory="yes" /> </property> <property> <input type="hidden" name="submitted" value="true" /> <input type="hidden" name="view" value="viewMyTeeTimes" />

Notice that this page contains two distinct forms, each with different hidden view input types. The first form posts to viewMyTeeTimes, while the second form posts to register. We know this by inspecting the hidden input view arguments present in each form.

 </property> </properties> </view> </section> </form> <form action="controller.asp"> <section> <header> <title>Register</title> </header> <view> <description> If you do not yet have a username and password, please provide the 
information below to become a registered user. </description> <properties> <property description="Choose a username"> <input type="text" name="username" mandatory="yes" /> </property> <property description="Choose a Password"> <input type="text" name="password" mandatory="yes" /> </property> <property description="First Name"> <input type="text" name="firstName" mandatory="yes" /> </property> <property description="Last Name"> <input type="text" name="lastName" mandatory="yes" /> </property> <property description="Address"> <input type="text" name="streetAddress" mandatory="yes" /> </property> <property description="City"> <input type="text" name="city" mandatory="yes" /> </property> <property description="State"> <input type="text" name="state" mandatory="yes" /> </property> <property description="Postal Code"> <input type="text" name="postalCode" mandatory="yes" /> </property> <property description="Country"> <input type="text" name="country" mandatory="yes" /> </property> <property description="email"> <input type="text" name="email" mandatory="yes" /> </property> <property description="Telephone"> <input type="text" name="phone" mandatory="yes" /> <input type="hidden" name="submitted" value="true" /> </property> <property> <input type="hidden" name="view" value="register" /> </property> </properties> </view> </section> </form> </document>

The input name attributes are significant in each form in Golf Reservation System Client. These name attributes (that is, name="phone", name="email") must be identical to the variable names expected by the Class-behind file of the .aspx to which each form is posted. This implementation of Golf Reservation System Client assumes all form variables will be present and formatted correctly. This, of course, is not a real-world example. Register.aspx.cs (Listing 10-6) receives information from login.aspx if the user submitted the new user form. This information is used to create a new user in the database using the addGolfer Web method.

Listing 10-6 register.aspx.cs: Creates a new user record based on user input from login.aspx.

 namespace TeeTimesClient.content { public class register : System.Web.UI.Page { protected localhost.Golfer newGolfer; private void Page_Load(object sender, System.EventArgs e) { localhost.GolfCourseService gs = new localhost.GolfCourseService(); // Get all values from the Request.QueryString object string usernameValue = Request.QueryString.Get("username"); string passwordValue = Request.QueryString.Get("password"); string firstNameValue = Request.QueryString.Get("firstName"); string lastNameValue = Request.QueryString.Get("lastName"); string streetAddressValue = Request.QueryString.Get
("streetAddress"); string cityValue = Request.QueryString.Get("city"); string stateValue = Request.QueryString.Get("state"); string postalCodeValue = Request.QueryString.Get("postalCode"); string countryValue = Request.QueryString.Get("country"); string emailValue = Request.QueryString.Get("email"); string phoneValue = Request.QueryString.Get("phone"); // Add a new golfer to the database newGolfer = gs.AddGolfer(firstNameValue,lastNameValue,
streetAddressValue,cityValue,stateValue,postalCodeValue,
countryValue,emailValue,phoneValue,usernameValue,passwordValue); }

Listing 10-7, register.aspx, simply displays a thank-you message to the user after a registration is submitted. Notice the generic XML—no specific object XML is in this file.

Listing 10-7 register.aspx: Displays a thank-you message to the user following the registration routine in register.aspx.cs.

 <%@ Page language="c#" Codebehind="register.aspx.cs" 
AutoEventWireup="false" Inherits="TeeTimesClient.content.register" %> <document> <header> <title>My Tee Times</title> </header> <section> <header> <title>Thank You for Registering</title> </header> <view> <description> Thanks for registering <%= newGolfer.firstName%> - Now that you are a registered user, you can set up Tee Times with
any of our partner Golf Courses. </description> </view> </section> </document>

The scheduleTeeTime file receives a courseId, a golferId, and a System.DateTime object from the Request.QueryString object, builds a registration, and inserts it into the database using the AddTeeTime Web method. It also provides the Golfer, GolfCourse, and TeeTime object to its .aspx file.

Notice the lack of error checking. If the golferIdValue is not present in the Request.QueryString object, or if it's not a valid golferId, Golf Reservation System Client will throw a run-time error. The variable golfer will be a null reference and scheduleTeeTime will function incorrectly.

 scheduleTeeTime.aspx.cs - Build a registration and insert it into the database namespace TeeTimesClient.content { public class scheduleTeeTime : System.Web.UI.Page { protected localhost.TeeTime tt; protected localhost.Golfer golfer; protected localhost.GolfCourse gc; private void Page_Load(object sender, System.EventArgs e) { localhost.GolfCourseService gs = new localhost.GolfCourseService(); int courseIdValue = Int32.Parse(Request.
QueryString.Get("courseId")); int golferIdValue = Int32.Parse(Request.QueryString.Get
("golferId")); System.DateTime dateValue = System.DateTime.Parse
(Request.QueryString.Get("teeTime")); // AddTeeTime requires two System.DateTime objects // The first is parsed for the date, the second for the time tt = gs.AddTeeTime(courseIdValue,golferIdValue,dateValue,dateValue); golfer = gs.FindGolferById(golferIdValue); gc = gs.GetCourseDetail(Request.QueryString.Get("courseid")); }

Listing 10-8, the scheduleTeeTime.aspx file, displays a notification that the selected tee time was inserted into the database. The object-based XML model used in this page affords the XSLT developer design luxury in creating a UI for displaying the tee time. Though ie5.xsl doesn't do much with the scheduleTeeTime.aspx result, a VoiceXML skin could read off the tee time detail, or the skin could hook into a calender client and build a calender entry.

Listing 10-8 scheduleTeeTime.aspx: Displays a thank-you message after a tee time is registered.

 <%@ Page language="c#" Codebehind="scheduleTeeTime.aspx.cs" 
AutoEventWireup="false" Inherits="TeeTimesClient.content.
scheduleTeeTime" %> <document> <header> <title>My Tee Times</title> </header> <section> <header> <title>Tee Time Scheduled</title> </header> <description> Thank you for registering a tee time. The detail of your registration is below. </description> <teeTimeDetail> <golfer> <name> <%= golfer.firstName %> <%= golfer.lastName %> </name> </golfer> <course> <id> <%= gc.id %> </id> <name> <%= gc.name %> </name> </course> <date>

Once an .aspx page has a handle on an object and the developer knows the variable datatype, the complete library of C# methods is available. Here, the System.DateTime object returned with the tee time is parsed into a readable format for the XSLT using the ToShortDateString( ) and ToShortTimeString( ) methods.

 <%= tt.date.ToShortDateString() %> </date> <time> <%= tt.time.ToShortTimeString() %> </time> </teeTimeDetail> </section> </document> 

The viewMyTeeTimes file receives a valid golferID and returns TeeTime objects associated with that user, as well as the associated GolfCourse objects. As an example of error checking, if the golferID is not valid, this class returns a protected Boolean redirect informing the instance .aspx file that the user is invalid and needs to be redirected.

Notice also that GolfCourseService is provided to the .aspx file because it's defined as a protected variable. Choosing where you should perform the Web method calls is an application design decision. The Class-behind files should perform as much business logic as is possible, but in many cases you might choose to implement only getters and setters inside the .aspx.cs and use the .aspx result document instead to perform Web method calls. This is an especially attractive model when implementing the controller design pattern because the .aspx file isn't the final tier of the application. This does, however, place the burden of exception handling on the ASPX developer. Doing so in the Class-behind file might be more logical.

Listing 10-9, viewMyTeeTimes.aspx, is responsible for validating that a username and password are valid and returning an array of tee times for the validated user by calling the FindTeeTimesByGolfer Web method.

Listing 10-9 viewMyTeeTimes.aspx.cs: Validates a user and returns an array of TeeTime objects for that user.

 namespace TeeTimesClient.content { public class viewMyTeeTimes : System.Web.UI.Page { protected localhost.TeeTime[] tt; protected localhost.Golfer golfer; protected localhost.GolfCourse gc; protected bool redirect = false;  // Provide the GolfCourseService to instances protected localhost.GolfCourseService gs = new localhost.
GolfCourseService(); private void Page_Load(object sender, System.EventArgs e) { string usernameValue = Request.QueryString.Get("username"); string passwordValue = Request.QueryString.Get("password"); golfer = gs.ValidateLogin(usernameValue,passwordValue); // if ValidateLogin returns a null reference, set // boolean redirect to true if (golfer == null) { redirect = true; } else { int golferIdValue = golfer.id; tt = gs.FindTeeTimesByGolfer(golferIdValue); } }

Listing 10-10, the viewMyTeeTimes.aspx file, iterates over an array of TeeTime objects for a specific user, displaying the course name (with a link to the courseDetail page for that specific course), date, and time. If the Boolean variable redirect, provided by the ViewTeeTimes.aspx.cs Class-behind file, is true, the array of teeTimes and the golfer values will be null, so the username and password provided by the user failed the validateLogin method in ViewTeeTimes.aspx.cs.

Listing 10-10 viewMyTeeTimes.aspx.cs: Provides a list of registered tee times for a validated user.

 <%@ Page language="c#" Codebehind="viewMyTeeTimes.aspx.cs" 
AutoEventWireup="false" Inherits="TeeTimesClient.content.
viewMyTeeTimes" %> <document> <header> <title>My Tee Times</title> </header> <section> <header> <title>My Tee Times</title> </header> <view> <description> <% if (redirect) Response.Write("Your username and password failed
validation. If you feel this is an error, please try logging in
again. If you are not yet a registered user, please click 'My Tee
Times' above and complete our registration Form."); else Response.Write("Your registered Tee Times Are Listed Below"); %> </description> <% if (!redirect) {%> <myTeeTimes> <% for (int i=0; i<tt.Length;i++) { %> <myTeeTime> <course> <courseId> <%= tt[i].courseId%> </courseId>

Because it is defined in the proper scope as protected, complete Golf-CourseService functionality is provided here in the .aspx page, including the ability to communicate with Web Services. Here we call the toString method on the courseId value in the TeeTime array so we will have a string value to pass into the GetCourseDetail method. We then assign the returned value to our local variable gc.

 <% gc = gs.GetCourseDetail(tt[i].courseId.ToString()); %> <name> <%= gc.name %> </name> </course> <teeTime> <date> <%= tt[i].date.ToShortDateString() %> </date> <time> <%= tt[i].time.ToShortTimeString() %> </time> </teeTime> </myTeeTime> <% } %> </myTeeTimes> <% } %> </view> </section> </document> 

Figure 10-7 shows the result of Listing 10-10.

Figure 10-7 ViewMyTeeTimes displays all tee times associated with a specific user and provides links to courseDetail.aspx for the associated golf courses.

Listing 10-11, the viewTeeTimes file, receives an optional username and password combination, a courseId representing a golf course in the database, and year, month, and day values. It returns an array of available tee times for the selected year, month, and day, and a link to immediately register for those tee times if they are not already registered and if the username and password resolves to a valid user.

Listing 10-12, the viewTeeTimes.aspx file, iterates over an array of TeeTimes for a given course and, if a valid user is returned from the Class-behind file, it displays a link for registering those tee times. If a valid user is not present, a <noRegister> node is appended to the document. If a tee time in the array is already registered for, a <taken > node is appended, but the tee time information is still provided.

Listing 10-11 viewTeeTimes.aspx.cs: Retrieves valid TeeTimes given a courseID. It also returns a valid user if the username and password validate.

 namespace TeeTimesClient.content { public class viewTeeTimes : System.Web.UI.Page { protected localhost.TeeTime[] tt; protected localhost.Golfer golfer; protected localhost.GolfCourse gc; protected int selectedYear; protected int selectedMonth; protected int selectedDay; protected bool redirect = false; private void Page_Load(object sender, System.EventArgs e) { localhost.GolfCourseService gs = new localhost.GolfCourseService(); string usernameValue = Request.QueryString.Get("username"); string passwordValue = Request.QueryString.Get("password"); string courseIdString = Request.QueryString.Get("courseId"); golfer = gs.ValidateLogin(usernameValue,passwordValue); if (golfer == null) { redirect = true; } else { int golferIdValue = golfer.id; } // FindTeeTimesByDate requires the Course Id value to   // be cast as an Int int courseIdValue = Int32.Parse(courseIdString); selectedYear = Int32.Parse(Request.QueryString.Get("year")); selectedMonth = Int32.Parse(Request.QueryString.Get("month")); selectedDay = Int32.Parse(Request.QueryString.Get("day")); System.DateTime dateValue = System.DateTime.Parse
(Request.QueryString.Get("month")+"/"
+Request.QueryString.Get("day")+"/"
+Request.QueryString.Get("year")); tt = gs.FindTeeTimesByDate(courseIdValue,dateValue); gc = gs.GetCourseDetail(courseIdString); }

Listing 10-12 viewTeeTimes.aspx: Iterates over an array of TeeTimes for a specific GolfCourse . If a valid Golfer is not provided, append a <noRegister> node to the XML. Also, if a TeeTime is already registered, append a taken node to the XML.

 <%@ Page language="c#" Codebehind="viewTeeTimes.aspx.cs" 
AutoEventWireup="false" Inherits="TeeTimesClient.content.
viewTeeTimes" %> <document> <header> <title>My Tee Times</title> </header> <section> <header> <title>Select a Tee Time</title> </header> <view> <description> <% <!--- redirect will be true if the username and password provided to ViewTeeTimes.aspx.cs failed validation ---> if (redirect) Response.Write("Your username and password failed
validation. If you feel this is an error, please try logging in
again. If you are not yet a registered user, please click 'My Tee
Times' above and complete our registration Form."); else Response.Write("Please choose a tee time from the list below"); %> </description> <TeeTimes> <courseId> <%=gc.id%> </courseId> <% if (redirect) { <!---If redirect is true, append noRegister node---> Response.Write("<noRegister/>"); } else { <!---Otherwise, provide the golferId---> Response.Write("<golferId>"+golfer.id+"</golferId>"); } %> <% System.DateTime thisTeeTime = new System.DateTime
(selectedYear,selectedMonth,selectedDay,8,0,0,0); System.DateTime lastTeeTime = new System.DateTime
(selectedYear,selectedMonth,selectedDay,17,0,0,0); int taken = 0; while(thisTeeTime.CompareTo(lastTeeTime) != 0) { thisTeeTime = thisTeeTime.AddHours(.5); %> <teeTime> <TeeTimesystemDate> <%=thisTeeTime.ToString()%> </TeeTimesystemDate> <date> <%= thisTeeTime.ToShortDateString() %> </date> <time> <%= thisTeeTime.ToShortTimeString() %> </time> <% for(int i=0;i<tt.Length;i++) { <!---Compare this TeeTime to the TeeTime in this loop iteration of the TeeTimes array tt. If they are equal, the TeeTime is already registered for, append a taken node to the XML ---> string myTime = thisTeeTime.ToShortTimeString(); string takenTime = tt[i].time.ToShortTimeString(); if (myTime.Equals(takenTime)) { %> ` <taken /> <% } } %> </teeTime> <% } %> </TeeTimes> </view> </section> </document>

Figure 10-8 shows the results of Listing 10-12.

Figure 10-8 ViewTeeTimes.aspx displays a list of available tee times for a given course, and if a valid username and password combination is provided, it provides a link to immediately register for the given tee time.

The Controller and ie5.xsl

Listing 10-13, the controller.asp file, is obviously the heart of the Golf Reservation System client application, at least for the UI portion. The controller accepts every request from the client, implements the functionality requested, receives the result XML, and transforms it using the selected skin (in our case, ie5.xsl) and delivers the result to standard out. For detailed information about the controller design pattern, see Chapter 9, which describes it in detail. If you're already familiar with the controller design pattern, notice that the Golf Reservation System client application implements a standard version of the pattern.

Listing 10-13 controller.asp: The file responsible for loading the view XML and transforming it with the XSLT skin.

 <% // Get the view argument // Define root for client application view  = Request.QueryString("view")  base  = Request.ServerVariables("SERVER_NAME")+
Replace(Request.ServerVariables("URL"),"controller.asp","") if Request.ServerVariables("HTTPS") = "ON" Then base = "https://"+base else base = "http://"+base end if xmlurl = base+"content/"+view+".aspx?" arguments = "" // Parse additional arguments For Each item in Request.QueryString if arguments <> "" then arguments = arguments+"&" itemvalue = Request.QueryString(item) arguments = arguments+item+"="+itemvalue Next // Append the argument string to the xmlurl xmlurl = xmlurl+arguments // Construct a URL to the appropriate skin // This implementation uses only the Internet Explorer 5.0 skin // Other skins could be used based on the browser type if Request.QueryString("skin") <> "" then skin = Request.QueryString("skin") else skin = "ie5" end if xslurl = base+"xslt/"+skin+".xsl" // Load the XML // A production implementation should use the multi-threaded
version of the DomDocument. error = false Set source = Server.CreateObject("MSXML2.DOMDocument") source.async = false tmp = source.setProperty("ServerHTTPRequest", true) source.load(xmlurl) Set e = source.parseError if e.errorCode <> 0 then Response.write(e.reason) // Display error if exists if e.line > 0 Then Response.write(e.line) Response.write(" ") Response.write(e.linepos) Response.write(" ") Response.write(e.srcText) end if error = true end if // Load the XSLT Set style = Server.CreateObject("MSXML2.DOMDocument") style.async = false tmp = style.setProperty("ServerHTTPRequest", true) style.load(xslurl) Set e = style.parseError if e.errorCode <> 0 Then Response.write(e.reason) // Display error if exists if e.line > 0 Then Response.write(e.line) Response.write(" ") Response.write(e.linepos) Response.write(" ") Response.write(e.srcText) end if error = true end if if error = false Then xmlresult = source.transformNode(style) end if Response.write(xmlresult) %>

Listing 10-14, the ie5.xsl file, is the only XSLT skin used in this client application. Describing the complete functionality of ie5.xsl in detail is nearly impossible. Basically, this document is loaded with each call to controller.asp (for a larger production application, splitting the skin amongst multiple includes would be a better idea), and is responsible for transforming the XML result returned from .aspx files. The result of the XSLT is, in this case, an HTML document available for viewing in a Web browser. Golf Reservation System Client utilizes only this skin, but could conceivably utilize numerous skins, both for different interface designs as well as for different client devices.

XSLT and its associated technologies are constantly evolving. You can find the latest specifications for XSL, XSLT, and XPath at http://www.w3.org/Style/XSL/.

The initial block of content loaded with each call to ie5.xsl is not template-specific. That is, it's included in the result tree every time. This block is where we place document declarations, such as the <html> and <head> nodes, as well as any scripts or style sheets that need to be included in each result.

Listing 10-14 ie5.xs: The XSLT Responsible for transforming the result view XML.

 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="html" indent="no" /> <xsl:strip-space elements="*" /> <xsl:template match="document"> <html> <head> The css styles for ie5.xsl are included with each view in the 
application at the location css/style.css. Any header/title node can
be inserted as the HTML title node. <link rel="stylesheet" type="text/css" href="./css/style.css" /> <xsl:if test="header/title"> <title> <xsl:value-of select="header/title" /> </title> </xsl:if>

As discussed earlier, each device should include its own form validation mechanism to guarantee that required form inputs are satisfied and formatted correctly. The form validation script used in Golf Reservation System Client is located at js/formvalidate.js and is written using JavaScript. This file is extensive; displaying its entire contents in this chapter would be unreasonable. However, the form validation script could serve as an advanced lesson in developing JavaScript. This file is available on the accompanying CD.

In a production application, JavaScript is not recommended as the form validation method of choice. The primary reason is that the specification is not implemented with precision or consistency between browsers, and because browsers offer the user the ability to turn off this functionality. These arguments should also be taken into consideration while developing the UI. Rich Web UIs are often developed with extensive JavaScript, and therefore might result in unexpected functionality in some browser/platform combinations. For this client we assume the user has Microsoft Internet Explorer 5 or later. Our ie5.xsl includes extensive use of JavaScript, both in the UI and in form validation, and thus segregates those users without standards-based browsers and those who simply choose to turn off JavaScript.

 <script src="/books/4/456/1/html/2/js/formvalidate.js" type="text/javascript"></script> 

The following JavaScript functions are used in CourseDetail.aspx to create a rich UI for viewing course detail information and for allowing the user to switch between content sections without requesting new data from the server. Because CourseDetail page includes three different contentSections, the XML is present and available to the user with a single call to the page. Therefore, the UI designer must find a way to utilize that bulk of information in a manner that is intuitive for the user. In Golf Reservation System Client this is achieved using a tabular format and an expandable and collapsible list grouping.

When including content in the XSLT style sheet that you predict might be poorly formed (that is, the content could not satisfy an XML parser), you need to wrap that content between the following tags: <![CDATA[ poorly-formed XML content goes here ]]>. The following JavaScript is an example. Without the CDATA declaration, ie5.xsl would fail parsing.

The JavaScript function showTab is responsible for the toggling behavior of the tabs present in the CourseDetail page. Each courseDetailBlock is presented as a tab, and clicking on the header will show that courseDetailBlock and hide the others.

 <![CDATA[  function showTab(section, scnt) { for (i=0; i < scnt; i++) { t = document.getElementById("s_"+i); if (t) { if (i == section) { t.style.display = ''; } else { t.style.display = 'none'; } } } } 

The JavaScript function ExpandCollapse is responsible for the toggling behavior of the line items created by the individual Tee descriptions in the CourseDetail page. Each Tee object is presented as an expandable division, and clicking on the division will expand or collapse it.

 function ExpandCollapse(c1,ca) { var ca_a = ca.split(','); var c1_a  = c1.split(','); c1 = document.getElementById(ca_a[1]); if (c1.style.display == 'none') expand = true; else expand = false; thePlus = ca_a[0]+"_plus_image"; plus_div = document.getElementById(thePlus); if (expand) { if (plus_div) plus_div.src = "./images/minus.gif"; for (i=0; i < c1_a.length; i++) { theRow = document.getElementById(c1_a[i]); theRow.style.display = ''; plus_div = document.getElement
ById(c1_a[i]+"_plus_image"); if (plus_div) plus_div.src = "./images/plus.gif"; } } else { if (plus_div) plus_div.src = "./images/plus.gif"; for (i=1; i < ca_a.length; i++) { theRow = document.getElementById(ca_a[i]) theRow.style.display = 'none'; } } } // ]]> </script> </head> <body topmargin="0" leftmargin="0"> The class attributes throughout ie5.xsl refer to CSS style classes defined in the css.style.css file.

In order for HTML content to satisfy the XML parser, the HTML developer should use double quotes for attributes, close all tags (that is, using <br/> instead of <br>), and take note of case sensitivity. An exciting development by the W3C is the ongoing development of XHTML—HTML 4 in the form of valid XML. Conforming to the latest specification of XHTML ensures that your XHTML content will satisfy compliant XML parsers.

The initial division in ie5.xsl with the class "document" is included with each view in the application. This division encapsulates the entire viewable HTML content. Also included with each page in the application is the masthead table, which displays the name of the organization.

 <div > <table  width="600" align="left" cellpadding="0" 
cellspacing="0" bgcolor="ffffff"> <tr > <td> <a href="./index.asp">Golf Reservation System</a> </td> </tr> <tr height="1" bgcolor="000000"> <td> <img src="/books/4/456/1/html/2/./images/spacer.gif" height="1" /> </td> </tr> <tr> <td align="left"> <table cellpadding="2" cellspacing="2" border="0"> <tr> <td>

All anchor references and form posts resolve to controller.asp with a view argument appended (except for links to index.asp). The requirement to do so is defined by, and consistent with, the controller design pattern.

Like the initial document division and masthead table, a table of links is also included with each view in the application. This table provides links to the CourseSearch and Login pages.

 <a  
href="./controller.asp?view=login">My Reservations</a> </td>

All entity references must be well-formed. For example, the common non-breaking space reference in HTML, &nbsp;, will fail an XML parser. Instead, you must fully qualify entity references: &nbsp; becomes &#160;.

The complete set of entity reference values as of HTML 4 can be found courtesy of the W3C at http://www.w3.org/TR/html4/sgml/entities.html.

 <td>&#160;|&#160;</td> <td> <a  href=
"./controller.asp?view=courseSearch">Course Search</a> </td> </tr> </table> </td> </tr> <tr height="1" bgcolor="#000000"> <td> <img src="/books/4/456/1/html/2/./images/spacer.gif" height="1" /> </td> </tr> <tr> <td> <table width="100%" cellpadding="2" cellspacing="2" border="0"> <tr> <td >

The following apply-templates call sets off the xsl:template calls based on the contents of the XML being transformed. From here, each page takes a different path through the XSLT.

Apply-templates instruct the XSLT parser to match all source children nodes of the current node with appropriate template matches provided in the XSLT. For more information on apply-templates and other XSL/XSLT functionality, visit the XSL home page at the W3C located at http://www.w3.org/Style/XSL/.

 <xsl:apply-templates /> </td> </tr> </table> </td> </tr> </table> </div> </body> </html> </xsl:template> <xsl:template match="section"> <xsl:apply-templates /> </xsl:template> <xsl:template match="section/header"> <table width="100%" cellpadding="2" cellspacing="2" 
> <tr> <td> <xsl:value-of select="title" /> </td> </tr> </table> </xsl:template> <xsl:template match="view"> <xsl:apply-templates /> </xsl:template> <xsl:template match="description"> <table cellpadding="2" border="0" > <tr> <td> <xsl:value-of select="." /> </td> </tr> </table> </xsl:template>

For property/value pairs defined in XML we simply display them side by side in a table, with property description in one table cell and property value (or an input, whichever is appropriate) in another. This is an example of presentation-specific XSLT, as opposed to object-specific. The XSLT here has no context of the object it displays. The majority of the forms in Golf Reservation System Client use this approach because the UI for a form is rarely tied to an object in the system; it is simply used to solicit input from the user.

Here we begin to see the power and simplicity provided by XSLT. For every form in the client application the ASPX developer only needed to wrap <input> nodes with a <property> node. Here in ie5.xsl, one template match suffices each <property> node in the entire application: only three lines of code are necessary. Each input type is implemented differently, but <property> nodes are all implemented the same.

 <xsl:template match="properties"> <table cellpadding="2" border="0"> <xsl:apply-templates /> </table> </xsl:template> <xsl:template match="property"> <tr> <td> <font >
<xsl:value-of select="@description" />
&#160;&#160;</font> </td> <td> <font >

The xsl:choose element is provided by XSLT as an if-then-else type statement. The XSLT developer must use his or her discretion on when to use xsl:choose instead of an apply-templates or an xsl:if. Apply-templates is almost always preferred beacuse it takes advantage of the recursive nature of XSLT. Sometimes, such as when we choose whether or not to display an anchor reference for the property, understanding the code is easier if you use an xsl:choose.

This xsl:choose decides if the property value should be an anchor reference based on the existence (or nonexistence) of an href attribute of the current <property> node.

 <xsl:choose> <xsl:when test="@href"> 

To output a value, we can use <xsl:value-of select="node">, or we can imply that value-of by wrapping the node name with braces. In the href case we output the value of the attribute href using braces.

 <a href="{@href}"> <xsl:apply-templates /> </a> </xsl:when> <xsl:otherwise> <xsl:apply-templates /> </xsl:otherwise> </xsl:choose> </font> </td> </tr> </xsl:template> 

The form template match is used on every form in the Golf Reservation System client application and includes JavaScript for input validation. The JavaScript ensures that all inputs with the mandatory attribute set to yes are validated before the form is submitted. Also notice that, as in login.aspx, the ASPX developer can place multiple forms on a single page. The validation still works as each form is assigned a unique identifier through the XSLT function generate-id( ).

The JavaScript function validateForm is good example of using XSLT to create executable code at runtime. ValidateForm is an extremely generic JavaScript function but works well in validating all types of HTML inputs. Notice the use of xsl:for-each. The xsl:for-each element is a looping construct provided by XSLT that executes for each node qualifying for the select attribute. For each textarea node with an attribute of mandatory set to yes, for example, this XSLT creates a JavaScript validation routine that ensures that a value is submitted for that input. If no value exists, the JavaScript sets the browser focus on that input and returns false to the onSubmit call from the form. In doing so it stops the form from being submitted and instructs the user to enter a value.

 <xsl:template match="form"> <script type="text/javascript"> function validateForm_<xsl:value-of select="generate-id()" 
/>(thisForm) { <xsl:for-each select="//input[@type='textarea']"> thisForm.<xsl:value-of select="@name" />.value =
document.getElementById('d<xsl:value-of select="generate-id()"
/>').innerHTML; </xsl:for-each> <xsl:for-each select="//*[@mandatory='yes']"> <xsl:choose> <xsl:when test="@type = 'text' or @type='textarea'"> if (!hasValue(thisForm.<xsl:value-of select="@name"
/>,"text")) { alert("<xsl:value-of select="@name" /> is mandatory."); var the_field = thisForm.<xsl:value-of select="@name" />; if(the_field.style.display != "none" &amp;
!the_field.disabled &amp; !the_field.readOnly &amp;
!the_field.editableDiv) the_field.focus(); return false; } </xsl:when> <xsl:when test="name() = 'choice'"> if (!hasValue(thisForm.<xsl:value-of select="@name"
/>,"select")) { alert("<xsl:value-of select="@name" /> is mandatory."); var the_field = thisForm.<xsl:value-of select="@name" />; if(the_field.style.display != "none" &amp;
!the_field.disabled &amp; !the_field.readOnly &amp;
!the_field.editableDiv) the_field.focus(); return false; } </xsl:when> </xsl:choose> </xsl:for-each> <xsl:for-each select="//input[@datatype]"> if (!isoftype(thisForm.<xsl:value-of select="@name"
/>.value,'<xsl:value-of select="@datatype" />')) { alert("<xsl:value-of select="@name" /> is not in
<xsl:value-of select="@datatype" /> format"); return false; } </xsl:for-each> <xsl:choose> <xsl:when test="@onSubmit"> var rtnval = <xsl:value-of select="@onSubmit" />; return rtnval; </xsl:when> <xsl:otherwise> return true; </xsl:otherwise> </xsl:choose> } </script>

Here the XSLT builds a form tag here and includes an event handler for the onSubmit action, requiring validation before the form is submitted. The apply-templates call in the middle of this routine matches all <property> nodes and all <input> nodes of the source XML. This, again, is a good example of code reuse in XSLT: each form in the entire application is passed through this transformation and you need only write a few lines of code to build every form.

 <form action="{@action}" method="get" onSubmit="return validateForm_{generate-id()}(this)"> <xsl:for-each select="@*[name() != 'action']"> <xsl:copy> <xsl:value-of select="." /> </xsl:copy> </xsl:for-each> <xsl:apply-templates /> <input style="cursor:hand" type="submit" value=" submit " /> </form> </xsl:template> 

Next we build the HTML input types for each <input> node in the XML. The <input> nodes are present in the source view XML and are more or less copied in their initial format.

 <xsl:template match="input[@type='text']"> <input type="text" name="{@name}" value="{@value}"  style="border:inset px"> <xsl:for-each select="@*[name() != 'name' and name() != 'value']"> <xsl:copy> <xsl:value-of select="." /> </xsl:copy> </xsl:for-each> </input> <xsl:if test="@mandatory = 'yes'"> <font >&#160;*</font> </xsl:if> </xsl:template> <xsl:template match="input[@type='hidden']"> <input type="hidden" name="{@name}" value="{@value}"> <xsl:for-each select="@*[name() != 'name' and name() != 'value']"> <xsl:copy> <xsl:value-of select="." /> </xsl:copy> </xsl:for-each> </input> </xsl:template> 

For the <textarea> nodes, the XSLT developer can take advantage of Internet Explorer's content-editable division functionality. The XSLT could simply display a <textarea>, but instead it displays a division with content-editable set to true so users can copy and paste images and the like from any application instead of being restricted to entering only text. When the form is submitted the validation script reads the current contents of this division and sets a form variable equal to that value, and then the form is submitted. (This functionality is available only in Internet Explorer 5.5 or later and will not work in other browsers.)

The following code matches the <textarea> node in the source XML and creates a division with a content-editable attribute set to true. It also chooses the size dimensions of the division based on a size attribute of the <textarea> node in the source XML. It does so using xsl:choose.

 <xsl:template match="input[@type='textarea']"> <div  contentEditable="true" > <xsl:choose> <xsl:when test="@size='small'"> <xsl:attribute name="style">display:inline;
overflow:scroll;width=350;border:solid;border-style:ridge;
border-width:2;background-color:white;height:150</xsl:attribute> </xsl:when> <xsl:when test="@size='medium'"> <xsl:attribute name="style">display:inline;
overflow:scroll;width=350;border:solid;border-style:ridge;
border-width:2;background-color:white;height:250</xsl:attribute> </xsl:when> <xsl:when test="@size='large'"> <xsl:attribute name="style">display:inline;
overflow:scroll;width=350;border:solid;border-style:ridge;
border-width:2;background-color:white;height:350</xsl:attribute> </xsl:when> <xsl:otherwise> <xsl:attribute name="style">display:inline;
overflow:scroll;width=350;border:solid;border-style:ridge;
border-width:2;background-color:white;height:350</xsl:attribute> </xsl:otherwise> </xsl:choose> <xsl:value-of disable-output-escaping="yes" select="value" /> </div> <input type="hidden" editableDiv="yes" name="{@name}" value="{@value}" /> <xsl:if test="@mandatory = 'yes'"> <div style="display:inline"> <font >&#160;*</font> </div> </xsl:if> </xsl:template>

The <choice> node, as discussed earlier, is an excellent example of providing the XSLT developer room to design the appropriate UI. Specifically, the <choice> node can result in either a set of radio buttons, a set of check boxes, a single-select select box, or a multiple-select select box. The decision of which to use is made at runtime.

The xsl:choose statements count the number of options in the source XML and decide what type of form inputs to insert into the result document. xsl:if is then used to decide whether multiple choices will be allowed based on the existence or nonexistence of the allowmultiple attribute in the <choice> node in the source XML.

 <xsl:template match="choice"> <xsl:choose> <xsl:when test="count(option) > 5"> <select > <xsl:for-each select="@*[name() != 'mandatory']"> <xsl:copy> <xsl:value-of select="." /> </xsl:copy> </xsl:for-each> <xsl:if test="@allowmultiple = 'yes'"> <xsl:attribute name="multiple">yes</xsl:attribute> <xsl:attribute name="size">5</xsl:attribute> </xsl:if> <option value="">Please Choose Below</option> <xsl:for-each select="option"> <option> <xsl:attribute name="value"> <xsl:value-of select="@value" /> </xsl:attribute> <xsl:if test="@selected='yes'"> <xsl:attribute name="selected"> <xsl:value-of select="@selected" /> </xsl:attribute> </xsl:if> <xsl:value-of select="." /> </option> </xsl:for-each> </select> </xsl:when> <xsl:otherwise> <div style="display:inline"> <xsl:for-each select="option"> <xsl:choose> <xsl:when test="../@allowmultiple = 'yes'"> <input type="checkbox" name="{../@name}" 
value="{@value}" style="border:0px" /> &#160; <xsl:value-of select="." /><br /> </xsl:when> <xsl:otherwise> <input type="radio" name="{../@name}"
value="{@value}" style="border:0px" /> &#160; <xsl:value-of select="." /><br /> </xsl:otherwise> </xsl:choose> </xsl:for-each> </div> </xsl:otherwise> </xsl:choose> <xsl:if test="@mandatory = 'yes'"> <div style="display:inline"> <font >&#160;*</font> </div> </xsl:if> </xsl:template>

We begin the object-specific XSLT with the xsl:template match for course. In this case the template will only match for the CourseSearchResult result where the XSLT receives a list of courses as children of a <courseList> node. For each course we toggle the background color and provide links to courseDetail.

The template match for courseList provides an HTML table to surround the course list, supplies an apply-templates call to match all <courseListItem> nodes, and displays the number of <courseListItem> nodes in the last row of the table. The template match for <courseListItem> displays the name, city, and state for each node. The name is also provided with an anchor reference allowing the user to click through to get specific detail about that course. The link has an appended id argument as is required by the target view, courseDetail.

 <xsl:template match="courseList"> <table  width="100%" cellspacing="0" cellpadding="0" border="0"> <xsl:apply-templates select="courseListItem" /> <xsl:if test="count(courseListItem) != 0"> <tr> <td colspan="3" height="1" bgcolor="#000000"> <img src="/books/4/456/1/html/2/./images/spacer.gif" height="1" /> </td> </tr> </xsl:if> <tr> <td colspan="3" bgcolor="#ffffff" > <xsl:value-of select="count(courseListItem)" /> course(s) satisfied your criteria </td> </tr> <tr> <td colspan="3" height="1" bgcolor="#000000"> <img src="/books/4/456/1/html/2/./images/spacer.gif" height="1" /> </td> </tr> </table> </xsl:template> <xsl:template match="courseListItem"> <tr> <xsl:choose> <xsl:when test="position() mod 2"> <xsl:attribute name="style"> background-color:#D1D7DC </xsl:attribute> </xsl:when> <xsl:otherwise> <xsl:attribute name="style"> background-color:#f1f1f1 </xsl:attribute> </xsl:otherwise> </xsl:choose> <td  valign="top"> <a href="controller.asp?view=courseDetail&amp;id={id}"> <xsl:value-of select="name" /> </a> </td> <td  valign="top"> <xsl:value-of select="city" /> </td> <td  valign="top"> <xsl:value-of select="state" /> </td> </tr> </xsl:template> 

The courseDetail xsl:template match employs the most extravagant use of HTML and DHTML in the Golf Reservation System client application. This match is selected for each call to courseDetail.aspx.

 <xsl:template match="courseDetail"> <xsl:apply-templates select="courseDetailBlock" /> </xsl:template> 

In the XSLT defined in the following block of code, each courseDetailBlock is assigned a unique id as well as a displayed tab. This tab takes advantage of JavaScript defined previously in the XSLT file to dynamically hide and display different sections of content—in this case, different matches of the <courseDetailBlock> node.

 <xsl:template match="courseDetailBlock"> <xsl:variable name="numsections" 
select="count(../courseDetailBlock)" /> <xsl:variable name="precedingsibs" select="count(
preceding-sibling::courseDetailBlock)" /> <xsl:variable name="sectionid" select="concat('s_',
$precedingsibs)" /> <table cellspacing="0"
cellpadding="2" align="center"> <xsl:if test="$precedingsibs > 0"> <xsl:attribute name="style">display:none</xsl:attribute> </xsl:if> <tr> <td colspan="{$numsections}" height="5"> <img src="/books/4/456/1/html/2/images/spacer.gif" height="5" /> </td> </tr> <tr align="center"> <xsl:for-each select="preceding-sibling::courseDetailBlock"> <td align="center"> <a href="javascript:showTab({
position()-1},{$numsections})"> <xsl:value-of select="@description" /> </a> </td> </xsl:for-each> <td align="center" > <xsl:value-of select="@description" /> </td> <xsl:for-each select="following-sibling::courseDetailBlock"> <td align="center"> <a href="javascript:showTab
({$precedingsibs+position()},{$numsections})"> <xsl:value-of select="@description" /> </a> </td> </xsl:for-each> </tr> <tr> <td> <xsl:attribute name="colspan"> <xsl:value-of select="$numsections" /> </xsl:attribute> <table width="100%" cellpadding="0"
cellspacing="0" border="0"> <tr> <td>

Each courseDetailBlock is applied with the apply-templates call in the following snippet.

 <xsl:apply-templates /> </td> </tr> </table> </td> </tr> </table> </xsl:template> 

The courseItem with attribute type set to courseInfo template match displays generic GolfCourse information such as the address and greens fee.

 <xsl:template match="courseItem[@type='courseInfo']"> <xsl:apply-templates select="section" /> <table > <tr> <td rowspan="3" valign="top">Address:</td> <td> <xsl:value-of select="address/address1" /> </td> </tr> <tr> <td><xsl:value-of select="address/city" />, <xsl:value-of select="address/state" /></td> </tr> <tr> <td> <xsl:value-of select="address/zip" /> </td> </tr> <tr> <td rowspan="3" valign="top">Greens Fee:</td> <td>$<xsl:value-of select="price" /></td> </tr> </table> </xsl:template> 

The holeInfo Block displays hole-specific information such as hole number, par, distance and handicap. It also matches the <tees/tee> node to provide tee-specific information.

 <xsl:template match="courseItem[@type='holeInfo']"> <xsl:apply-templates select="section" /> <table width="100%" cellspacing="0" cellpadding="0"> <tr> <td valign="top" nowrap="yes"> <table width="100%" cellpadding="0" cellspacing="1"> <tr bgcolor="a0a0a0" style="padding:2px;spacing-top:0px"> <td  align="center" style="color:white;
font-weight:bold"> Hole </td> <td align="center" style="color:white;
font-weight:bold"> Par </td> <td align="center" style="color:white;
font-weight:bold"> Distance </td> <td align="center" style="color:white;
font-weight:bold"> Handicap </td> </tr> <xsl:apply-templates select="tees/tee" /> </table> </td> </tr> <tr bgcolor="#336699"> <td height="1"> <img src="/books/4/456/1/html/2/./images/spacer.gif" height="1" /> </td> </tr> </table> </xsl:template>

For each tee associated with a GolfCourse object, this xsl:template match provides an expandable and collapsible division which provides information for the holes associated with the tee. The JavaScript necessary for expanding and collapsing these divisions is included in the result document and was listed previously in ie5.xsl under the function name ExpandCollapse.

 <xsl:template match="tee"> <xsl:variable name="id" select="concat('v',generate-id())" /> <tr> <xsl:attribute name="id"> <xsl:value-of select="$id" /> </xsl:attribute> <td colspan="4"> <xsl:attribute name="id"><xsl:value-of select="$id" />_plus
</xsl:attribute> <table width="100%" cellpadding="0" cellspacing="0"
border="0" bgcolor="#e0e0e0" > <xsl:attribute name="id">
<xsl:value-of select="$id" />_table</xsl:attribute> <tr> <td width="5" style="cursor:hand"> <img src="/books/4/456/1/html/2/./images/plus.gif" hspace="4" style="border:1px outset"> <xsl:attribute name="onClick">ExpandCollapse
('<xsl:for-each select="holes/*[name() = 'hole']"> <xsl:value-of select="concat('v',generate-id())" /> <xsl:if test="last() > position()">,</xsl:if> </xsl:for-each>','<xsl:value-of select="$id"
/><xsl:for-each select=".//*[name() = 'hole']">,<xsl:value-of
select="concat('v',generate-id())" /></xsl:for-each>')</xsl:attribute> <xsl:attribute name="id"><xsl:value-of select="$id"
/>_plus_image</xsl:attribute> </img> </td> <td align="left" > <xsl:value-of select="description" /> Tee: Distance:
<xsl:value-of select="distance" /> yards&#160;Slope: <xsl:value-of select="slope" /> </td> </tr> </table> </td> </tr> <xsl:apply-templates select="holes" /> </xsl:template>

Each tee contains its own list of holes, complete with number, hole, distance, handicap, and par. Here we iterate over each <hole> node and display that data, toggling the row color of each hole as we did with the courseList match previously.

 <xsl:template match="hole"> <xsl:variable name="id" select="concat('v',generate-id())" /> <tr> <xsl:choose> <xsl:when test="position() mod 2"> <xsl:attribute name="style"> background-color:#ffffff;display:none </xsl:attribute> </xsl:when> <xsl:otherwise> <xsl:attribute name="style"> background-color:#f1f1f1;display:none </xsl:attribute> </xsl:otherwise> </xsl:choose> <xsl:attribute name="id"> <xsl:value-of select="$id" /> </xsl:attribute> <td  valign="top"> <xsl:value-of select="@number" /> </td> <td  valign="top"> <xsl:value-of select="par" /> </td> <td  valign="top"> <xsl:value-of select="distance" /> </td> <td  valign="top"> <xsl:value-of select="handicap" /> </td> </tr> </xsl:template> 

The scheduleTeeTime block provides a table of available tee times that the user can view, and if no <taken> child node of the TeeTime and no <noRegister> ancestor node are present, the user is provided a link to the scheduleTeeTime page to register.

 <xsl:template match="courseItem[@type='scheduleTeeTime']"> <xsl:apply-templates select="section" /> </xsl:template> <xsl:template match="TeeTimes"> <table  width="100%" cellspacing="0" cellpadding="1" border="0"> <tr bgcolor="#a0a0a0" style="padding:2px;spacing-top:0px"> <td  align="left" style="color:white;
font-weight:bold"> Date </td> <td align="left" style="color:white;
font-weight:bold"> Time </td> <td align="right" style="color:white;
font-weight:bold"> Reserve It! </td> </tr> <xsl:apply-templates select="teeTime" /> </table> </xsl:template> <xsl:template match="teeTime"> <tr> <xsl:choose> <xsl:when test="position() mod 2"> <xsl:attribute name="style"> background-color:#D1D7DC </xsl:attribute> </xsl:when> <xsl:otherwise> <xsl:attribute name="style"> background-color:#f1f1f1 </xsl:attribute> </xsl:otherwise> </xsl:choose> <td valign="top"> <xsl:value-of select="date" /> </td> <td valign="top"> <xsl:value-of select="time" /> </td> <td valign="top" align="right"> <xsl:choose> <xsl:when test="taken"> Tee Time Reserved </xsl:when> <xsl:when test="../noRegister"> login to register </xsl:when> <xsl:otherwise> <a> <xsl:attribute name="href">controller.asp?view=
scheduleTeeTime&amp;golferId=<xsl:value-of select="../golferId"
/>&amp;courseId=<xsl:value-of select="../courseId" />&amp;
teeTime=<xsl:value-of select="TeeTimesystemDate" /></xsl:attribute> Reserve Tee Time</a> </xsl:otherwise> </xsl:choose> </td> </tr> </xsl:template>

The <teeTimeDetail> node is processed with the scheduleTeeTime.aspx file. This node allows the user to select a tee time and register. Specifically, this template match displays the golfer's name, the course name with a link to the courseDetail page, and the selected tee-time data.

 <xsl:template match="teeTimeDetail"> <table cellpadding="2" > <tr> <td>Golfer:</td> <td> <xsl:value-of select="golfer/name" /> </td> </tr> <tr> <td>Course:</td> <td> <a> <xsl:attribute name="href"> controller.asp?view=courseDetail&amp;id=<xsl:value-of select="course/id" /> </xsl:attribute> <xsl:value-of select="course/name" /> </a> </td> </tr> <tr> <td>Date:</td> <td> <xsl:value-of select="date" /> </td> </tr> <tr> <td>Time:</td> <td> <xsl:value-of select="time" /> </td> </tr> </table> </xsl:template> 

The MyTeeTimes template match is used exclusively by the viewMyTeeTimes.aspx page and lists the tee times registered by a validated user. The source XML provides <myTeeTimes> nodes with children nodes named myTeeTime containing data about each registered tee time. The <myTeeTime> node is called using apply-templates in the center of the table.

 <xsl:template match="myTeeTimes"> <table  width="100%" cellspacing="0" cellpadding="0" border="0"> <tr bgcolor="#a0a0a0" style="padding:2px;spacing-top:0px"> <td  align="left" style="color:white;
font-weight:bold"> Course </td> <td align="left" style="color:white;
font-weight:bold"> Date </td> <td align="right" style="color:white;
font-weight:bold"> Time </td> </tr> <xsl:choose> <xsl:when test="myTeeTime"> <xsl:apply-templates select="myTeeTime" /> </xsl:when> <xsl:otherwise> <tr> <td colspan="3"> You have no tee times scheduled </td> </tr> </xsl:otherwise> </xsl:choose> <tr> <td colspan="3" height="1" bgcolor="#000000"> <img src="/books/4/456/1/html/2/images/spacer.gif" height="1" /> </td> </tr> </table> </xsl:template>

The myTeeTime template matches a single tee time assigned to a validated user, providing a link to courseDetail for the associated golf course.

 <xsl:template match="myTeeTime"> <tr> <xsl:choose> <xsl:when test="position() mod 2"> <xsl:attribute name="style"> background-color:#D1D7DC </xsl:attribute> </xsl:when> <xsl:otherwise> <xsl:attribute name="style"> background-color:#f1f1f1 </xsl:attribute> </xsl:otherwise> </xsl:choose> <td  valign="top"> <a> <xsl:attribute name="href"> controller.asp?view=courseDetail&amp;id=<xsl:value-of select="course/courseId" /> </xsl:attribute> <xsl:value-of select="course/name" /> </a> </td> <td  valign="top"> <xsl:value-of select="teeTime/date" /> </td> <td  valign="top" align="right"> <xsl:value-of select="teeTime/time" /> </td> </tr> </xsl:template> </xsl:stylesheet> 

This XSLT file, ie5.xsl, is a large and extensive file providing complete template matches for each node in every source XML document possible within the Golf Reservation System client application. Maintaining this file is difficult because it includes a number of different languages: HTML, JavaScript, and XSLT. The developer creating this XSLT style sheet would therefore require working knowledge of all these languages and possibly more. A more modular approach might be in order for a production application where the JavaScript might be provided through an include instead of being present in the actual XSLT style sheet. You could also develop a client application that contains skins that span a number of documents. For example, if it is possible to separate form XSLTs from generic display transformations, it might be a good idea to develop those in separate files (perhaps named ie5_form.xsl and ie5_display.xsl).

Nonetheless, the UI developer must hold responsibility for not only being familiar with the Web Services available in building the .aspx and .aspx.cs files, but also for having knowledge of the various Web languages available for use. And given that the controller design pattern is flexible in cross-platform development, the UI developer or team would also require knowledge of markup for devices such as Wireless Application Protocol and others.

Therefore, while ie5.xsl is extensive and large, it is not a complete solution for a production application. Instead, it is an all-inclusive example of how one would use XSLT, HTML, and DHTML/JavaScript to create a browser-ready skin for a controller design pattern client in a .NET Framework client application.



XML Programming
XML Programming Bible
ISBN: 0764538292
EAN: 2147483647
Year: 2002
Pages: 134

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