The Response Page |
Session Five will focus on creating response pages. Any time a web user submits a form, your application must send a response page. This might be a simple "Thank You" message, or it might be a complex page with embedded data. Either way, the browser is expecting a response and you application must oblige.
A simple response page can be sent with the Webclass's streaming methods: StreamHeader(), StreamBody(), and StreamFooter(). The StreamBody() method accepts a parameter that is the message to be sent. For a more complex page, you will need to use a HTML editor to create the page, and your CGI program must stream that page to the Web browser.
A dBASE Plus CGI program communicates with a web server through a piping system called Standard In (StdIn) and Standard Out (StdOut). Messages coming from the server do so via the former mode and messages being sent to the server do so via the latter. Within dBASE Plus this communication pipe is established using the file object. The dBASE Webclass, in the Connect() method, opens both an incoming and outgoing channel with the web server, using code similar to the following.
fIn = new file() fIn.Open("StdIn", "RA") fOut = new file() fOut.Open("StdOut", "RA")
In your CGI programs, there is seldom a need to work with the fIn object, but working with the fOut object is very common. This is the route through which your response page must travel.
Response pages are as varied as web pages them self. In Session Two we worked with the Web Wizards and saw a few examples of response pages. In some of the examples, the response page was rendered from a report file. The Query and Response Wizard, for instance, sends a filtered report back to the browser.
Other examples, like the Remote Data Entry wizard stream a response page with a simple message: "Data entered successfully!" The following is the code used to generate the entire response page:
oCGI.fOut.Puts("Content-type: text/html") oCGI.fOut.puts('') oCGI.fOut.Puts("<HEAD>") oCGI.fOut.Puts( '<META HTTP-EQUIV=Content-Type CONTENT="text/html"') oCGI.fOut.Puts( "<TITLE>Data Entered Successfully</TITLE>") oCGI.fOut.Puts("</HEAD>") oCGI.fOut.Puts(" ") oCGI.fOut.Puts([<BODY bgcolor="#C0C0C0" background="None">]) oCGI.fOut.Puts(" <blockquote>") oCGI.fOut.Puts([ <I>Data entered successfully!</I>]) oCGI.fOut.Puts(" </Blockquote>") oCGI.fOut.Puts("</BODY>") oCGI.fOut.Puts("</HTML>")
In Session Four, we explored how to use the WebClass library to create our own data entry applications. In each example a response page was streamed after the data is appended. The code is similar to the following:
oCGI.StreamHeader("Information added") oCGI.StreamBody( cMsg ) oCGI.StreamFooter()
As you can see, there are a number of different ways to generate a response page.
When you need to create the entire response page, without any help from the WebClass library, the first thing that must be sent is the CGI header. This line is read by the Web server and tells it what kind of data it needs to handle. The standard CGI header is streamed from your program with the following lines (assuming that an oCGI.fOut object has been created):
oCGI.fOut.Puts("Content-type: text/html") oCGI.fOut.puts("")
The second line is essential, so don't forget it when you construct your own response pages. This blank line defines the end of the CGI header. Do not send a space (" "). It must be an empty string (""). When you use the WebClass library, the streamHeader() method sends these lines for you.
After the CGI header, you can stream the entire HTML page. The WebClass library, however, breaks the page into three parts: the header, the body, and the footer. The basic structure of a HTML page looks like the following (which is barrowed from "dBASE and the Web," p. 36):
<HTML> // defines the PDL <HEAD> // start header <TITLE>Hello dBASE</TITLE> // title tag </HEAD> // end head <BODY> // start body <FORM> // start form </FORM> // end form </BODY> // end body </HTML> // end HTML page
The streamHeader() method, in addition to streaming the CGI header, streams the <HTML> to the <BODY> tag. For many Web applications you can use the streamHeader() method without modification. Occasionally, however, you will need to create a custom streamHeader() method. The most common reason for this is to add JavaScript functions or CSS style properties, which are generally placed within the <Head></Head> tags.
The page body is the element that you will most often need to customize. This element is the visible part of an HTML document and it contains the messages, controls, graphics, and hyperlinks that the user will be viewing. In the sections that follow, we will discuss some of the techniques used to construct the body of an HTML page.
One way to create a response page is to use an HTML editor to layout your page. Once the file is saved you can open it with a text editor. That text might, for example, look like the following:
<h2><font color="#000000">Building Web Applications <br>Course Schedule</font></h2>
You can then edit this HTML code and convert it into a dBL function. I add "oCGI.fOut.Puts([...])" to each line created by the HTML editor.
oCGI.fOut.Puts([<h2><font color="#000000">Building Web Applications]) oCGI.fOut.Puts([<br>Course Schedule</font></h2>])
Copy and paste this into your prg file and your Web app will stream the page back to the browser.
Note |
Some developers like to use a technique that is a slight variation
on this method. Rather than "streaming" the HTML to the file object (fOut)
line-by-line, they will accumulate it in a variable, and send the full
page all at once.
local cHTML cHTML += [<h2><font color="#000000">Building Web Applications] cHTML += [<br>Course Schedule</font></h2>] oCGI.fOut.Puts(cHTML) This technique is particularly useful if you want to catch errors while you are constructing the HTML page. If an error does occur, your response page will be the error message and the contents of cHTML variable will be ignored. |
A not so well known utility called htmlToPrg.wfm is included with dBASE Plus. This desktop form is located in the web folder and will convert an HTML file into a PRG. The utility is very handy. One should note, however, that it converts only the text within the <BODY> tag. This is consistent with the standards adopted in the WebClass library, that is, where each response page is divided into three parts.
oCGI.StreamHeader() oCGI.StreamBody() oCGI.StreamFooter()
The converted code replaces the StreamBody() call.
oCGI.StreamHeader() oCGI.MyConvertedBody() oCGI.StreamFooter()
A potential problem with this three part method of creating a response pages is that sometimes we want code in the HTML header. I often put JavaSctipt or Style information in the HTML header. For this reason I've modified htmlToPrg.wfm by adding option buttons for "Export Body" and "Export All". The later option converts the entire HTML page.
My copy of the form is available in the Web Tutorial's "Source" folder. You may want to compile this form and build it into a stand alone exe for easy use.
It is common that your response page will need to include data from a DBF table. For example, you might be listing a customer invoice or you might be providing current data for a remote data editing page. In such cases you will need to edit your response page function so that the field values are inserted into the appropriate locations of the HTML page.
Say for example, you have a TEXT object on the HTML page which contains a customer's name. The HTML must look like this:
<INPUT TYPE="TEXT" MAXLENGTH="40" NAME="name" VALUE="Mike Nuwer">
And in the browser it will be rendered like this:
You can convert the HTML to dBL code with the following:
oCGI.fOut.puts([ ; <INPUT TYPE="TEXT" MAXLENGTH="40" NAME="name" "VALUE="] + ; trim(q.rowset.fields["name"].value) + [">] )
This concatenation works fine unless the field value is null. An empty field in a level 7 table is NULL by default, and "text" + null + "text" messes up the HTML. Therefore you will want to test for empty values in the table with code like the following.
if not empty(f["email"].value) cValue = [ VALUE="] + trim(f["email"].value) + ["] else cValue = "" endif puts([<INPUT TYPE="TEXT" NAME="Email" MAXLENGTH="40"] ; + cValue + [>])
When you create data lists (or web reports) you will want to use a similar technique. Web reports are normally rendered within a HTML table structure. So in this case if your data value is NULL you should use the HTML escape character for a non-braking space: " ." You will get some odd results if you use a true space (" ") in a HTML table.
Because fetching data values is such a common task in a web application I have added a method to the WebClassEx library that facilitates the formatting of field values for use in a web page. You can find the GetFieldValue() method in WebclassEx.cc. This method not only checks for null values, but it also converts numbers and dates to strings. It assumes that the row pointer is at the desired record, and when called, returns the desired field value.
oCGI.GetFieldValue(oQuery, cFieldName, bAddSpace)
You may have noticed on some web page or another, strange characters like "%20" in a hyperlink. This strange character is a URL-encoded punctuation characters.
When you use a HTML form for submitting data to a CGI program, the MIME type is declared to be:
ENCTYPE="application/x-www-form-urlencoded"
Notice that this MIME type tells the web browser that the form data should be "urlencoded." The browser obeys this request and encodes the name/value sent to the web server. In Session Three we discussed that the WebClass library, when the connect() method is called, will de-code the name/value pairs. This means that the developer does not need to be concerned about such matters. When a HTML form is used the browser will encode the data and when the WebClass library is used, the data will be decoded.
There is, however, one circumstance where the developer will need to consider the issue of encoding and decoding. This is where a hyperlink that calls a CGI program is used on a response page. In Session Two, for example, we saw how a hyperlink can be used as a kind of data lookup, and, in combination with a drill-down report, will retrieve data that meats the specified search parameters.
Consider the report that was created in Exercise 2.5. That report used a calculated field to construct a hyperlink. The beforeGetValue event handler looks like this:
f.beforeGetValue = {|| [<A HREF="OrderItems.exe?search=] ; + ltrim( str(this.parent["InvoiceID"].value )) + [">GO</A>]
In this particular case the data from the database table is a number, so we wouldn't expect any punctuation characters or spaces. But, unallowed characters could be part of the query string where a more complicated set of name/value pairs are used. Such characters will cause a problem for the web server or your CGI program.
The specification for URLs (RFC 1738) limit the use of allowed characters to alphanumeric and a small number of reserve characters ($&+,/:;=?@). For this reason any URL in an HTML document must be URL encoded. For example, if your query string will contain "John & Jane", the ampersand here must not be misunderstood as a separator for a name/value pair. There is no way for the web server, or for your CGI program, to know this unless the ampersand is encoded (%26).
The rules for encoding are rather simple.
What this means to the developer is that care must be used when creating hyperlink that send query string data to a CGI program. Say, for example, you have two search values, the first is a start date and the second is an end date. The following query string, however, will cause problems.
?search1=04/04/03&search2=05/04/03
To avoid these problems you must URL encode the query string. The developer is in control of the name for the name/value pair, so you shouldn't use characters other than alphanumeric. But the developer has less control of the value piece and, therefore, it is the value which most likely needs to be encoded.
The WebClassEx library contains a method which is designed to encode the Name or the Value of a URL query string. The name of this method is "escapeURL()". The following example concatenates the query string and uses this method to encode the dates.
cString = "?" cString += "search1=" cString += oCGI.escapeURL("04/04/03") cString += "&search2=" cString += oCGI.escapeURL("05/04/03")
The resulting string will then look like this where the "/" characters are replaced with "%2F".
?search1=04%2F04%2F03&search2=05%2F04%2F03
When this query string is submitted to a dBASE CGI program, the WebClass library will decode the data and add the two name value pairs to the oCGI array.
Another variation for creating response pages is to use Ken Mayer's library of WebClass extensions. The idea behind this library is simple: instead of streaming HTML code line by line, special purpose methods are used to stream elements that make a page. As Ken notes, it is easer to call a method with a parameter like this:
oCGI.streamTitle( "My Title" )
than it is to stream this:
oCGI.fOut.puts( [<TABLE BORDER="0" CELLPADDING="0" CELLSPACING="0" WIDTH="100%">] ) oCGI.fOut.puts( [<TR>] ) oCGI.fOut.puts( [<TD BGCOLOR="LIGHTBLUE">] ) oCGI.fOut.puts( [<CENTER><H1><FONT COLOR="WHITE">My Title</FONT></H1></CENTER>] oCGI.fOut.puts( [</TD>] ) oCGI.fOut.puts( [</TR>] ) oCGI.fOut.puts( [</TABLE>] )
The library is contained in kenWebClass.cc (it's distributed in the dUFLP). It is a subclass of the WebClass library and it contains a number of special purpose streaming method. An example might look like this:
oCGI.streamHeader( "Ken's Humor Files" ) oCGI.streamBody() oCGI.streamTitle( "Ken's Humor Files" ) oCGI.streamDetail( "<P>" ) oCGI.streamDetail( [<CENTER>Select Option to Filter On</CENTER>] ) oCGI.streamFormBegin( "query2.exe" ) oCGI.streamDetail( "<P>" )
The following table lists the streaming methods found in the kenWebClass library.
Method Name | Description |
errorMessage | streams out a message on a form that has already started to display |
streamTitle | stream out the title at the top of the page |
streamDetail | streams out individual lines of text and/or html |
streamFormBegin | steams out the <FORM> code with the cgi definition you specify for what to do with the form when it's submitted |
streamFormEnd | streams out the end tag for a form |
streamText | streams out an HTML entryfield |
streamPassword | streams out an HTML entryfield with a password mask |
streamRadio | streams out an HTML radio button |
streamCheckbox | streams out an HTML checkbox |
streamReset | streams out an HTML "Reset" button |
streamSubmit | streams out an HTML "Submit" button |
streamTextArea | streams out an HTML Editor control |
streamSelectBegin | streams out an HTML Combobox or Listbox |
streamOption | stream out elements used in Select |
streamSelectEnd | end tag for a select object |
streamHidden | special object used to pass values in standard Name/Value pairs to an application when a form is posted, which do not have any UI (i.e., the user cannot see/interact with them!) |
streamFile | stream out the HTML FILE tag, used to obtain a filename, adds a browse button |
For an explanation of each method and details on how to use them, see the header of kenWebClass.cc. In addition, Ken's "Creating a dB2K Web Application" paper in the dBASE Knoweledgebase is an extended example of this method for generating response pages.
Sometimes it's useful or necessary to send a redirection CGI header rather than an actual response page. For example, you may wish to write a file to the server's hard drive and have the web browser pickup this file. The file may be a HTML document, or it may be an image, a speadsheet, a word processing document, or something else. Another possibility is that you want to send the user to a CGI applet that will generate the response page.
The easiest way to redirect the web browser to is to send the "Location:" CGI header instead of the "Content-type:" header. The Location header is used to tell the web server that you are returning a reference to a document rather than an actual document.
This header must be in one of two forms: a complete URL or a virtual path. If the value is a full URL, such as http://my.server/app/myApp.exe, then the server redirects the client to the new URL (this is transparent to the user). The client then acts as if it had originally requested that redirected URL, so all relative links in the document of the URL are resolved from the directory specified in that URL.
If the argument to this is a virtual path, the server will retrieve the document specified as if the client had requested that document originally. In this case the folder that contains your CGI program remains the home folder and any relative links in the document are resolved from that folder.
As an example you can send the following in place of response page and the client browser will be redirected to this location. Your CGI application must send only these two lines. Do not send streamHeader(), streamBody(), or streamFooter(). The Location header tells the web server to look for a response page at some other location.
oCGI.fout.puts( "Location: /app/MyApplet.exe?SEARCH=xyz" ) oCGI.fout.puts( "" )
Be sure to send the second line. The blank line is required.
You may have noticed that when you go back to a page you've looked at not too long before the page loads much quicker. That's because the browser stored a local copy of it when it was first downloaded. These local copies are kept in what's called a cache. Usually one sets a maximum size for the cache and a maximum caching time for documents in their browser.
The browser cache can be problematic for dynamic web sites. The browser has no idea how the response page was generated. It don't know whether the document is a static page stored on the web servers hard drive or is streamed dynamically from a CGI program. Either type of document can get cached by the browser. In the case of the dynamic document, this means that when a Web client returns to your page, they may be using outdated information.
You can prevent browsers from caching your dynamic documents by setting the correct HTTP headers. The "Expires: " header reports the date on which the HTML document should be considered outdated by the client. It is used to prevent browsers from caching the resource beyond the given date. If the Expiration header is set to a date/time in the past the output from the request will not be cached. (Note that HTTP requires browsers to keep the result in the browser history, so that going back to a non-cached page does not cause a new request.)
The date must be a GMT formatted string in the following form:
Saturday, 12-Nov-94 14:05:51 GMT
In your dBASE program you can send something like following. Notice that the "Expires" header line is streamed before calling the streamHeader() method. This sequence means the after the "Expires" line is sent, the streamHeader() method will send the Content-type header and the header terminator.
cExpiresDate = new date( dtoc(date()-1 ) ).toGMTString() oCGI.fOut.puts( "Expires: " + cExpiresDate ) oCGI.streamHeader()
The following exercises focus on response pages that include more than one or two line messages. We will start with a very simple response page. The HTML for this page is listed below. If you would like to see what it looks like in a browser, you can copy this code to a text editor and save the file using a ".htm" extension. You can then open the file in your web browser.
<HTML><HEAD> <TITLE> Session 5 -- Web </TITLE> </HEAD> <BODY BGCOLOR="#FFFFFF"> <H1>Session 5 Exercises</H1> <H1>Home Page</H1> <A HREF="/app/Exercise0502.exe">Exercise 5.2</A><BR> <A HREF="/app/Exercise0503.exe">Exercise 5.3 (using htmlToPrg.wfm)</A><BR> </BODY> </HTML>
We do not, however, intend to post this HTML file on a web site. Rather, we are going to create a dBASE applet to generate the page. The program file is very simple. We will not use the WebClass library for this application. The purpose here is to show you the minimum code necessary to produce a response page in a CGI application.
The first four lines of the program file will open a connection to standard out and stream the CGI header. The lines are:
fOut = new file() fOut.Open("StdOut", "RA") fOut.Puts("Content-type: text/html") fOut.puts("")
Normally these lines are executed from within the WebClass library. Lines 1 and 2 are included in Connect() and lines 3 and 4 are included in streamHeader(). However, our simple application is not using that library, so we need to write this code ourself.
Next we want to send the HTML code to the web server. This is done by "writing" (or puts-ing) each line of the HTML to the Standard Out object. In dBASE the syntax is the same as if we were writing a line of text to an ASCII disk file:
fOut.puts([<HTML><HEAD>]) or fOut.puts("<HTML><HEAD>")
At this point you should copy and paste the HTML code for our page into your program file and add the puts() method.
We have one last thing to do. The last three lines of our program file must close the standard out port, null the file object, and quit.
fOut.close() fOut = null Quit
Save this program file. You will find a completed copy of this program file in the "Source" folder. The file name is "Exercise0501.prg."
Next switch to the command windows and build the executable:
compile Exercise0501 build Exercise0501 to ..\app\Exercise0501.exe WEB
To run this program, enter the following in your browser's location field:
http://localhost/app/Exercise0501.exe
We are going to use this simple response page to call some additional response pages. But we must first build those pages.
The next exercise will be similar to the default menu but we will incorporate the response page within a program file that uses the WebClass library.
We first need a HTML page. You can create your own page using a HTML designer, or you can use the HTML code that follows. This code produces a table with text aligned in different ways.
<HTML> <HEAD> <TITLE>Horizontal and Vertical Text Alignment</TITLE> </HEAD> <BODY bgcolor="#FFFFFF"> <H1>Sample One</H1> <P>An HTML Table <TABLE border="5" width="50%"> <TR align="LEFT"> <TD>Left</TD> <TD>Align Left</TD> </TR> <TR align="CENTER"> <TD>Center</TD> <TD>Align Center</TD> </TR> <TR align="RIGHT"> <TD>Right</TD> <TD>Align Right</TD> </TR> <TR valign="TOP"> <TD>Vertical Alignment Top<BR> <BR> </TD> <TD>Top</TD> </TR> <TR valign="CENTER"> <TD>Vertical Alignment Center<BR> <BR> </TD> <TD>Center</TD> </TR> <TR valign="BOTTOM"> <TD>Vertical Alignment Bottom<BR> <BR> </TD> <TD>Bottom</TD> </TR> <TR valign="BASELINE"> <TD>Vertical Alignment Baseline<BR> <BR> </TD> <TD>Baseline</TD> </TR> </TABLE> </BODY> </HTML>
The above HTML code will be incorporated in our program file as a stand alone function and streamed as a response page. To create the program file we will begin with the basic template. Open a copy of your CGI template file. If you do not have a copy here is the one I use:
Set talk OFF // turn off interactive mode Set century ON // Y2K Try // Error trap entire CGI applet. ////// Create new instance of the Web Class Set proc to WebClass.cc additive oCGI = new CGISession () ////// Connect to the Web Server (StdIn/StdOut) oCGI.Connect() catch (exception e) oCGI.errorPage(e) endtry /// cleanup oCGI = null ////// all done Quit
After the line which makes the web server connection, i.e. oCGI.Connect(), we need to add a command to call a function. You can name the function anything you would like (but you can't use a dBL reserve word). I will call it "Print_response_page". Then at the bottom of the program file, after the "Quit" command, add a new function and place all the HTML code inside. In addition, since we are not calling streamHeader(), we need to send the CGI header as the first two lines in the function.
function Print_response_page oCGI.fOut.Puts("Content-type: text/html") oCGI.fOut.puts('') // "" -- not " " oCGI.fOut.puts([]) oCGI.fOut.puts([<HEAD>) oCGI.fOut.puts([Horizontal and Vertical Text Alignment ]) oCGI.fOut.puts([</HEAD>) ..... etc. oCGI.fOut.puts([ ]) oCGI.fOut.puts([ ]) return
Save this program file. My copy of this program is named Exercise0502.prg.
You must next build the executable.
compile Exercise0502.prg build Exercise0502.pro to ..\app\Exercise0502.exe WEB
You can use the default page that was created in Exercise 5.1 to call this program. Be sure the executable's name and the URL in the anchor tag are the same.
Exercise 5.3 is similar to the previous program, but this time we will create the response page with a slightly different method. We will use the htmlToPrg.wfm utility that is included with dBASE Plus. This utility is located in the Plus\web\classes folder. In addition, following the techniques of the dBASE SignUp application, we will use a subclass of the streamBody() method from the WebClass library.
This is one of more common ways of designing a web application. It is most useful if much of your application is generated from the Web Wizards and you want to edit or add features to that code set.
To use the HtmlToPrg utility, create a HTML file (or use the HTML code below). Save the file as Exercise0503.htm.
Note |
If you use the code below, modify the "action" property of the FORM tag
by entering your email address. When the form is submitted the data will
be sent to your email account.
|
<HTML> <HEAD> <TITLE> Bugs Bee Wee </TITLE> </HEAD> <BODY> <H1>Online Catalog Order Form</H1> We would love to send you our newest online catalog.<BR> To do that we need to have you supply a little information so we can better serve you. Please supply the following data: <FORM enctype="text/plain" action="mailto:Someone@domain.com" method="POST"> Your name: <INPUT type="TEXT" name="name" size="30" maxsize="80"> <BR><BR> What do you like to do with your bugs?<BR> <INPUT type=RADIO name="use" value="W"> Watch 'em <INPUT type=RADIO name="use" value="E"> Eat 'em <BR><BR> E-Mail address:<BR> <INPUT type="TEXT" name="email" size="30" maxsize=80> <BR><BR> <INPUT type="SUBMIT" value="Request Catalog"><BR> </FORM> </BODY> </HTML>
Next run HTMLtoPrg.wfm. Enter the path to the html file ("Some\Path\Exercise0503.htm") for the "Source HTML File", and enter something like "Some\Path\TempHTML.prg" for the "Destination .prg file". Note: Do Not Use An Existing PRG File. The utility will overwrite everything in file.
After completing the conversion, open the newly created TempHTML.prg file and examine the code.
This code is designed to work with the WebClass library streaming methods: streamHeader(), streamFooter(), and streamBody(). Ideally you would subclass the WebClass library and override the default streamBody() method with the code produced by htmlToPrg.wfm. And that is what we will do in our CGI program file.
That means we need to create another CGI program file. Use the CGI template file to get started.
At the bottom of the program file create a subclass of the WebClass library.
Class MyCGISession of CGISession from "WebClass.cc" function streamBody return true endClass
Notice that the name of this class is MyCGISession and that it is a child of CGISession which is the class name found in WebClass.cc. To make this class a child of the WebClassEx library the first line would be:
Class MyCGISession of CGISessionEx from "WebClassEx.cc"
Copy the code generated by htmlToPrg and paste it as the streamBody function in this child class. This streamBody method will be used to override the one contained in the WebClass library.
Our program needs to use this child class rather then the parent. So find the line that creates the oCGI object: oCGI = New CGISession().
This line must now be written like this: oCGI = New MyCGISession().
The program file should then execute the following functions:
oCGI.streamHeader() oCGI.streamBody() oCGI.streamFooter()
And the Body of the response page will be our new document.
Build this program and deploy it to your cgi-bin folder.
compile Exercise0503.prg build Exercise0503.pro to ..\app\Exercise0503.exe WEB
In this Session we have made three programs, each of which produces a response page. The first program functions as a menu. The other two are doing some work.
In a more realistic application, the response pages could be reports based on data from a database. We will look at these type of pages is Session Seven. The response page could also be a HTML form that contains data from a database. This would be the case when we are editing data remotely. The next session of this class will look at working with remote data editing.
A response page can be a simple "Thank You" message or it can be a complex document filled with data. We will continue this discussion in the next session.