Skip to main content.

Web Based Programming Tutorials

Homepage | Forum - Join the forum to discuss anything related to programming! | Programming Resources

Special Edition Using Visual FoxPro 6

Special Edition Using Visual FoxPro 6 -- Ch 25 - Internet Support

Special Edition Using Visual FoxPro 6


Chapter 25

Internet Support




What's so Exciting About the Internet?

The Internet and the technologies that are driving it are hot these days! People are flocking to the Internet, and the World Wide Web in particular, by the millions, and new Web sites and other Internet-related businesses and institutions are keeping pace with the ever-increasing hook-up rates of new Web surfers. Building applications that are integrated with Internet technology and can connect databases to Web sites will likely become an important aspect in your software development, as companies like Microsoft integrate Web-based technology into every aspect of software from high-level applications down to the operating system.

Although traditionally the Internet has been made of mostly static content, there is a tremendous rush under way to make dynamic data available. Database connectivity is key to this concept, and Visual FoxPro is up to the task to provide the speed and flexibility to act as a database back end and integrate Internet functionality into existing applications.

There is a lot of hype about the Internet and, to be sure, some of the new technologies are not all that they are cracked up to be. The use of the Internet for business application development is still in its infancy, and there are quite a few limitations when compared to the application development facilities that you might be used to using.

Nevertheless, the Internet is bringing about a major shift in the way applications are deployed by making it much easier to build solutions that are open and widely distributed, even open to the public, over the World Wide Web. Over the last few years, you have probably heard a lot about client/server development. A client is normally a user application, such as an accounts payable program. A server is an active process, usually running on a separate computer, that provides a service for the client. In the case of database operations, a database server manages the databases and their indexes and keeps track of user resources. The Internet is providing the full promise of what client/server was always meant to be: a platform that enables you to build widely distributed applications that can be centrally maintained using a common front end provided by a Web browser.

The driving forces behind the popularity of the Internet and the World Wide Web in particular are

The following sections look at these points in more detail.

The Universal Client Web Browsers as a Common Interface

The World Wide Web and the Web browsers used to cruise it are changing the way we look at applications today. Web browsers have brought a universal interface to applications in a way that no other software interface before it has ever achieved.

Web browsers are easy to use and provide a very simple interface to the user. The use of simple controls for navigation (back and forward buttons, a list of the last places visited, a "favorites" list, and so on) make the browser interface just about self-explanatory. It also provides a consistent interface across applications developed with a browser in mind. A typical example of a dynamically generated Web page is shown in Figure 25.1.

Figure 25.1 : A Web page containing dynamically generated data. This order form shows items selected on a previous form, enabling the user to fill out order info for an online purchase using a secure transaction.

The hyperlink-based nature of Web pages, where you can simply click on a highlighted area and immediately be transported to the location of the relevant information, makes them an extremely powerful tool for integrating information from various sources, both internally from within a company and externally from the open Internet.

Ease of use is important, but even more compelling is the ubiquitous nature of Web browsers. Browsers exist for all the major computing platforms from Windows to Macintosh to UNIX. What this means is that an application interface developed for a Web browser will instantly run on all the platforms that support a Web browser. Cross-platform development has been an often-overlooked part of software development in the past because of its complexities, but with the Web browser interface, this feature is included for free in the one-time Web application development process.

Finally, Web browsers free developers from having to distribute huge application runtimes that need to be installed on each client system. Take a typical standalone Visual FoxPro application, for example. To distribute such an application across the company, you have to install the application runtimes on each of the machines. You distribute your .EXE, the runtime, and various system files that get installed on the client system. With a Web browser, none of this happens. There is a one-time installation for the browser after which all Web-based applications are accessible. As long as the client system is equipped with a Web browser and access is available to the network that runs the application over either the public or company internal Web, the application can be run without any special installation procedures. Furthermore, the application can be run from anywhere, whether the user is at the office, on the road, or on vacation, as long as she has access to an Internet connection.

Distributed Applications Over the Web

The last point is extremely important! The Web has made distributed computing a reality for the first time by making it relatively easy to install applications that can be accessed publicly over the Internet or privately over an internal network (called an intranet). Prior to the Internet explosion, building a publicly accessible application was extremely difficult, inconvenient for end users, and very costly because proprietary communications and network protocols needed to be set up.

The promise of client/server has always been to create a distributed environment where there is a distinct line between the application interface and the database and business logic. The Web is providing this environment by clearly separating the client (the Web browser) and the server (the Web server and the back-end applications tied to it) as well as providing an open platform (a network running the TCP/IP protocol) to connect the two.

Web applications make it possible to distribute applications widely, including the capability to enable public access at reasonable cost. But at the same time, the application is maintained centrally at the server with no pieces of the application actually residing on the client side. This means that updates to an application don't require updating of any part of the client's system. All that's required is a code change on the back-end application, and all clients are automatically updated.

Open Standards

The Internet is based on open standards. Although there are quite a few struggles to extend standards and push them into company-specific directions, by and large, all the protocols and tools used are standards-based and supported by a wide variety of products and vendors. This is extremely important because it enables different companies to build tools that can interact with one another.

In addition, the open nature of the Internet is forcing companies to try to extend standards in non-proprietary ways. A good example is the struggle to extend HTML (Hypertext Markup Language), which is used to display output in Web browsers. The two leading browser vendors, Microsoft and Netscape, are the standard-bearers in this field, and both are trying to extend the standard beyond its current limitations. But unlike the past, when extensions were often proprietary, both of these companies are making the specifications widely available for developers to start using them immediately, thereby encouraging other browser vendors to pick up on the new extensions for use in their own products.

Many of the protocols used on the Internet are modular and relatively simple to implement. This means it's easy to integrate many of the connectivity features that the Internet provides with readily available protocols. Specifications are publicly maintained and accessible over the Internet. Because most of the protocols used are simple to implement, a wide variety of tools are available to use with these protocols; you can easily roll your own if you need specialized functionality.

All of this openness adds up to better interoperability of tools as well as immediate accessibility to the tools that are required to build advanced Internet applications.

Limitations of Building Web-Based Applications

Although the previous sections point out the glowing advantages of building Internet-based applications, it's important to keep in mind that this is a very young and still developing field. Most of the dedicated Web-only development tools that are available today are fairly limited. There are lots of solutions available to hook just about any kind of data to the Web, but the complexity or limits of the tools can often get in the way of building applications that provide the full breadth of functionality that a traditional standalone application can provide.

In this chapter, you will learn how to use Visual FoxPro as a database back end that gets around some of the Web application development limitations. Still, there are limitations in interface design that will require a change from the way you might be accustomed to building applications with visual tools like Visual FoxPro.

For example, HTML and the distinct client/server interface that disallows direct access to the data from the Web browser requires building applications with the different mindset of server-based programming. HTML input forms are more reminiscent of the dBASE II days when you had to hand-code fields and had no immediate control over user input via field-level validation. All access to the data happens on the server end so that each request for a data update requires calling the Web server and requesting it to update the current HTML page with new data. In essence, this process requires reloading of the currently displayed page with the updated information. This is a somewhat slower process than what can be achieved with in-line field validation. However, it is a very sure process that is simple to develop and maintain.

Web-based form input is transaction-based, where you first capture all the information entered in the Web browser and then validate the input and return an error message relating to an entire input form on the Web server. Although new HTML extensions provide more control over input forms and active HTML display, the fact that the Web browser has no direct access to data makes it difficult to build truly interactive input forms that can immediately validate input against the database rules. To update the input form or even send back an error message, the original form has to be submitted, sent to the server, and then be redrawn from scratch with the updated information retrieved from the server.

Another limitation to consider is that Web applications cannot easily print reports. All you can do is display a page as HTML and then print the result; there's no full-fledged report writer to create banded and subtotaled reports. Instead of a report builder you have to hand-code.

Although these limitations are real and something you have to consider when building Web-based applications, Microsoft and Netscape are addressing these issues with extensions to HTML that are starting to look more like the fully event-driven forms that you are used to with tools like Visual FoxPro. The not-too-distant future will bring tools to paint input forms with a form designer and enable attaching of validation code directly to fields. However, the lack of direct data access will likely continue to be a major difference between Web and desktop applications.

Limitations or not, the Web is hot, and those who have taken the first step toward building Web-based applications rarely look back as the advantages of the distributed environment and the easy scalability that goes with it often outweigh the disadvantages just mentioned.

Database Development on the Web

With the popularity of the World Wide Web, the need for database application development on the Web is exploding as more and more companies are realizing that to make maximum use of their Web sites, dynamic display of database information is essential to provide interesting and up-to-date content.

This section discusses the logistics of building database applications that run over the Web, how Web deployment affects the application development process, and what's required to make it happen.

Tools you Need to Develop Web Applications

To build applications to run over the World Wide Web, you need the following components:

The preceding list provides a road map of tools you need to build Internet applications and test them on your local machine or over a local network. In addition, you must deal with the issue of connecting the application to the Internet. Several options are available:

Running Applications Over the Web

Database connectivity over the Internet is essentially a specialized form of client/server: You have a front end (the Web browser) that accesses a back end (the Web server), which, in turn, is connected to the application that provides the database access. The front end and the back end are connected via an Internet connection, which in most cases will be the World Wide Web and HTTP (Hypertext Transfer Protocol). Figure 25.2 shows the relationship between the Web browser and Web server.

Figure 25.2 : The Web server is responsible for data access using server extensions, and the browser is responsible for display of output.

When you look at the diagram, keep in mind the clear distinction between the client and server sides-note the distinct line between the two. The Web browser has no direct access to the data, and the Web server on the other end has no direct access to the interface presented to the user. Think of the Web browser as the interface and the Web server as the database/application server. All interaction between the two is accomplished via a predefined protocol and transaction-based requests. Database

Web Browsers Get Smart: Scripting and Active Controls  On the browser side of Figure 25.2, you can see the browser scripting and active controls options. Browser scripts are client-side browser extensions, such as JavaScript for Netscape and VBScript for Internet Explorer, that provide programmable control over the browser's interface. These scripting languages provide additional control over the user interface used for input as well as provide programmable features for generating the HTML output; in essence, browser-side scripting provides logic smarts to the browser. Remember, though, there's no access to data from the browser.

Active controls refers to external controls that can be called by HTML pages and controlled by browser scripts. These controls can be automatically downloaded over the Web. In this context, active controls include Java applets and ActiveX custom controls (special-purpose, lightweight OLE controls that contain the logic to download and install themselves over the Internet) that can be executed or called by the scripting engines provided in the Web browser. Scripting languages provide the basic interface programmability and the active controls provide high-performance, optimized, and operating system-specific features to a Web browser. Many multimedia-related tools and controls are implemented as active controls, and, in the future, you will likely see controls that match common GUI interface controls, such as pop-up calendars for date validations, shipping rate calculators, and so on.

Database But even these high-powered controls do not have direct access to the data that resides on the server.

The Web Server: Providing the Link to Application and Database Connectivity  On the other end of the Internet sits the Web server. When dealing with applications running over the Internet, the Web server acts as the intermediary that negotiates access to the application and database. The Web server on its own knows nothing about applications, but instead calls helper applications known as server scripts to perform this task for it.

The flow goes something like this: The Web browser requests access to data by sending a request to execute a script on the server side. A script is essentially a program that runs on the Web server machine in response to a hyperlink hit or the click on an HTML form button. Scripts can either be .EXE files following the CGI (Common Gateway Interface) protocol, or a server-side API interface such as ISAPI (Internet Server API), which is available with Microsoft's Internet Information Server (IIS). The API-level .DLLs that are called in response to script links provide much better performance than the .EXE-based CGI scripts because they are loaded in memory as in-process .DLLs that don't need to be reloaded on each hit.

Keep the server-side script concept in mind, as all dynamic Web access is accomplished by calling scripts. Don't confuse server-side scripts with scripting languages such as JavaScript or VBScript either on the client or server side. Scripts in this context are external applications called by the Web server. These scripts can either be fully self-contained or can be used as connectors to call other applications such as Visual FoxPro to handle the database access, application logic, and the output display.

With each Web request, the browser makes available some information about itself-the browser type, its Internet address, and any field values defined on an input form-and passes this information to the Web server. When calling a server-side script, this information is passed to the script along with additional information the Web server makes available about itself. The script is then responsible for running its processing task and generating Web server-compliant output. The processing task could be anything from running a database query, adding records to a table, or simply sending back a plain HTML response page that shows the time of day.

Whenever a server-side script executes, it runs its own custom processing, but it is always required to return a response to the Web server. In most cases, the response is an HTML document that displays the results of the request: The result of a query or a confirmation or error page stating the result status of the request that was just processed. Although HTML output is the most common, other responses can also be sent, such as a request to authenticate a user, a redirection to send a user to another location, or a trigger to display a standard Web server error message.

Typically, a server script provides the following three functions:

Keep these three points in mind because flexibility in handling each of these tasks is important when choosing a tool with which to build Web applications.

How Visual FoxPro Fits In

Visual FoxPro is an excellent platform for building back-end applications because of its extremely fast data retrieval speed against local data, its flexibility when accessing remote data, and its powerful object-oriented, database-oriented language, which is ideal for programming complex data access and business rule logic.

Please note that to standardize this text, the following examples all use IIS. Some of the tools described work with other Web servers; I will point out what servers are supported.

NOTE
The rest of this chapter will focus on connecting Visual FoxPro databases to IIS, which is part of the operating system in Windows NT Server. Although some of the tools that will be mentioned work with other Web servers, I chose to standardize this discussion on IIS because it is built into Windows NT Server, which provides the ideal platform for hosting Web servers. Version 4.0 of Windows NT Server ships with this Web server.

Following are several mechanisms available for accessing Visual FoxPro data over the Web:

ODBC-based tools are easy to set up and get started and provide integrated solutions that are closely tied to the Web server. However, ODBC is comparatively slow when running against local data such as Visual FoxPro and does not scale well if your server load gets heavy.

Using Visual FoxPro on its own for serving Web requests has distinct advantages over ODBC, as you get a significant database speed improvement and much better flexibility when building Web back ends using the full functionality of the FoxPro language.

To run Visual FoxPro as a standalone back-end tool, Visual FoxPro must act as a data server, an application that is preloaded in memory waiting for requests. This is necessary to provide timely response to requests without having to incur the overhead of loading the entire Visual FoxPro runtime on each incoming hit. A data server can be implemented in a variety of ways, whether it's as a standalone application, an OLE server, or a DDE server. I will discuss several methods in the following sections.

Why You Should or Shouldn't Use Visual FoxPro

There are a couple of things to keep in mind when using Visual FoxPro as a database back end.

The following are advantages of using Visual FoxPro:

The biggest advantage of using Visual FoxPro as the database back end is its flexibility. Visual FoxPro is a high-end database tool, and you can take advantage of all the language and database functionality provided in it to create your Web application logic. You won't be limited by a cryptic general purpose scripting language, and there's no learning curve for new syntax (well, okay, you'll have to learn a little about HTML no matter how you slice it).

On the other hand, keep in mind the following disadvantages of using Visual FoxPro as a Web back end:

At first glance, these limitations seem major. However, they are easy to overcome with proper implementation of the data server. Visual FoxPro's single-threaded nature can be handled by running multiple simultaneous sessions of the data server, which essentially simulates a multithreaded environment. Although speed will decrease for simultaneous requests occurring in this fashion, the same applies to true multitasked tools. The CPU load is the real performance factor; and whether you're running one or 10 simultaneous sessions or threads, the actual load that a single server can handle depends on the number and power of the CPUs available to handle that load.

The latter problem of maintenance is one to carefully consider. Running Visual FoxPro as a data server means that the application can respond to requests only while it's up and running; crash the server and you won't process requests. It's extremely important to build bulletproof code that can recover from any error and continue running.

Running Visual FoxPro as its own server also means that a separate startup procedure is required. It's easy enough to stick a shortcut to the data server into the system startup folder to have it load automatically when the system is rebooted, but unless you have the system log in automatically, some manual intervention for logging in is required. Unlike Web servers, Visual FoxPro cannot easily be run as a system service. Running a Visual FoxPro OLE Server provides some automation of this process as the OLE server is treated as a system component that's accessible directly from the system.

Updating code or data also translates to shutting down the data server, and that means either physical access to the machine or accessing it via remote-control software such as pcAnywhere to make the changes online. With OLE servers, the Web server might need to be shut down to handle code updates.

For in-house Web installations, the latter points won't be much of a problem. However, these issues can be especially problematic if you plan to install your application on a third-party ISP's network, as this will in essence mean that you need extensive security rights to access your data server. ISPs can be very touchy about what goes on their network and who is given access to network resources.

The Internet Database Connector

The first and most straightforward method to access FoxPro data is one of the ODBC-based tools that is available with IIS. IIS ships with the Internet Database Connector (IDC), which is a simple script-based tool that enables accessing Visual FoxPro tables or any other ODBC-accessible data source. Output is accomplished via a simple HTML-like scripting language that can be used to display the results from a SQL statement (SELECT, INSERT, UPDATE, DELETE, and so on).

NOTE
The IDC uses SQL syntax that follows the ODBC SQL guidelines, which vary slightly from the Visual FoxPro SQL implementations. For example, you cannot call FoxPro's built-in functions from the SQL command line; instead, you have to use the ODBC/Transact SQL equivalent syntax.

To set up the IDC for use with a FoxPro database or directory of data files, start by configuring an ODBC datasource using the Visual FoxPro ODBC driver:

  1. Bring up the 32-bit ODBC manager from your taskbar menu.
  2. Click System DSN (the button on the bottom of the list window of installed drivers). It's extremely important that you add the new ODBC datasource using the system datasource rather than a standard ODBC source, or else IIS will fail to find your datasource and exit with an error. Figure 25.3 shows the resulting dialog box.
  3. Add a new datasource for the Visual FoxPro ODBC driver. For the demo, name the new data source QueVFP and point the driver at the directory with the sample data.

Figure 25.3 : Create a new System DSN using the Visual FoxPro ODBC driver and store it in the directory that contains your DBC or directory containing free tables.

After the ODBC driver is installed, you're ready to create the scripts that enable you to access this data. Take a look at Figure 25.4 and see how the data travels from Web browser to Web server and back to the browser.

Figure 25.4 : An HTML form link accesses the .IDC script containing the query information. Results are displayed in the .HTX document.

All in all, a request generated via the IDC requires three files: an HTML file that contains the link or form that launches the script, the .IDC file responsible for defining the query parameters, and the .HTX HTML file template that is used to display the output. The example provided here is extremely simple but serves to illustrate the various pieces of the database connector.

The .IDC and .HTX files need to be placed into a directory that has been set up for execution rights in the IIS Service Manager application. By default, IIS assigns a /scripts directory for server scripts, but it's usually a good idea to create a separate directory for each of your applications. The example below uses a /que directory for this. To create this directory, follow these steps:

  1. Bring up the IIS Service Manager.
  2. Create a new Virtual Directory on the Directory tab of the Service Manager by clicking Add.
  3. Set the directory to the physical DOS path of the script directory that you are creating.
  4. Set the Alias to the name that you would like to use as part of your URL. For example, /que translates to a full URL of http://servername.com/que/.
  5. Make sure you set the Execute check box to enable the server to execute a script in this directory.
  6. Read access is optional. If you place HTML files in this directory as well, check the Read check box.

After you set up the script directory on the server, you can get to work and call the HTML page. The HTML form that captures input from the user looks like this:

<HTML><BODY>
<FORM ACTION="/cgi-win/QUE.IDC" METHOD="POST">
  Enter Name to Lookup: <input name=Search size=20><br>
  <input type=submit value="Retrieve Names"></FORM>
</BODY></HTML>

The script is fired off by the ACTION tag of the input HTML form. Here, you're retrieving input from the user and storing it to an HTML variable named Search.

Running Server Scripts
You can also run an IDC script from an HREF link:
<A HREF="QUE.IDC?">Run Query</a>
Notice the trailing question mark when running in this fashion. The ? is required to tell the browser that the link is a script. Without the question mark, some browsers might attempt to download the script and display it.
The question mark serves as a delimiter to signify the end of the executable and the beginning of the query string or the parameter list. You can pass additional parameters on the URL that can be evaluated inside of the script as if they were entered on a form:
<A HREF="QUE.IDC?UID=00001&Name=Rick">
These values can be retrieved in the .IDC file by using %UID% or %Name%.

When the user clicks the Retrieve Names button on this form, the QUE.IDC script is called. Behind the scenes, IIS calls an ISAPI DLL called httpodbc.dll that handles the routing, parameter translation, and evaluation of the .IDC and .HTX script files. The .IDC script file contains all the parameters that are related to the query to run:

Datasource: QUEVFP
Template: que.htx
SQLStatement:
+SELECT Company, Careof, Phone
+ FROM TT_Cust
+ WHERE Company Like "%Search%%"
+ ORDER BY COMPANY

You can specify a host of other parameters that enable you to limit the number of records returned from the query, specify the name of the user for database access, and provide default parameter values.

You can also run multiple SELECT statements by including multiple SQL statement clauses in the .IDC file (multiple queries work only with IIS 2.0, which ships with NT 4.0), but keep in mind that these run one after the other immediately without allowing you to tie logic to them. If you're using SQL server, you can also execute Transact SQL syntax here and execute stored procedures.

The most important options are used in the preceding .IDC file: datasource name, SQL statement, and name of the .HTX template file that is loaded when the query completes.

Output from this query is created with the .HTX template file, which is essentially an HTML document that contains embedded field values. Listing 25.1 shows what the .HTX file looks like.


Listing 25.1  25CODE01.HTX-The .HTX Template File That Generates the HTML Output
<html><body>
<title>Query Results</title>
<h2>Internet Database Connector Result</h2>
<HR>
Here are the results of your query for company search string: <b><%idc.search%></B>
<p>

<TABLE CELLPADDING=5 BORDER=2 WIDTH=95% ALIGN=CENTER>
<TR>Company</TH>Contact</TH>Phone</TH>

<%begindetail%>
<TR><TD><%Company%></TD><TD><%CareOf%></TD><TD><%Phone%></TD></TR>
<%enddetail%>

</TABLE>
</body></html>

The <%BeginDetail%> and <%EndDetail%> tags provide a looping structure that runs through the result cursor. Any HTML between these two tags is repeatedly generated for each of the records in the result set. Field names can be embedded inside the page using the <%FieldName%> syntax that is used in the previous example. There are additional constructs available, such as a conditional <%IF%> (which can't be nested, though). For example:

<%if idc.company eq "West Wind Technologies"%>
    <H2>Special Message for West Wind Technologies</H2>
<%else%>
   <H2>Standard Message for <%idc.company%> </H2>
<%endif%>

For more detailed information on the .IDC and .HTX format options, you can look in the \IIS\IISADMIN\HTMLDOCS directory and search the index on the Database Connector (the actual page that contains this info is in \IIS\IISADMIN\HTMLDOCS\08_IIS.HTM).

The Internet database connector provides an easy mechanism for simple access to your Visual FoxPro data. When using this mechanism for accessing your data over the Web, keep the following advantages and disadvantages in mind:

Advantages:

Disadvantages:

The central advantage of the IDC is that it is well-integrated with the Web server and provides an easy way to get started connecting databases to the Web. On the downside, the scripting mechanism is limited in the functionality that is provided when accessing data and creating dynamic HTML output. Furthermore, ODBC is slow compared to running Visual FoxPro natively. ODBC makes good sense when running against remote server data, but provides limited scalability against local data such as FoxPro tables.

Using Visual FoxPro as a Data Server

ODBC works well for small and simple Web applications, but for better performance and the ultimate in flexibility when creating applications based on FoxPro data, a Visual FoxPro data server is the ticket.

What exactly is a data server? The term implies that Visual FoxPro is used as a server that responds to requests rather than running as an interactive application. Although it's possible to run a FoxPro .EXE file directly in response to a Web server request, there are several problems with this approach. A Visual FoxPro .EXE file takes several seconds to load under the best of circumstances, and loading the .EXE directly in this manner causes the application to run invisibly on the desktop, which makes it next to impossible to debug your code should something go wrong. It's much more efficient for Visual FoxPro to be already loaded, waiting for incoming requests from the Web server and instantly springing to life when a request is received. This always-on state is a requirement for Web applications where fast response time is crucial. Using a data server, it is possible to return data-based page responses in sub-second times.

To provide the data server functionality, it's necessary to use an intermediary piece of software, called a connector, that passes requests from the Web server to Visual FoxPro. These connector applications are usually small library-type routines that are written in C to provide a messaging interface that communicates between the Web server and a Visual FoxPro server that is waiting for incoming requests. Following are descriptions of two different implementations of FoxPro data servers using ISAPI-based connector applications.

Using FoxISAPI and OLE Servers for Web Applications

NOTE
Using FoxISAPI with an OLE server for Web applications requires that the user have an ISAPI-based Web server, such as MS IIS, Commerce Builder, or Purveyor.

Visual FoxPro's new capability to create OLE servers has brought about another slick option for implementing Visual FoxPro-based Web applications. What if you could use an OLE server to respond to a request placed from a Web page to handle the data processing and HTML page generation? With a tool called FoxISAPI that's provided with Visual FoxPro, you can do just that. You can find all the required files and an interesting example of an OLE server that makes use of FoxISAPI on your installation disk in the directory ..\SAMPLES\SERVER\FOXISAPI.

Before trying out this mechanism, be sure to read this entire section, especially the areas on setting up and creating the OLE server. Configuration is critical in getting FoxISAPI to work correctly.

How FoxISAPI Works  FoxISAPI consists of a small connector script .DLL that is called directly from an HTML page using a link similar to this:

<a HREF="/scripts/foxisapi.dll/
oleserver.myclass.mymethod?UID=1111&Company=Que+Publications">

The first thing that happens on a script call is that the FoxISAPI.dll is accessed. This .DLL is implemented as an Internet Server API (ISAPI) extension, which is an API that extends Internet Information Server via an in-process .DLL interface. Because ISAPI extensions run in the same address space as the Web server, are multithreaded, and are coded in a low-level compiled language such as C, these extension scripts are extremely fast.

The task of the ISAPI DLL is to provide an OLE Automation client that makes calls to your Visual FoxPro OLE server using the class ID (server, class, method) that is passed as part of the URL. The .DLL parses out the class string and makes an OLE Automation call to your OLE server accessing your class method directly. In response, your code should return a compliant result, which in most cases should be an HTML document. Figure 25.5 shows how a request travels from the Web server to your OLE server and back. Notice how FoxISAPI.dll is the mediator that receives both the outgoing script call and the incoming HTML output, sending the output to the Web server for display or processing.

Figure 25.5 : FoxISAPI enables calling OLE server methods directly via an HTML script link or form submission.

A Simple Example Server  The FoxISAPI example provided by Microsoft in your \VFP\SAMPLES\SERVERS\FOXISAPI directory is a good way to check out some of the things you can do with a FoxPro-based data server. But a simpler example might be more adequate in showing how FoxISAPI works. Listing 25.2 demonstrates how FoxISAPI.dll calls a Visual FoxPro OLE server.


Listing 25.2  25CODE02.PRG-A Minimal Response OLE Server Using FoxISAPI
#DEFINE CR CHR(13)+CHR(10)

*************************************************************
DEFINE CLASS QueVFP AS FOXISAPI OLEPUBLIC
*************************************************************

************************************************************************
* QueVFP : HelloWorld
*********************************
*  Function: Minimal response method for handling FoxISAPI call.
************************************************************************
FUNCTION HelloWorld
LPARAMETER lcFormVars, lcIniFile, lnReleaseFlag
LOCAL lcOutput

* HTTP header - REQUIRED on each request!
lcOutput="HTTP/1.0 200 OK"+CR+;
         "Content-type: text/html"+;
         CR+CR

lcOutput=lcOutput+;
"<HTML><BODY>"+CR+;
"<H1>Hello World from Visual FoxPro</H1>"+CR+;
"<HR>The current time is: "+TIME()+CR+;
"This page was generated by Visual FoxPro...<HR>"+CR+;
"</HTML></BODY>"

RETURN lcOutput
ENDFUNC
* FoxISAPI

ENDDEFINE
* QueVFP

This example doesn't do much, but it demonstrates the basics of what's required of a method that responds to a call from the FoxISAPI.DLL. The idea is this: FoxISAPI calls your OLE server method with three input parameters (described in Table 25.1), so your method must always support these three parameters. The parameters provide all the information made available by the HTML input form as well as Web server and browser stats. You pull the appropriate information from these references in order to do your data processing. For example, you might retrieve a query parameter and, based on it, run a SQL SELECT statement. To complete the process, your code needs to then return an HTTP-compatible response to the Web server. In most cases, this response is an HTML document, as demonstrated by the preceding example code, which simply returns an HTML page along with the time, so you can assure yourself that the page is actually dynamically generated. Notice that the output must include an HTTP header that is created by the first assignment to lcOutput.

Just like the HelloWorld example method, every response method must have three parameters. If your code has fewer than three parameters, the OLE call will fail, generating a FoxISAPI-generated error.

Table 25.1  FoxISAPI Response Method Parameters
Parameter
Contents
cFormVarsThis parameter contains all variable names and their contents in encoded form.
CIniFileFoxISAPI creates an .INI file containing server/browser variables, which are stored in an .INI file; the name and path of this file is passed in this parameter.
NreleaseFlagPassed by reference, this variable determines whether FoxISAPI.dll releases the reference to the OLE server. You can set this value in your code and it's returned to the .DLL. 0 is the default and means the server is not unloaded; 1 means it is unloaded.

The first parameter is probably the most important, as it contains the name and values of any fields that were filled out on an HTML form. All key/value pairs from an HTML form are returned; fields that are empty simply return an empty value. In typical CGI fashion, and because browsers do not support certain characters on the URL line, the string is MIME-encoded using various characters to signify "extended" characters and spaces. Before you can use the string, you typically have to decode it. Here's an example of a string passed in lcFormVars:

UserId=000111&BookTitle=Using+Visual+FoxPro

Each of the key/value pairs is separated by an ampersand (&) and spaces are converted to plus (+) signs. In addition, lower ASCII characters are converted into a hex code preceded by an ampersand. For example, a carriage return would be included as %0D (hex 0D or decimal 13).

Listing 25.2 doesn't use the parameters passed to it, so let's look at another example that does. Listing 25.3 demonstrates how to retrieve the information provided by the Web server using a simple FoxISAPI class provided in 25CODE02.prg. The code retrieves a couple of form variables passed on the URL of the request and then displays the entire .INI file in a browser window for you to examine.


Listing 25.3  25CODE03.prg-How to Retrieve Information Provided by the Web Server
************************************************************************
* QueVFP : TestMethod
*********************************
FUNCTION TestMethod
LPARAMETER lcFormVars, lcIniFile, lnReleaseFlag
LOCAL lcOutput

* Decode the Form Vars and assign INI file to class property
THIS.StartRequest(lcFormVars,lcIniFile)

* Must always add a content Type Header to output first
THIS.HTMLContentTypeHeader()

lcUserId=THIS.GetFormVar("UserId")
lcName=THIS.GetFormVar("UserName")

THIS.SendLn("<HTML><BODY>")
THIS.SendLn("<H1>Hello World from Visual FoxPro</H1><HR>")
THIS.SendLn("This page was generated by Visual FoxPro using FOXISAPI. ")
THIS.SendLn("The current time is: "+time()+"<p>")

THIS.SendLn("<b>Encoded Form/URL variables:</b> "+lcFormVars+"<BR>")
THIS.SendLn("<b>Decoded UserId:</b> "+ THIS.GetFormVar("UserId")+"<br>")
THIS.SendLn("<b>Decoded UserName:</b> " +lcName+"<P>")

* Show the content of the FOXISAPI INI server/browser vars
IF !EMPTY(lcIniFile) AND FILE(lcIniFile)
   CREATE CURSOR TMemo (TFile M )
   APPEND BLANK
   APPEND MEMO TFile from (lcIniFile)
   THIS.SendLn("Here's the content of: <i>"+lcIniFile+"</i>."+;
               "You can retrieve any of these with <i>"+;
               THIS.GetCGIVar(cVarname,cSection)+"</i>:<p>")
   THIS.SendLn([For example to retrieve the Browser use ]+;
               [THIS.GetCGIVar("HTTP_USER_AGENT","ALL_HTTP"): ]+;
               THIS.GetCGIVar("HTTP_USER_AGENT","ALL_HTTP") )

   THIS.SendLn("<PRE>")
   THIS.SendLn(Tmemo.Tfile)
   THIS.SendLn("</PRE>")
   USE in TMemo
ENDIF

THIS.SendLn("<HR></HTML></BODY>")

RETURN THIS.cOutput

The code makes heavy use of the FoxISAPI class' internal methods to simplify retrieving information and generating the output. Table 25.2 shows the public interface to the FoxISAPI class from which the QueVFP class in the examples is derived.

Table 25.2  FoxISAPI Class Methods
Method
Function
Send(cOutput, llNoOutput)A low-level output routine that simplifies SendLn(cOutput, llNoOutput) creating HTML output by using a method call. This is also useful for abstracting the output interface in case you want to modify the way output is generated later on. SendLn is identical to Send, but adds a carriage return to the output.
StandardPage(cHeader, cBody)A simplified routine that creates a full HTML page by passing a header and body. The page created includes minimal formatting and a title directive. Both header and body can contain embedded HTML codes that are expanded when the page is displayed.
ContentTypeHeader(llNoOutput) Generates the HTTP header required by FoxISAPI. Generates a default header for HTML documents. Required for each output page.
StartRequest()Call this method to automatically decode the FormVariable string and set up the internal handling for retrieving form and server variables using the following two methods. Required for each request that retrieves form or CGI variables using the internal methods.
GetFormVar(cVarName)Returns the value for the form variable passed as the first parameter. Note that only single variables are returned; there's no support for multiselects.
GetCGIVar(cCGIVar,cSection)Returns variables contained in the .INI file that is passed by FoxISAPI. Pass the name of the variable and the section that it is contained in. The default section is FoxISAPI.
ReleaseServer()A full request method that takes the standard three request parameters and sets the lnReleaseFlag to 1, thus forcing the FoxISAPI .DLL to release the OLE server reference.

In the Testmethod code, notice the calls to THIS.StartRequest and THIS.ContentTypeHeader. StartRequest sets up the internal variable retrieval routines by assigning the input parameters to class properties so that they can be easily referenced by the internal methods such as GetCGIVar and GetFormVar. Both of these routines make it easy to retrieve information related to the current request from the Web server's provided information. ContentTypeHeader creates the required header that must be sent back to the Web server in the result output. An HTTP header tells the server what type of content to expect and ContentTypeHeader obliges by providing the proper identification for an HTML document.

HTML output is accomplished by using the class Send method, which abstracts the output. Using this method is easier than concatenating strings manually and also provides the capability to build more complex output mechanisms that are required when your output gets longer than a few thousand characters. Behind the scenes, Send() does nothing more than add the text to a string property of the class. Notice that at the exit point, a RETURN THIS.cOutput is used to return the final result text to the FoxISAPI DLL.

The next snippet outputs the original lcFormVars encoded string and then uses GetFormVar() to print the decoded values of the actual values that were passed on the URL. GetFormVar() takes the name of the key as a parameter and returns the decoded value. If the key does not exist or the key is blank, a null string ("") is returned.

CGI variables returned by the server provide information about the server, browser, and the environment. FoxISAPI.dll captures most of the relevant information into an .INI file and the code wrapped in the IF statement outputs this file to the HTML page. All the keys are accessible with the GetCGIVar() method, which takes a keyname and section as parameters. For example, to retrieve the name of the Web server in use, you can use the following code:

THIS.GetCGIVar("Server Software ","FOXISAPI")

Let's take another look at the customer list example you used with the IDC and see how to implement it with FoxISAPI. Listing 25.4 shows the method code that accomplishes the task.


Listing 25.4  25CODE04-This Sample Method Generates a Customer List Based on a Name Provided on an HTML Form
************************************************************************
* QueVFP : CustomerLookup
*********************************
FUNCTION CustomerLookup
LPARAMETER lcFormVars, lcIniFile, lnReleaseFlag

* Decode the Form Vars and assign INI file to global var
THIS.StartRequest(lcFormVars,lcIniFile)

lcName=THIS.GetFormVar("Name")
lcCompany=THIS.GetFormVar("Company")
lcWhere=""
IF !EMPTY(lcName)
   lcWhere="UPPER(Careof)='"+UPPER(lcName)+"'"
ENDIF
IF !EMPTY(lcCompany)
   IF !EMPTY(lcWhere)
      lcWhere=lcWhere+" AND "
    ENDIF
    lcWhere=lcWhere+"UPPER(Client)='"+UPPER(lcCompany)+"'"
ENDIF
IF !EMPTY(lcWhere)
   lcWhere="WHERE "+lcWhere
ENDIF

SELECT Careof, Company, Address, Phone ;
   FROM (DATAPATH+"TT_CUST") ;
   &lcWhere ;
   INTO Cursor TQuery

IF _Tally <1
   THIS.StandardPage("No matching records found",;
                     "Please enter another name or use a shorter search
                      string...")
    USE IN Tquery
    USE IN TT_Cust
    RETURN THIS.cOutput
ENDIF

THIS.HTMLContentTypeHeader()

THIS.SendLn([<HTML><BODY>])
THIS.SendLn([<H1>Customer Lookup</H1><HR>])

This.SendLn([Matching found: ]+STR(_Tally)+[<p>])

THIS.Send([<TABLE BGCOLOR=#EEEEEE CELLPADDING=4 BORDER=1 WIDTH=100%>]+CR+;
          [<TR BGCOLOR=#FFFFCC>Name</TH>Company</TH>Address</TH>;
                       </ TR>]+CR)
SCAN
   THIS.Send(;
          [<TR><TD>]+;
          TRIM(IIF(EMPTY(TQUery.Careof),"<BR>",Tquery.CareOf))+[</TD><TD>]+;
          TRIM(IIF(EMPTY(Tquery.Company),"<BR>",Tquery.Company))+[Company</
          TD><TD>]+;
          TRIM(IIF(EMPTY(Tquery.Phone),"<BR>",TQuery.Phone))+[</TD></TR>]+CR)
ENDSCAN

THIS.SendLn([</TABLE><HR>])
THIS.SendLn([</BODY></HTML>])

USE IN Tquery
USE IN TT_Cust

RETURN THIS.cOutput
 * CustomerLookup

Setting Up for FoxISAPI  It's extremely important to correctly set up the Web server, the FoxISAPI.dll script connector, and your Visual FoxPro OLE server to get them to properly run under Windows NT. Windows NT 4.0 especially requires special attention to OLE server access rights and user configuration rights in order to run OLE servers driven by the Web server.

Here are the configuration steps for using FoxISAPI with Internet Information Server under NT 4.0:

Deciding What OLE Server Instancing to Use  Whenever you build an OLE server, one of the important issues you need to deal with is server instancing. A server must provide a separate, identical process to handle each client request; each use of the process is an instance. In Web applications, instancing is even more critical as timing and freeing up of the server for quick handling of requests are crucial to provide adequate Web performance.

Visual FoxPro is a single-threaded application and as such can handle only one request at a time. OLE servers, even multiuse servers, are no different. If an OLE server is busy, it cannot handle another request until it finishes. Furthermore, although ISAPI DLLs are multithreaded, FoxISAPI blocks simultaneous OLE server access in its code to prevent Visual FoxPro from taking more than one request at a time.

Your instancing options are to use the following:

The bottom line is that you should stick to DLL or multiuse servers. DLLs provide the best speed but they are volatile because a crash in the OLE server can crash the entire Web server and will require a server shutdown. Multiuse servers probably offer the best compromise between performance and flexibility. Speed for these multiuse servers is excellent and it is possible to shut them down without shutting down the Web server.

One major limitation of FoxISAPI to keep in mind is that you are limited in scalability. If you outgrow a single instance of your OLE server, FoxISAPI can't easily offload requests to another server. FoxISAPI can handle only one OLE Automation call at a time. It is possible to call different OLE servers simultaneously by making copies of FoxISAPI.dll and using a different name to call specific multiuse OLE servers, which are essentially one ISAPI DLL per OLE server. But even using this workaround, you can't call the same server simultaneously.

As long as a single machine and a single OLE server called serially can serve your needs, FoxISAPI provides a fast, efficient, and easily implemented interface to your FoxPro applications.

Using Web Connection for Web Applications

NOTE
In order to use Web Connection, you must have ISAPI or a Windows CGI-based Web server (IIS, Commerce Builder, Website, Purveyor for NT).

FoxISAPI provides an easy, speedy interface for creating Web applications. But for maximum flexibility and scalability, a standalone Web application that runs as a data server can provide much better scalability, maintenance, and debugging functionality. FoxISAPI is limited to a single OLE server of the same type for handling like requests, which can be a serious limitation on busy sites. In contrast, when running a standalone Visual FoxPro application, it is possible to run multiple instances of Visual FoxPro to provide an imitation of multithreading for a Visual FoxPro-based Web application. Running only two sessions, others have tested up to 120,000 database requests per day on a single dual-processor Pentium Pro machine on a live site. With a tool like Web Connection, it's even possible to further scale the application to multiple machines across a network for even greater scalability by having remote nodes handle data processing over the network.

NOTE
To provide working examples of how you can hook FoxPro code to process requests from a Web server, this section describes how to build Web applications using a third-party tool called Web Connection. A shareware version of the software can be found at http://www.west-wind.com.

Web Connection is a developer tool that provides a framework for connecting a Web server to Visual FoxPro. The framework provides many important features for developing industrial-strength Web applications: The capability to create external HTML pages with embedded FoxPro expressions to enable working with HTML designers, robust error handling and logging, automatic logging of Web hits, and an easy interface to retrieve information from and output HTML to the Web server.

Unlike FoxISAPI, which uses OLE as its messaging medium, Web Connection uses a file-based connector approach to communicate with the Web server. Figure 25.6 shows how data flows from the Web browser to the Web server to Visual FoxPro and your code.

Figure 25.6 : With Web Connection, Visual FoxPro acts as an active server waiting for incoming requests from the Web server, responding by executing FoxPro code and generating an HTML page.

From Web Server to Visual FoxPro  A typical request starts off on the Web browser where the user clicks either a hyperlink or the submit button of an HTML input form:

<A HREF="/cgi-win/wwcgi.dll?Test_Page">Simple CGI Test</A>

The wwcgi.dll script captures the information that the Web server and Web browser make available. Like FoxISAPI, Web Connection captures the content information, including the HTML form variables, into an .INI file that can be easily accessed with the application framework provided.

Instead of using an OLE server as FoxISAPI does, Web Connection employs a Visual FoxPro application based on a set of framework classes that wait for an incoming message that is provided by the wwcgi.dll. The message comes in the form of a small file that contains the path to the content .INI file the .DLL creates for each request. Web Connection picks up the file, retrieves the .INI file path, and creates a CGI object that exposes all of the .INI file's content.

NOTE
If you would rather use OLE messaging similar to FoxISAPI's instead of Web Connection's standard file messaging, you can also use Web Connection's OLE connector, which uses all the Web Connection framework classes. You can access servers using the OLE object syntax and still use the CGI, HTML, and CGIProcess classes described here for generating your request code.

The data server, which is responsible for picking up these requests, is implemented as a form class running with a timer and retrieves the filename and creates an object that facilitates access to the .INI file via a simplified class interface provided by the wwCGI class. After the Web Connection server has received the message file from the Web server, it takes the newly created CGI object and calls a user-defined function using the object as a parameter. This function is the hook that acts as an entry point for your own custom Visual FoxPro code.

Now it's your program's turn to take the information available via the CGI object and create HTML output. Your code can run any available FoxPro commands and functions, access class libraries, the data dictionary, and views to remote data-the entire language is available to you at this point. Web Connection facilitates creation of the HTML output by providing a CGIProcess class that contains both the CGI object passed to the Process function as well as an HTML object that is preconfigured to output the HTML (in most cases the response is HTML, but you can actually return any HTTP-compliant result) result to the proper output file.

After the HTML output has been generated, your custom code terminates and control returns to the wwCGIserver object, which, in turn, notifies the Web server that processing is complete. The Web server now takes the output file generated by your code and sends it over the Web for display by the Web browser.

A Look at the Components and the Setup Code  Before you dig in to the code, let's describe the components that make up Web Connection:

In its simplest form, startup of the Web Connection CGI server requires only a handful of lines of code, as shown in Listing 25.5.


Listing 25.5  25CODE05.PRG-Simplified Web Connection Server Startup Code
************************************************************************
FUNCTION CGITEST
******************
*   Function: Web Connection server startup program.
************************************************************************
#INCLUDE WCONNECT.H
SET PROCEDURE TO CGIServ ADDITIVE
SET PROCEDURE TO CGI ADDITIVE
SET PROCEDURE TO HTML ADDITIVE
SET PROCEDURE TO CGIPROC ADDITIVE
SET PROCEDURE TO WWUTILS ADDITIVE

* Starts up the server and gets it ready to poll
* for CGI requests. Call Process UDF() on a request
oCGIServer=CREATE("wwCGIServer","Process")
IF TYPE("oCGIServer")#"O"
   =MessageBox("Unable to load the CGI Request Server",;
      MB_ICONEXCLAMATION,"Web Connection Error")
   RETURN
ENDIF

oCGIServer.SetCGIFilePath("c:\temp\")

* This actually puts the server into polling mode - Modal Window
oCGIServer.show()
RETURN

The preceding code loads all the required code-based class libraries and simply creates a new wwCGIServer object. All the actual CGI request retrieval logic is handled transparently by this server class. The only crucial item in this piece of code is the second parameter in the CREATE command. The second parameter, Process, specifies a function of your choice that is called with a wwCGI object parameter each time a request is generated by the Web server. Notice the call to the SetCGIFilePath() method. The path specified here is inserted by the SETUP.APP installation program and should point to the location of the CGI temp files generated by each Web request.

When a request from the Web server hits, the code shown in Listing 25.6 is called.


Listing 25.6  25CODE06.PRG-The Process Procedure Is Called by Any Incoming Request
************************************************************************
FUNCTION Process
****************
*  Function: This is the program called by the CGI Server that
*            handles processing of a CGI request.
*
*            This example creates a process class, which
*            simplifies error handling and validation of
*            success. However, you can use procedural
*            code if you prefer.
*      Pass: loCGI -       Object containing CGI information
************************************************************************
LPARAMETERS loCGI

* Now create a process object. It's not necessary
* to use an object here, but it makes error handling
* document and CGI handling much easier!
loCGIProcess=CREATE("MyCGIProcess",loCGI)

* Call the Process Method that routes request types
* to methods in the loCGIProcess class
loCGIProcess.Process

* Debug: See what the input and output files look like
* RELEASE loCGIProcess  && Must release first or file isn't closed
* COPY FILE (lcIniFile) TO TEMP.INI
* COPY FILE (lcOutFile) TO TEMP.HTM
RETURN

This procedure is the entry point of the custom FoxPro code that can be executed in response to a Web server request. Notice that this function expects a wwcgi parameter when it is called from the Web server.

Although this routine creates another layer of abstraction by creating an instance of the CGIProcess class, this step is strictly optional (though highly recommended for ease of use). You could, at this point, use logic to retrieve the information passed by the Web server and start processing and generating HTML output right here. For maximum ease of use and maintainability, however, the CGIProcess class provides preconfigured settings that let you get to work immediately.

The Process Class: Putting Your Code to Work  For maximum ease of use and maintainability, the CGIProcess class created in Listing 25.6 exposes a framework that provides development and debug mode error handling, an easy mechanism for routing requests to your code and preconfigured CGI and HTML objects. With this class, adding your own code becomes as easy as adding a method to a subclassed version of the class.

Let's see how this works. First, here's a typical URL that generates a request in the running Web Connection server:

<A HREF="wwcgi.dll?MethodToCall~Parameter1~Parameter2">Que Test Request</a>

Notice the use of "parameters" on the URL to identify which method in the wwCGIProcess class to call.

When this request runs, the Web Connection server passes the request on to the Process function, which, in turn, creates a subclassed object of the wwCGIServer class as seen in Listing 25.6. After the object is created, its Process method is called.

Listing 25.7 contains a skeleton class definition.


Listing 25.7  25CODE07.PRG-A Skeleton Class Definition for Setting Up Your Own Request Handlers
*************************************************************
DEFINE CLASS webConnectDemo AS wwCGIProcess
*************************************************************
*  Function: This class handles the requests generated by
*            the wconnect.htm form and its results. The
*            class implementation makes error and output
*            doc handling much cleaner
*            Subclassed from a generic wwCGIProcess class
*            handler which provides error handling and
*            HTML and CGI object setup.
*************************************************************
* Properties defined by wwCGIProcess Parent Class
* -----------------------
* oCGI=.NULL.
* oHTML=.NULL.
* Methods defined by wwCGIProcess Parent Class
* ----------------------
* Init(oCGI) && Initializes and checks HTML and CGI objects
* Process    && Virtual Method always overridden, used to route requests
* Error      && Handles errors that occur in the Process code
* ErrorMsg(cErrorHeader,cMessage)  && Quick Message Display
************************************************************************
* webConnectDemo : Process
***************************
*  Modified: 01/24/96
*  Function: This is the callback program file that handles
*            processing a CGI request
*      Pass: THIS.oCGI  Object containing CGI information
*    Return: .T. to erase Temp File .F. to keep it
************************************************************************
FUNCTION Process
LOCAL lcParameter

* Retrieve first 'parameter' off the URL
lcParameter=UPPER(THIS.oCGI.GetCGIParameter(1))

DO CASE
   * Call the method if it exists
   CASE !EMPTY(lcParameter) AND PEMSTATUS(THIS,lcParameter,5)
      =EVALUATE("THIS."+lcParameter+"()")
   OTHERWISE
     * Generate Error Response Page
     THIS.ErrorMsg("The server was unable to respond "+;
         "to the CGI request.<br>"+;
         "Parameter Passed: '"+PROPER(lcParameter)+"'...",;
         "This error page is automatically called when a "+;
         "Visual FoxPro code error occurs while processing "+;
         "CGI requests.<p>It uses the wwHTML:HTMLError() method to "+;
         "output two error strings and generic server information, "+;
         "as well as overwriting existing HTML output for this request.")
ENDCASE
RETURN .T.

Function CustomMethod1
 ... Your code here
EndFunc

Function CustomMethod2
 ... Your code here
EndFunc

ENDDEFINE

You'll always create a Process method, which is used to route incoming CGI requests to the appropriate processing method within the wwCGIProcess subclass. This class can process requests generated by HTML tags with the following format:

/cgi-win/wwcgi.dll?Method ~Optional+Parameter ~Optional+Parm2

The method is an identifier that is used in the CASE statement to decide which method to call to process the request. The optional parameters are any additional parameters that you need to pass when processing a request. Note that the ~ is used as a parameter separator that is recognized by the wwCGI:GetCGIParameter(ParmNo) method to separate parameters passed on the URL following the ?.

Because of the way the class is designed, it consists almost entirely of your own custom code. The Process method is provided here more for reference than anything else; the code is actually defined in the base class. However, you often will want to override the Process method to use a more complex parameter scheme that enables you to call different classes for request processing (see the CGIMAIN example in the Web Connection samples to see how to call multiple projects from one session).

The main task of the Process method is to route the request by figuring out which method to call. This logic is handled by retrieving the first parameter on the URL, and then checking whether a method of that name exists with PEMSTATUS(). If the method exists, the EVALUATE() goes out and executes the method.

NOTE
PEMSTATUS() is an extremely powerful function for writing generic code that checks for the existence of class properties and methods. The function provides a mechanism to query all aspects of properties or methods. You can determine Public/Protected status, whether the value was changed from the default, whether the property is read only, and what type a property is.

What all of this does is provide you with an easy mechanism to hook your own code: All you have to do is add a method to your subclassed version of the CGIProcess class, and the code is practically called directly from a URL link.

Lights, Camera, Action  So what does the actual code you write to respond to requests look like? Let's take a look at a couple of examples.

For starters, let's use the same simple example you used with the Internet Database Connector:

<HTML><BODY>
<FORM ACTION="/cgi-win/wwcgi.dll?CustomerList" METHOD="POST">
  Enter Name to Lookup: <input name=Search size=20><br>
  <input type=submit value="Retrieve Names">
</FORM></BODY></HTML>

To respond to this request, add a new method to the wwConnectDemo class started previously (see Listing 25.8).


Listing 25.8  25CODE08.PRG-The Customer List Example Using Web Connection
************************************************************************
* wwConnectDemo : CustomerList
*********************************
*  Function: Returns an HTML table customer list.
************************************************************************
FUNCTION CustomerList
LOCAL loCGI, loHTML

* Easier reference
loCGI=THIS.oCGI
loHTML=THIS.oHTML

* Retrieve the name the user entered - could be blank
lcCustname=loCGI.GetFormVar("Search")

* Get all entries that have time entries (expense=.F.)
SELECT tt_cust.Company, tt_cust.careof, tt_cust.phone ;
   FROM TT_Cust ;
   WHERE tt_cust.company=lcCustname ;
   ORDER BY Company ;
   INTO CURSOR TQuery

IF _TALLY < 1
   * Return an HTML response page
   * You can subclass ErrorMsg to create a customized 'error page'
   THIS.ErrorMsg("No Matching Records Found",;
                 "Please pick another name or use fewer letters "+;
                 "to identify the name to look up")
   RETURN
ENDIF

* Create HTML document header
* - Document header, a Browser title, Background Image
loHTML.HTMLHeader("Customer List","Web Connection Customer List",;
                  "/wconnect/whitwav.jpg")

loHTML.SendLn("<b>Returned Records: "+STR(_TALLY)+"</b>")
loHTML.SendPar()
loHTML.SendLn("<CENTER>")

* Show entire result set as an HTML table
loHTML.ShowCursor()

* Center the table
loHTML.SendLn("</CENTER>")

loHTML.HTMLFooter(PAGEFOOT)

USE IN TQuery

ENDFUNC
* CustomerList

The logic of this snippet is straightforward. The code retrieves the value entered on the HTML form, runs a query, and then displays as an HTML table.

The important pieces in this code snippet are the uses of the CGI and HTML objects. As you can see, you don't need to create these objects because they are instantiated automatically when the CGIProcess object is created.

The loCGI.GetFormVar() method retrieves the single-input field from the HTML form. You can retrieve any field from an HTML form in this manner. Note that method always returns a string. If the form variable is not found, a null string ("") is returned, so it's safe in the preceding example to simply use the result in the query without further checks. The CGI class provides a ton of useful functionality. Here's a list of the most commonly used methods:

wwCGI Method
Function
GetFormVar(cFormVar)Retrieves a field entered on an HTML form.
GetFormMultiple(aParams)Retrieves multi-select field selections into an array.
GetBrowser()Returns the Browser ID string.
IsHTML30()Does the browser support HTML 3.0 extensions like tables?
IsSecure()Does the browser support secure transactions?
GetPreviousURL()Name of the page that generated this link.
GetRemoteAddress()Returns the IP address of the user.
GetCGIVar(cKey,cSection)Low-level CGI retrieval routine that enables retrieval of key values that don't have predefined methods. The section name defaults to the CGI section in the content .INI file.

The HTML class provides a simple output mechanism for generating HTML code. The class consists of both high-level and low-level methods that aid in creating your result output. At the low level are the Send() and SendLn() methods, which enable you to send string output to the HTML output file. It's entirely possible to generate your HTML output entirely using these two commands.

However, some of the higher-level functions can make life a lot easier. Here are a few of the functions available:

wwHTML Method
Function
Send()Lowest-level function. All output must go through this method to enable for different output methods. All the methods in this class call this method for final output.
SendLn()Identical to Send except it adds a carriage return/linefeed at the end.
HTMLHeader()Creates a standard HTML header with a title line, browser window title, and background image, as well as providing an easy mechanism to control HTTP headers passed back to the Web server.
HTMLFooter()Adds <HTML><BODY> tags and enables for sending a standard HTML footer for pages.
ShowCursor()Displays all fields of the currently open cursor/table as either an HTML table, or a <PRE> formatted list including headers and a title.
ShowMemoPage()Displays HTML text from either disk file or a memo field contained in a system table (wwHTML.dbf). The file can contain embedded FoxPro character expressions. This function enables you to work with HTML designers for data-driven pages.
MergeText()Merges text by translating embedded text expressions and returning the result. This function is more low level than ShowMemoPage and can be used for partial pages.
HRef()Creates a hotlink.
List()Creates various HTML list types.
HTMLError()Creates an entire HTML page with a couple of text input parameters. Great for quick status displays or error messages.
SendMemoLn()Formats large text fields.

The preceding example uses ShowCursor() to display the result table with a single line of code. This powerful method takes the currently selected table and parses out the headers and fields, creating an HTML table as output. ShowCursor() has the capability to display custom headers passed into the method as an array and the capability to sum numeric fields.

Another extremely powerful method of the wwHTML class is ShowMemoPage(). This method makes it possible to build external HTML pages stored on disk or in a memo field that can contain embedded FoxPro expressions and even entire code snippets to be evaluated by the Web Connection engine.

Now look at the more complex example in Listing 25.9, which provides an interactive guestbook browser. This example centers around a single page that shows a guestbook entry form, which is implemented as a standalone HTML document that contains embedded FoxPro fields.


Listing 25.9  25CODE09.wc-The HTML Template Page for the Guestbook Application Contains Embedded FoxPro Expressions and Fields
<HTML>
<HEAD><TITLE>West Wind Guest Book Browser</TITLE></HEAD>
<BODY Background="/wconnect/whitwav.jpg">
<p>
<IMG src="/wconnect/toolbar.gif" USEMAP="#entry" border=0, ismap HSPACE=20>
<MAP NAME="entry">
  <!- Image Map Coordinates here ->
</MAP>
<FORM ACTION="wwcgi.dll?ShowGuest ~Save ~##pcCustId##" METHOD="POST">
<INPUT TYPE="SUBMIT" VALUE="##IIF(pcCustId="NEW_ID","Add Info to","Update")##
       Guestbook" WIDTH=40>
<p>

##IIF(!EMPTY(pcErrorMsg),[<hr><font color="#800000"><h3>]+pcErrorMsg+[</h3>
        </font><hr>],"")##

<PRE>
Entered on: ##IIF(EMPTY(guest.entered),DTOC(date()),DTOC(guest.entered))##

Name: <INPUT TYPE="TEXT" NAME="txtName" VALUE="##guest. name##" SIZE="39">
Cust Id: ##pcCustId##
Company: <INPUT TYPE="TEXT" NAME="txtCompany" VALUE="##guest.company##" SIZE
        ="39">
Email: <INPUT TYPE="TEXT" NAME="txtEmail" VALUE="##guest.email##" SIZE="54">
Checking in from: <INPUT TYPE="TEXT" NAME="txtLocation" VALUE=
        "##guest.location##" SIZE="54">
</PRE>
<b>Leave a note for fellow visitors if you like:</b><br>
<TEXTAREA  NAME="txtMessage" ROWS=5 COLS=75>##guest.message##</TextAREA>
<BLOCKQUOTE>
    <b>Password:</b> <INPUT TYPE="TEXT" NAME="txtPassword" VALUE=
     "##pcPassword##" SIZE="8" MAXLENGTH="8"> (required to change entry)
</BLOCKQUOTE>
<CENTER>
<b>##STR(RecCount())## visitors have signed the guestbook.</b><p>
</CENTER>
</FORM>
<CENTER>
[<A HREF="/cgi-win/wwcgi.dll?ShowGuest ~Top ~##pcCustId##">First</A>]
[<A HREF="/cgi-win/wwcgi.dll?ShowGuest ~Previous ~##pcCustId##">Previous</A>]
[<A HREF="/cgi-win/wwcgi.dll?ShowGuest ~Next ~##pcCustId##">Next</A>]
[<A HREF="/cgi-win/wwcgi.dll?ShowGuest ~Bottom ~##pcCustId##">Last</A>]
[<A HREF="/cgi-win/wwcgi.dll?ShowGuest ~Add ~##pcCustid##">Add Entry</A>]
[<A HREF="/cgi-win/wwcgi.dll?BrowseGuests">Browse Guests</A>]
</CENTER>
<hr>

<IMG SRC="/wconnect/wcpower.gif" ALIGN="LEFT" HSPACE=5 ALT="Powered by
       Web Connection">
<FONT SIZE=-1><I>Query created by <A HREF="mailto:rstrahl@west-wind.com">
       Rick Strahl</A><br>
<A HREF="/wconnect/wconnect.htm">Web Connection demo page</A>
</BODY>
</HTML>

You'll notice the use of double pound signs (##) as delimiters to indicate embedded expressions in this HTML page. Between these delimiters you can find FoxPro character expressions that are evaluated by the ShowMemoPage() method. To use ShowMemoPage() in this manner, a Web Connection routine locates the record pointer(s) to the proper locations and then embeds the fields directly into the HTML form. When ShowMemoPage() is then called from code, it evaluates the character expressions and inserts the evaluated string in its place. Errors in expressions are automatically handled with an error string inserted instead.

The expressions can be database fields, variables, FoxPro expressions, even class method calls and user-defined functions. By storing this page in an externally edited file, it's possible to edit this page visually using an HTML editor such as FrontPage or WebEdit. As you might expect, this is easier and more maintainable than making changes inside of the actual FoxPro code. Furthermore, it enables you to design pages that can be edited by HTML designers who don't know anything about database programming.

Listing 25.10 contains the entire code for the Guestbook application shown in Figure 25.7. The code is wrapped into two methods that are part of the wwConnectDemo process class started above. This example is lengthy and a bit more complex than the previous one, but it demonstrates a full range of features in a realistic example of a Web application (you can find the code for this and the previous examples in the Web Connection examples).

Figure 25.7 : The Guestbook browser demonstrates how you can build interactive Web pages that act a lot like standalone applications.


Listing 25.10  25CODE10.PRG-A Guestbook Application Implemented in Web Connection
************************************************************************
* wwConnectDemo : ShowGuest
*********************************
*  Function: Guest Book Interactive Browser. Note that all this code
*            is not related to creating HTML at all, but rather
*            consists of setting up the logic for navigation and
*            adding editing entries.
************************************************************************
FUNCTION ShowGuest
LOCAL lcCustId, lcMoveWhere, llError
PRIVATE pcErrorMsg, pcPassword

loHTML=THIS.oHTML
loCGI=THIS.oCGI

* Retrieve the Operation option (Next, Previous etc.)
lcMoveWhere=UPPER(loCGI.GetCgiParameter(2))

* Grab the commandline Customer Id
lcCustId=loCGI.GetCGIParameter(3)

pcPassword=""
pcErrorMsg=""
llError=.F.

IF!USED("Guest")
  USE GUEST IN 0
ENDIF
SELE GUEST

IF EMPTY(lcCustId)
  lcMoveWhere="BOTTOM"
ELSE
  IF lcCustId#"NEW_ID"
     LOCATE FOR CustId=lcCustId
     IF !FOUND()
        pcErrorMsg="Invalid Record. Going to bottom of file..."
        lcMoveWhere="BOTTOM"
     ENDIF
  ENDIF
ENDIF

DO CASE
  CASE lcMoveWhere="GO"
     * Do nothing - just display
  CASE lcMoveWhere="NEXT"
     IF !EOF()
        SKIP
        IF EOF()
           pcErrorMsg="Last Record in table..."
        ENDIF
     ELSE
        GO BOTTOM
     ENDIF

     IF EOF()
        GO BOTTOM
        pcErrorMsg="Last Record in table..."
     ENDIF
  CASE lcMoveWhere="PREVIOUS" AND !llError
     IF !BOF()
        SKIP -1
     ENDIF
     IF BOF()
        pcErrorMsg="Beginning of File..."
     ENDIF

  CASE lcMoveWhere="TOP"
    GO TOP
    DO WHILE EMPTY(guest.name) AND !EOF()
       SKIP
    ENDDO

  CASE lcMoveWhere="BOTTOM"
     GO BOTTOM
     DO WHILE EMPTY(guest.name) AND !BOF() AND !EOF()
        SKIP -1
     ENDDO

  CASE lcMoveWhere="ADD"
     * Don't add record - move to 'ghost rec' to show blank record
     GO BOTTOM
     SKIP

     pcErrorMsg="Please fill out the form below and click the Save button..."

  CASE lcMoveWhere="SAVE"
     IF EMPTY(loCGI.GetFormVar("txtName")) AND ;
EMPTY(loCGI.GetFormVar("txtCompany"))
        THIS.ErrorMsg("Incomplete Input",;
"You have to enter at least a name or company.")
        USE IN GUEST
        RETURN
     ENDIF

     IF lcCustId="NEW_ID"
         APPEND BLANK
         REPLACE custid with sys(3), ;
                 entered with datetime(), ;
                 password with loCGI.GetFormVar("txtPassWord")
     ELSE
         * Check password
         pcPassWord=PADR(loCGI.GetFormVar("txtPassWord"),8)
         IF UPPER(guest.password) # UPPER(pcPassword)
            pcErrorMsg="The password you typed does not allow you to "+;
                       "change the selected entry..."
            pcCustId=guest.custid
            pcPassword=""
            loHTML.ShowMemoPage(HTMLPAGEPATH+"Guest.wc",.T.,"FORCE RELOAD")
            RETURN
         ENDIF
     ENDIF

     REPLACE name with loCGI.GetFormVar("txtName"), ;
             company with loCGI.GetFormVar("txtCompany"),;
             location with loCGI.GetFormVar("txtLocation"),;
             Email with loCGI.GetFormVar("txtEmail"),;
             Message with loCGI.GetFormVar("txtMessage")


      pcErrorMsg="Record saved..."
ENDCASE

* Prime pcCustId for all links
IF lcMoveWhere#"ADD"
   pcCustId=guest.custid
ELSE
   pcCustId="NEW_ID"
ENDIF
pcPassword=""

pcHomePath=HOMEPATH

* Display GUEST.WC - This HTML form contains the fields and
* pcErrorMsg variable...
loHTML.ShowMemoPage(HTMLPAGEPATH+"Guest.wc",.T.,;
       IIF(ATC("MSIE",loCGI.GetBrowser())>0,"ForceReload","text/html"))

IF USED("Guest")
   USE IN Guest
ENDIF

ENDFUNC
* ShowGuest

************************************************************************
* wwConnectDemo : BrowseGuests
*******************************
*  Function: Shows a list of Guests in table form for the Guest
*            Sample application.
*            This example manually creates the Browse page.
************************************************************************
FUNCTION BrowseGuests
LOCAL loHTML, loCGI, lcOrder

loHTML=THIS.oHTML
loCGI=THIS.oCGI

* Retrieve the Order Radio Button Value - Name, Company, Location
lcOrderVal=TRIM(loCGI.GetFormVar("radOrder"))
IF EMPTY(lcOrderVal)
  lcOrderVal="Name"
ENDIF

* Build an Order By expression
lcOrder="UPPER("+lcOrderVal+")"

* Create Cursor of all Guests - Note the URL link is
* embedded in the SQL-SELECT
SELECT [<A HREF="wwcgi.dll?ShowGuest ~Go ~ ]+custid+[">]+Name+;
       [</a>] as Guest, ;
   company, location,;
   &lcOrder ;
   FROM Guest ;
   ORDER BY 4 ;
   INTO CURSOR TQuery

* Set up so we can use HTML tables
loHTML.SetAllowHTMLTables(loCGI.IsNetscape())

loHTML.HTMLHeader("Guest Book Browser",,BACKIMG,;
       IIF(ATC("MSIE",loCGI.GetBrowser())>0,"Force Reload","text/Âhtml"))
loHTML.SendLn([ <FORM ACTION="wwcgi.dll?BrowseGuests" METHOD="POST">])
loHTML.SendLn([ Sort by: <input type="radio" value="Name" name="radOrder" ;
    ]+IIF(lcOrderVal="Name","checked=true","")+[>Name ])
loHTML.SendLn([ <input type="radio" value="Company" name="radOrder" ;
    ]+IIF(lcOrderVal="Company","checked=true","")+[> Company  ])
loHTML.SendLn([ <input type="radio" value="Location" ; name="radOrder"
    ]+IIF(lcOrderVal="Location","checked=true","")+[> Location<br>])
loHTML.SendLn([ <input type="submit" value="Change Order">])
loHTML.SendLn([ </FORM> <p>])

* Explicitly set up headers so we only display first 3 cols
DIMENSION laHeaders[3]
laHeaders[1]="Name"
laHeaders[2]="Company"
laHeaders[3]="Location"

* Display the table
loHTML.ShowCursor(@laHeaders)

loHTML.HTMLFooter(PAGEFOOT)

IF USED("Guest")
   USE IN Guest
ENDIF

ENDFUNC
* BrowseGuests

Looking at this code, you can tell that the majority of the logic that takes place has to do with the navigation aspect of the application rather than the actual Web/HTML dynamics. Most of the display issues are wrapped up in the external HTML template page, which is displayed in Listing 25.9.

One difference from a typical Visual FoxPro application is the fact that the application is implemented as a transaction-based process: Every action is a request that is processed by a method call. There is no event-driven programming happening here, and all validation is happening at the FoxPro back end with error messages being sent back as a message on a regenerated HTML page. The actual error message is embedded on the HTML page with the following expression:

##IIF(!EMPTY(pcErrorMsg),[<hr><font
color="#800000"><h3>]+pcErrorMsg+[</h3></font><hr>],"")##

pcErrorMsg is a PRIVATE variable that is set in the code and then passed down to the evaluation engine in ShowMemoPage(). Because the variable is PRIVATE, ShowMemoPage() can still access the pcErrorMsg variable, as it is still in scope. Thus, any variables that are declared PRIVATE or PUBLIC in the processing method can be accessed in a subsequent call to evaluate an HTML page processed with ShowMemoPage(). ShowMemoPage() (and MergeText(), which is the actual workhorse routine that ShowMemoPage() calls) also makes available the CGI and HTML objects as poCGI and poHTML, respectively. So, you could do something like this inside the HTML page:

##poCGI.GetBrowser()##
##poHTML.ShowCursor(,,,.t.)##

The first expression displays the name of the browser used to access the current page. The latter expression displays a table of the currently selected cursor and embeds it inside the HTML page. Notice that all the HTML class methods can also be used to return a string rather than sending output directly to file so that HTML class methods can be nested inside of each other. All HTML class methods have a logical llNoOutput parameter, which, if set to .T., will return the output as string only.

Working with Web Browsers

Browsers are the interface to the user, and although there's no direct interaction from the Web server back-end application to the browser, the application does provide the interface in a non-interactive fashion by sending back an entire HTML page to display.

To give you an idea of just how important Web browsers are, Microsoft has integrated the Web browser interface directly into Windows 98. In Internet Explorer 4.0, there is no distinction between the Web browser and the other interface components for file browsing and even for the display of desktop applications. With your desktop using a browser interface that is fully customizable, any application on your local computer or on the Internet is only a click away at any time. The browser is fast becoming the all-encompassing application that provides the functionality required for navigating the Web, your local network, and even your own computer and its applications.

From the aspect of back-end Web applications, browsers are very frustrating beasts in that they are both intensely visual and seemingly interactive, yet at the same time have a mentality that is reminiscent of the dumb terminal of the mainframe days. I already discussed the issue of no direct data connectivity between the browser and the server, which makes for a purely transaction-based interface to the data.

Nevertheless, browsers are getting smarter; and although the data connectivity is an issue that will not likely get resolved for some time given the infrastructure of the Web, a lot of logic is moving to the browser. New scripting languages such as Java, VBScript, and JavaScript enable extending the browser interface beyond its limited display-only capabilities. With these scripting languages, it is possible to build some validation logic into the client-side HTML pages. For example, you can validate input as long as no data lookup is required (that is, checking a phone number field for proper formatting, a credit card number to be valid, a proper state abbreviation). In addition, these scripting languages are extending the form control interface to be a lot more like a full GUI-based screen in that event code can be attached to input fields so that validation can be triggered automatically.

Although Java is getting all the attention these days, the simpler scripting languages such as VBScript and JavaScript make it easy to move some of the simpler validation-based and simple calculation-oriented functionality and implement it on the browser.

Because these scripting languages are implemented as HTML-embedded text that executes on the client side, you can use your Visual FoxPro back-end application to actually generate script code that runs when the page is displayed on the Web browser. It's sort of like getting a double execution punch.

One thing to be aware of is that not all browsers support all the features. Netscape and Microsoft are the market leaders with both of their browsers providing multitudes of extensions. Although the leading browsers from both companies are reasonably compatible and comprise 90 percent of the market, browsers from other companies do not support some of the more advanced features. If your site is public, hard choices have to be made whether to support only the most basic HTML features, or build state-of-the-art pages at the risk of turning away a small percentage of users with incompatible browsers.

Figure 25.8 shows an example of how taking advantage of browser-specific features can enhance the user interface. This page can be displayed in two modes: one using frames for IE 3.0 or Netscape 2.0 or later, and one using plain pages for browsers that do not yet support frames. The frames version is much more visually appealing and provides a more functional interface for navigating the site than a similar page that doesn't use frames. Notice the option on the frames page to go to no frames-important for laptop and low-resolution users. This site is providing a dual interface to serve both the latest HTML extensions and the low-end browsers, but a fair amount of extra design effort is required to provide this functionality.

Figure 25.8 : Taking advantage of new HTML extensions can make your site easier to use.

As a Web developer, you have to weigh carefully which features you want to implement. When building internal intranet applications, it's likely that you have control over the browser to use, so you can choose the one that provides the most functionality for the job at hand. For public applications, though, there is a tradeoff between using the newest, coolest features and leaving some users who haven't upgraded to the latest and greatest in the dust.

One strategy is to develop two sets of pages to satisfy both the hottest new developments and the older browsers. For example, the following function (implemented with Web Connection) checks for browser support and returns a letter that identifies the browser type: "F" for frames-based pages, "M" for IE 2.0, and "" for all others:

************************************************************************
* SurplusProcess : PageType
*********************************
*  Function: Returns the PageType based on the Browser name
*      Pass: llNoFrames    -  Override flag to use or not use frames
*    Return: "F"rames for Netscape 1.2/IE 3 or higher, "M" for MSIE 2
*            "" for all other Browsers
************************************************************************
FUNCTION PageType
LPARAMETERS llNoFrames
LOCAL lcBrowser, lcType

lcBrowser=UPPER(THIS.oCGI.GetBrowser())
lcType=""       && Default to non-frames page
IF  INLIST(lcBrowser,"MOZILLA/2.","MOZILLA/3.") AND !llNoFrames
   lcType="F"
ENDIF

* Return "M" for MS Internet Explorer 2.0
* only on the Homepage
IF llHomePage AND ATC("MSIE 2.",lcBrowser)>0
  lcType="M"
ENDIF

RETURN lcType
* PageType

When a page is loaded for display, it is then loaded with the appropriate prefix:

loHTML.ShowMemoPage(HTMLPAGEPATH+THIS.PageType(THIS.lNoFrames)+ Â"ShowCats.wc",.T.)

If all pages are dynamically loaded, it's now possible to load the appropriate page for the specified browser by checking for specific browsers that support frames. All static pages reference the appropriate frames pages with the F prefix and the nonframe pages point at the nonframe, nonprefixed page names.

Web Development Issues in Working with Visual FoxPro

Web development feels a lot like two steps forward and one step back. When building FoxPro-based Web applications, here are several issues that you have to keep in mind:

Internet-Enabling Your Applications

Internet-enabling your applications doesn't have to be as complete a job as rebuilding them to run over the Web. Instead, you can use smaller and more easily integrated enhancements such as the capability to send email over the Internet, uploading or downloading files via FTP, or accessing a Web site through a Web browser controlled with Visual FoxPro.

The following examples for SMTP email and FTP functionality use third-party ActiveX controls from Mabry Software. Shareware versions of the Mabry controls are available at http://www.mabry.com.

NOTE
The Web browser control example requires that you have Internet Explorer 3.0 or later installed on your system.

Downloading a File via FTP

The File Transfer Protocol (FTP) is the primary protocol used to transfer files between client and server machines. Although it's possible to download files directly over the World Wide Web simply by setting a link to point at a file, uploading files cannot easily be handled over the Web.

FTP is a widely used, relatively simple protocol. Using the Mabry FTP control, it's easy to build a class that enables sending and receiving of files via FTP. Figure 25.9 shows the class when running in visual mode, but the class also supports a programmatic interface that can run without displaying the form.

Figure 25.9 : The wwFTP class lets you upload and download files from an FTP site. Uploads require a username and password, whereas downloads can often be anonymous.

To create a form that contains the Mabry ActiveX FTP control, use the following steps:

  1. Create a new form and grab two OLE container controls from the Form Controls toolbar. Insert the Mabry Internet GetAddress Control and the FTP Control from the Insert Control radio button selected list.
  2. Assign ocxGetAddress and ocxFTP to the Name property of the controls in the property sheet.
  3. Add the following properties to the class: cFTPSite, cRemoteFile, cLocalFile, cUsername, and cPassword. These fields correspond to the input fields that are used on the form shown in Figure 25.9. Create input fields for each of these variables and point the datasource at the class properties you just created (that is, THISFORM.cFTPSite).
  4. Add buttons for Sending and Receiving files. The Send button should call THISFORM.UploadFile(), and the Receive button should call THISFORM.GetFile().
  5. Add the GetFile and Uploadfile class methods shown in Listing 25.11


Listing 25.11  25CODE11.vcx-The Getfile Method of the wwFTP Class Is the Workhorse That Sends and Receives Files
* Retrieves a file from an FTP site. All properties
* must be set prior to calling this method.
PROCEDURE GETFILE
LPARAMETER llSendFile
LOCAL lcSite

#DEFINE RETRIEVE_FILE  7
#DEFINE SEND_FILE  6

#DEFINE BINARY = 2

IF llSendFile
   lnMode=SEND_FILE
ELSE
   lnMode=RETRIEVE_FILE
ENDIF

lcSite=TRIM(THISFORM.cFTPSite)

THIS.statusmessage("Retrieving IP Address for "+lcSite)
THIS.cIPAddress=THISFORM.ocxGetAddress.GetHostAddress(lcSite)

IF EMPTY(THIS.cIPAddress)
   THIS.statusmessage("Couldn't connect to "+lcSite)
   RETURN
ENDIF

THIS.statusmessage("Connected to "+THIS.cIPAddress)

* Must Evaluate call to FTP Logon method since VFP balks at the Logon name
llResult=EVALUATE("THISFORM.ocxFTP.Logon( THIS.cIPAddress,TRIM(THIS.cUsername),;
                                          TRIM(THIS.cPassword) )")
IF !llResult
   THIS.statusmessage("Logon to "+lcSite+ " failed...")
   RETURN
ENDIF
THIS.statusmessage("Logged on to "+lcSite)

* Assign files to upload/download
THISFORM.ocxFTP.RemoteFileName = TRIM(THIS.cRemoteFile)
THISFORM.ocxFTP.LocalFileName = TRIM(THIS.cLocalFile)

IF llSendFile
   THIS.statusmessage("Uploading "+TRIM(THISFORM.cLocalFile) )
ELSE
   THIS.statusmessage("Downloading "+TRIM(THISFORM.cRemoteFile) )
ENDIF

THISFORM.ocxFTP.TransferMode = BINARY

* Send or Receive File
THISFORM.ocxFTP.Action=lnMode
ENDPROC

*- Uploads a file to the FTP site.
PROCEDURE uploadfile
THISFORM.GETFILE(.T.)  && .T. means send file
ENDPROC

The workhorse routine is the GetFile() method, which handles connecting to the FTP site, logging in, and then sending the file. The site connection is handled by the Mabry GetAddress Control, which resolves the domain name on the form (ftp.server.net, for example) to an IP address in the format of 000.000.000.000 that is required by the FTP control. A simple call to the control's GetHostAddress() method with the domain name returns the IP address. If the IP address cannot be resolved, a null string ("") is returned.

Once connected, the user must be logged in to the FTP site. When downloading files from public sites, it's often acceptable to access the site anonymously by specifying "anonymous" as both the username and password. For other sites and for uploads in general, a specific username and password might be required. The login operation is handled using the Login method of the ActiveX control, which takes an IP address and a username and password as a parameter. Due to a bug in Visual FoxPro, the Login method call is causing a compilation error, requiring the method to be called by embedding it inside of EVALUATE function call rather than calling the Login method directly:

llResult=EVALUATE( "THISFORM.ocxFTP.Logon(THIS.cIPAddress,THIS.cUsername,THIS.cPassword)")

The actual file transfer operation is handled asynchronously: the LocalFile and RemoteFile properties are set with the filenames and the Action property is set to either RETRIEVE_FILE or SEND_FILE. As soon as a value is assigned to the Action flag, the transfer is started and control returns immediately to your program while the file transfer occurs in the background. You can use the following IsBusy() method of the FTP class to determine whether the current transfer is done:

*- Returns whether a file transfer is in progress.
PROCEDURE IsBusy
#DEFINE FTP_IDLE 5
RETURN (THISFORM.ocxFTP.CurrentState <> FTP_IDLE )

You should also check this method prior to starting another transfer. To receive a completion message of the last file transfer, call the GetErrorMessage() method of the class.

This class is implemented as a form, which can be activated with:

PUBLIC oFTP
SET CLASSLIB TO QueIP ADDITIVE
oFTP=CREATE("wwFTP")
oFTP.show

However, you can also control the class programmatically in code or from the Command window:

oFTP=CREATE("wwFTP")

oFTP.cFTPSite="ftp.gorge.net"
oFTP.cRemoteFile="/westwind/ww_web.zip"
oFTP.cLocalFile="C:\ww_web.zip"

oFTP.cUsername="anonymous"
oFTP.cPassword="anonymous"

*!* oFTP.Show()    && If you want to show the form and property settings

oFTP.GetFile()

Sending SMTP Mail

The Simple Mail Transfer Protocol (SMTP) provides a simple interface to sending mail over the Internet. The Mabry SMTP mail control enables you to take advantage of this protocol to send e-mail messages across the Internet with relative ease.

NOTE
SMTP is an open protocol. When it originally was devised, it was designed to be accessible without any restrictions. This means a true SMTP mail server does not require a login and anybody on the Internet can use the server to send mail.
Newer versions of SMTP servers implement IP address restrictions and login requirements based on a username/password scheme. The SMTP control on its own does not support this functionality; you have to use the POP3 control to log in to the mail server and then use that connection to send SMTP mail.
The example described here works only with SMTP mail servers that do not require a login.

To create a form that contains the ActiveX SMTP control, use the following steps:

  1. Create a new form and grab a couple of OLE container controls from the Form Controls toolbar. Add a Mabry Internet GetAddress Control and the SMTP control to the form.
  2. Assign ocxSMTP to the control in the property sheet.
  3. Add the following properties to the class: cSendToAddress, cFromAddress, cSubject, cMessage, cMailServerName. These fields correspond to the input fields that are used on the form shown in Figure 25.10. Create input fields for each of these variables and set their Controlsource at the form property (that is, THISFORM.cSendToAddress). The form layout should look like Figure 25.10.
    Figure 25.10: The wwSMTP class lets you send an email message over the Internet. With its public class interface, you can populate the form programmatically.
  4. Add a Send button to the form. The Send button should call THISFORM.SendMail().
  5. Create a new method named SendMail with the code shown in Listing 25.12.


Listing 25.12  25CODE12.vcx-The SMTP Class' Sendmail Method Is Responsible for Sending the Actual Message After the Message Properties Have Been Filled
*- Used to send a message. Mail properties must be set prior to calling this method.
PROCEDURE Sendmail
LOCAL lcIPAddress

#DEFINE NORMAL_PRIORITY   3

IF EMPTY(THIS.cIPAddress)
   * Resolve Mail Server IP Address - you can speed this up by not
   * using this code and plugging the IP Address directly to the
   * mail control
   lcSite=TRIM(THISFORM.cMailServerName)

   * Clear out the IP Address first
   THIS.statusmessage("Retrieving IP Address for "+lcSite)

   lcIPAddress=THISFORM.ocxGetAddress.GetHostAddress(lcSite)

   IF EMPTY(lcIPAddress)
      THIS.statusmessage("Couldn't connect to "+lcSite)
   ENDIF
ELSE
   lcIPAddress=TRIM(THIS.cIPAddress)
ENDIF

THIS.statusmessage("Connected to "+lcIPAddress)

THISFORM.ocxSMTP.OriginatingAddress = TRIM(THIS.cFromAddress)
THISFORM.ocxSMTP.OriginatingName = TRIM(THIS.cFromName)
THISFORM.ocxSMTP.HostAddress = lcIPAddress
THISFORM.ocxSMTP.DomainName = "west-wind.com"
THISFORM.ocxSMTP.MailApplication = "West Wind Web Connection"
THISFORM.ocxSMTP.MailPriority = NORMAL_PRIORITY

THISFORM.ocxSMTP.DestinationUserList=TRIM(THISFORM.cSendToAddress)
THISFORM.ocxSMTP.CCUserList=TRIM(THISFORM.cCCAddress)
THISFORM.ocxSMTP.MailSubject=TRIM(THISFORM.cSubject)
THISFORM.ocxSMTP.MailBody=TRIM(THISFORM.cMessage)
THISFORM.ocxSMTP.MailAttachment=TRIM(THISFORM.cAttachment)

THISFORM.statusmessage("Sending Message to "+TRIM(THISFORM.cSendToAddress))
THISFORM.ocxSMTP.Action=1
RETURN

As with the FTP class example, this SMTP example also uses the Mabry GetAddress control to resolve the mail server's domain name (mail.server.net, for example) to an IP address, which is required by the mail control. Because it's quicker to not resolve the address, there's also an option to pass an IP address in the class's cIPAddress property, which causes the name lookup to be skipped.

After the mail server is identified, the form properties are collected setting the mail control's internal properties. Finally, the mail control's Action property is set to 1, which causes the message to be sent and control is returned to your code immediately. As with the FTP class, an IsBusy() method tells whether a mail transfer is still in process:

*- Determines whether the Mail Server is busy sending a message.
*- While busy no other messages can be sent.
PROCEDURE isbusy
#DEFINE SMTP_IDLE 5
RETURN (THISFORM.ocxSMTP.CurrentState <> SMTP_IDLE )

In order to determine the final status of a sent message, the Mail control's EndSendMail event is used to update the nErrorCode custom property set up on the form class:

*- Fires when a message send is complete or failed
PROCEDURE ocxSMTP.EndSendMail
LPARAMETERS errornumber
THISFORM.nErrorCode=errornumber
THISFORM.statusmessage("Mail Transport done - Error Code: 
                       "+STR(errornumber),;
                       THISFORM.geterrormessage(errornumber) )
ENDPROC

This code basically traps the error code and updates the form's status window with the error information. After the error code is set, it stays set until the next message is sent.

The class is implemented as a form that can be run interactively with the following code:

SET CLASSLIB TO WwIPControls ADDITIVE

PUBLIC oSMTP
oSMTP=CREATE("wwSMTP")
oSMTP.show

However, you can also control the class under program control or from the Command window:

PUBLIC oSMTP
oSMTP=CREATE("wwSMTP")

oSMTP.cMailServerName="mail.server.net"

oSMTP.cFromAddress="rstrahl@west-wind.com"
oSMTP.cFromName="Rick Strahl"
oSMTP.cSendToAddress="rstrahl@gorge.net"  && Use Commas to separate more
                                          && than one recipient
* oSMTP.cCCAddress="rstrahl@west-wind.com,rstrahl@gorge.net"

oSMTP.cSubject="Test Message from QUE's Using Visual FoxPro!"
oSMTP.cMessage="This is a test message generated by the SMTP example..."

* oSMTP.cAttachment="c:\autoexec.bat"
* oSMTP.Show()    && If you want to display the form

oSMTP.SendMail()

You can then use oSMTP.IsBusy() to determine whether the control is still busy and oSMTP.GetErrorMessage() to determine the last result message of the mail message.

Activating a Web Browser from Visual FoxPro

The following is a little routine that is handy for support features or cross-linking a FoxPro application to the Web. Windows 95/98 and Windows NT 4.0 support the capability to execute URLs directly via the OLE extension mappings supplied in the Registry. If you have a browser installed in your system, and it's the default browser, when you click on an HTML document in your browser, you can use the simple code in Listing 25.13 to activate the browser and go to the specified URL.


Listing 25.13  25CODE13.prg-This Routine Fires Up the Registered Browser and Goes to the Specified URL
* GoWeb: Test out the GoURL procedure with call
GoURL("www.inprise.com")
*
****************************************************
FUNCTION GoURL
******************
*  Function: Starts associated Web Browser
*            and goes to the specified URL.
*            If Browser is already open it
*            reloads the page.
*    Assume: Works only on Win 95/98 and NT 4.0
*      Pass: tcUrl  - The URL of the site or
*                     HTML page to bring up
*                     in the Browser
*    Return: 2 - Bad Association (invalid URL)
****************************************************
LPARAMETERS tcUrl

tcUrl=IIF(type("tcUrl")="C",tcUrl,;
          "http://microsoft.com/")

DECLARE INTEGER ShellExecute ;
    IN SHELL32.dll ;
    INTEGER nWinHandle,;
    STRING cOperation,;
    STRING cFileName,;
    STRING cParameters,;
    STRING cDirectory,;
    INTEGER nShowWindow

DECLARE INTEGER FindWindow ;
   IN WIN32API ;
   STRING cNull,STRING cWinName

RETURN ShellExecute(FindWindow(0,_SCREEN.caption),;
                    "Open",tcUrl,;
                    "","c:\temp\",0)

To activate a URL from code, you can then simply use the following code and the IE screen displays as shown in Figure 25.11:

GoURL("www.inprise.com")

Figure 25.11: This is an example of launching IE 4.0 from the code shown in Listing 25.13.

This function can be extremely useful for product support. You could, for instance, stick a button containing a call to this function onto a form, thereby sending users directly to your Web site for support, upgrades, or other information.

If you need more sophisticated control of URL access, you can also place a browser directly onto a Visual FoxPro form. The Microsoft Web Browser ActiveX control enables you to embed all of IE's functionality directly into your Visual FoxPro forms. How's that for full-featured power?

NOTE
Unlike other ActiveX controls, the MS Web Browser is not fully self-contained and requires a full installation of IE 4.0 to run. This means you cannot use this control on computers that do not have a copy of IE installed.

Visual FoxPro has added a Web browser control to its Visual FoxPro Foundation Classes. The WEB Browser control features a number of custom methods and events that enable you to control the Internet Explorer 4.0 operations. You can add the control on a form or a project from the Component Gallery. There is also a URL ComboBox control that manages history URLs in a FoxPro table. Both controls are in the in Foundation Class Internet folder in the Component Gallery Object pane. I have created the MyBrowser form to illustrate how easy it is to build your own custom Web Browser. Figure 25.12 shows the Component Gallery and the MyBrowser form in the Form Designer. Drag the Web Browser Control class and the URL Combo class to the form, and then add navigation buttons as shown.

Figure 25.12: This is an example of creating your own custom Web browser control, which enables inclusion of much of IE's advanced functionality in your own forms using drag-and-drop techniques.

I also added the Resize class to the form from the Foundation Class User Controls folder. It resizes all of the controls when you resize the form. Code was added to the form's Resize event that calls the Resize control's AdjustControls() method to perform control resize operations.

The buttons are used to navigate around the WWW. Each button calls one of the Web browser's methods. When you run the form, code exists in the form's Init event to initialize the Web browser and go to the user's home page. Note that a user-defined method, Navigate(), was added to the MyBrowser form. Form1.Navigate() calls the Web Browser class Navigate() method to navigate to a specified URL. The URL Combo class calls the THISFORM.Navigate() method and passes a user-entered URL. It calls Form1.Navigate() only when its lFormNavigate property is set to .T.. If lFormNavigate is set to .F., the URL Combo actually launches the default browser.

Other than the button Names and the Caption properties of the various components, the only properties that needed to be set were for the URL Combo control and included the following:

_URLCOMBOBOX1.cURL = "www.microsoft.com"  && Initial WEB page.
_URLCOMBOBOX1.lFormNavigate   = .T.
_URLCOMBOBOX1.lrequestonenter = .T.

The user-defined code for the events in the MyBrowser form is presented in Listing 25.14.


Listing 25.14  25CODE14-The Methods of the MyBrowser Form Enable Control Over Browser URL Navigation and the Resize Control
* User-defined Navigate procedure called by _URLComboBox1
* class to navigate to current URL
PROCEDURE Form1.Navigate
LPARAMETERS tcURLTHIS._WEBBROWSER41.Navigate(tcURL)ENDPROC*
* Initialize form event goes to home URLPROCEDURE Form1.Init
THIS._WEBBROWSER41.GoHome()
ENDPROC
*
* Form Resize event calls Resize control to resize controlsPROCEDURE Form1.ResizeTHIS.
  _Resizable1.AdjustControls()ENDPROC*
* Refresh button click event refreshes current URL
PROCEDURE RefreshButton.Click
THISFORM._WEBBROWSER41.Refresh2()
ENDPROC
*
* Back button click event goes to previous URLPROCEDURE BackButton.ClickTHISFORM.
  _WEBBROWSER41.GoBack()ENDPROC*
* Foreword button click event goes to next URLPROCEDURE ForwardButton.Click
THISFORM._WEBBROWSER41.GoForward()
ENDPROC
*
* Stop button click event stops opening current URLPROCEDURE StopButton.ClickTHISFORM.
  _WEBBROWSER41.STOP()ENDPROC*
* Home button click event goes to home URLPROCEDURE HomeButton.ClickTHISFORM.
  _WEBBROWSER41.GoHome()
ENDPROC

Figure 25.13 shows the MyBrowser custom browser when it runs. Notice that you can view and select the URL history items by clicking on the arrow on the right of the URL Combo.

Figure 25.13: This is an example of running your own custom Web browser control.

There are a number of additional events and methods that can be called. When you need them, you can look at the Help for the Foundation Class Web browser control.

If you don't like the small form, just maximize it. The Resize control enlarges all the controls as shown in Figure 25.14.

Figure 25.14: This is an example of running your own custom Web browser control in its maximized state, which complements the FoxPro Foundation Class Resize control.

Creating HTML Files from Forms, Reports, or Tables

There are FoxPro Foundation classes that convert a form, database, reports, labels, and menus to HTML. These classes provide controls to control the scope and layout. You can view these converted objects in a browser, send them as mail, or post them on a Web site. This capability is very useful for applications that need to periodically post Web pages containing data on the Internet or a company intranet.

You use one of these classes by placing one of the HTML classes (_spx2html, _dbf2html, or _frt2html) on a form. The builder appears and lets you specify the name of the source file (cSource property) and the name of the destination HTML file (cOutfile property). If you are displaying a Visual FoxPro table, you need to specify the cScope property, as in this example:

CScope = "Next 30"

Also, you can set the properties at runtime. When you are ready to convert one of these objects to HTML, you call the object's GenHTML() method. Usually, you place the call to GenHTML() method in a Save As Menu item. You can set the nGenOutput property of the object to specify the type of output you want, which is defined as follows:

nGenOutput value
Description
0
Generates an output file.
1
Generates an output file and display file in the Visual FoxPro editor.
2
Generates an output file and display file in IE.
3
Displays a Save As dialog box and lets the user name the output file.
4
Generates an output file and creates a PUBLIC _oHTML object.
5
Creates a PUBLIC _oHTML object.

Here is an example that illustrates how to convert a .DBF table to an HTML file. The plan is to create a form with a button you press to convert a .DBF table to an HTML file. Here are the steps to create such a form:

  1. Create a new form with the Screen Designer.
  2. Add a button control and set its Caption property to Save spx.dbf as HTML file.
  3. From the Foundation Classes Internet folder in the Component Gallery object pane, drag the DBF->HTML class to the form (see Figure 25.15).
    Figure 25.15: This is an example of creating a form that illustrates the conversion of a Visual FoxPro table to an HTML file. The DBF-> HTML class is dragged from the Component Gallery to the form.
  4. Add the following code to the Click event of the button control:
THISFORM._DBF2HTML1.cScope = "ALL"     && Display all recordsTHISFORM._DBF2HTML1.cSource=
   "SPX.DBF" && Convert this fileTHISFORM._DBF2HTML1.cOutfile= "SPX"
             && Name of HTML output
THISFORM._DBF2HTML1.nGenOutput = 2     && Create file; run IE4
THISFORM._DBF2HTML1.GenHTML()          && Do conversion

Figure 25.16 shows the appearance of the form when it runs. Press the button and the file SPX.HTM is created and displayed in the Internet Explorer as shown in Figure 25.17. There are other enhancements that can be made to the converted form. For example, the cStyle property defines numerous display styles that alter the behavior of the conversion.

Figure 25.16: This form converts a Visual FoxPro table (SPX.DBF) to an HTML file using the DBF-> HTML class.

Figure 25.17: IE displays the form (SPX.HML) resulting from the conversion.

The Web Publishing Wizard will also convert a .DBF table to an HTML file. This wizard, which is accessed from the Tools menu, lets you select various styles, add headings, add images, and add other objects. All you have to do is to answer the questions and the Web Publishing Wizard converts contents of a .DBF table to an HTML file. The wizard was used to convert the SPX.DBF table used in the previous example to an HTML file. The results are shown in Figure 25.18.

Figure 25.18: IE displays the form (SPX.HML) that was built using the Visual FoxPro Web Publishing Wizard.

Active Documents

One of the innovative new features of Visual FoxPro 6 is its support for Active documents. An Active document is a special type of OLE embeddable document in which you can display different types of non-HTML documents from varied sources in the same Active document Web browser host. One example of a browser host is IE. One example of a source is Visual FoxPro 6.

An Active document takes over the entire client window of its browser host. Any menus associated with an Active document are automatically inserted into its host menu and toolbar system.

A key feature of Active documents is that they provide a seamless integration with other Web pages that are viewed on the Web. You cannot tell the difference. The menu and toolbar commands of an Active document can be routed to its host. For example, you can have a print command that links directly to the IE print command.

The most compelling feature of Active documents is that you can write a Visual FoxPro Active document client application that can run in an environment that uses an HTML-type client interface.

How Do I Create an Active Document?

A Visual FoxPro Active document is created just like any other Visual FoxPro application and can perform the same tasks. Most everything you can do in a Visual FoxPro application, you can do in a Visual FoxPro Active document. If you know how to create a Visual FoxPro project to create an application, you already know how to create an Active document, and I suspect that if you have followed all the material so far in this book, you are already an expert application builder.

The fact is that you can launch any application from HTML from within the Internet Explorer. But Visual FoxPro Active document applications support the hooks (properties, methods, and events) into the Active document host. The main difference between a regular Visual FoxPro application and a Visual FoxPro Active document is that Active documents are based on the ActiveDoc base class. The ActiveDoc base class provides all the properties, events, and methods required by an Active document application to interface with the Active document host.

The entry point, or main file, for a normal Visual Foxpro application is in either a program or a form. However, the main file for an Active document must be a class derived from the ActiveDoc base class. You must use the Class Designer to create your new class based on the ActiveDoc base class. Here are the steps to create a Visual FoxPro Active document application:

  1. Create a new project named MyActiveDoc.pjx.
  2. From the Component Gallery My Base Classes folder, drag the _activedoc base class to the new project as shown in Figure 25.19. The Add Class to Project dialog appears.
    Figure 25.19: Drag the _activedoc base class from the Component Gallery to the Myactivedoc project to access the Add Class to Project dialog.
  3. Indicate that you want to create a new class from the selected class and press OK. The New Class dialog appears, as shown in Figure 25.20.
    Figure 25.20: The New Class dialog prompts you to enter a new class name.
  4. Enter the new derived class name, MyActiveDoc, and press OK. This process adds the vfpgry.vcx visual class library that contains the _activedoc (ActiveDoc) class to the project.
  5. Expand the vfpgry.vcx visual class library hierarchy by clicking on the plus (+) box at the left of the library name.
  6. Select the class based on the ActiveDoc base class. Right-click on the class and choose Set Main from the shortcut menu to make MyActiveDoc the main file.
  7. Now you need a form to display. From the Component Gallery My Base Classes folder, drag the _form base class to the Project Manager. The Add Class to Project dialog appears. Indicate that you want to create a new class from the selected class and press OK. The New Class dialog appears.
  8. Name the New Class MyForm and press OK. The Class Designer displays, showing the new form.
  9. Drag a _commandbutton class from the project to the MyForm form in the Class Designer. Change its Caption to Panic and add the following line of code to the Click event of the button:
    MessageBox("Do not Panic")
    
  10. Add the following line of code to the Destroy event of the MyForm class:
    CLEAR EVENTS
    
  11. Add any free table to the project (for the example, SPX.DBF was added).
  12. Drag the free table from the project to the MyForm form in the Class Designer and a grid object is placed on the form (see Figure 25.21). Also add a label with its Caption property set to This is an Active Document.
    Figure 25.21: The Class Designer is used to create the MyForm class that contains a button, a label, and a grid control.
  13. Close the MyForm class.
  14. Double-click on the MyActiveDoc class in the project. The Class Designer opens, showing the MyActiveDoc class. Add the following code to the Run event for this class:
    LOCAL odMyForm
    oMyForm = NewObject('myform','_base.vcx')
    oMyForm.SHOW()
    READ EVENTS
    
  15. Add the following code to the MyActiveDoc class:
    CLEAR EVENTS
    CLEAR ALL
    
  16. Close the Class Designer and click on the Project Manager's Build button to create the MyActiveDoc.app application.
  17. Choose Tools, Run Active Document and the Run Active Document dialog appears as shown in Figure 25.22. Enter the path and filename of the MyActiveDoc.app application and click OK.

Figure 25.22: The Run Active Document dialog prompts the user to enter the path and filename of the Active document.

IE is launched and runs the Active document. IE creates an Active document object from the MyActiveDoc class. The MyForm object is displayed as shown in Figure 25.23. The MyActiveDoc object responds to events and method calls. All components of the displayed form are active. You can press the Panic button and perform editing and navigation operations on the grid.

Figure 25.23: IE displays the Active document, MyActiveDoc. The MyForm form object is shown.

ActiveDoc Object Properties, Events, and Methods

The ActiveDoc class contains properties, events, and methods, which are defined in Tables 25.3, 25.4, and 25.5, respectively.

Table 25.3  ActiveDoc Class Properties
Property
Description
BaseClassContains the name of the Visual FoxPro base class on which the referenced object is based.
CaptionSpecifies the text displayed in an object's caption.
ClassContains the name of the class on which an object is based.
ClassLibraryContains the filename of the user-defined class library that contains the object's class.
CommentStores information about an object.
ContainerReleaseTypeSpecifies whether an Active document remains open and running when it is released by its host.
NameSpecifies the name used to reference an object in code.
ParentReferences the container object of a control.
ParentClassSpecifies the name of the class on which the object's class is based.
TagStores any extra data needed for your program.

Table 25.4  ActiveDoc Class Events
Event
Description
CommandTargetExecThis event is triggered when an Active document host notifies an Active document that a command is to be executed.
CommandTargetQueryWhen an Active document host updates its user interface, this event is triggered.
ContainerReleaseWhen a host releases an Active document, this event is triggered.
DestroyWhen the Active document is released, this event is triggered.
ErrorWhen a runtime error in an Active document's method occurs, this event is triggered.
HideDocWhen the user navigates from an Active document, this event is triggered.
InitThis event is triggered when the Active document is created.
RunThis event is triggered when an Active document is prepared to run user-defined code. The Run command passes a URL to the Run event.
ShowDocThis event is triggered when the user navigates to an Active document.

Table 25.5  ActiveDoc Class Methods
MethodDescription
AddProperty (cPropertyName [, eNewValue]) This method adds a property to an object.
ReadExpression(cPropertyName) This method returns the expression that the user enters in the Properties window for the specified property.
ReadMethod(cMethod)This method returns the text associated with the specified method.
ResetToDefault(cValue)This method restores a property, event, or method to its Visual FoxPro default setting. All of the user-defined code is removed if you are resetting a method or event. cValue is the name of the property, event, or method.
SaveAsClass (ClassLibName, ClassName [, Description]) This method saves an instance of a specified object as a class definition in the specified class library.
WriteExpression [cPropertyName, cExpression] This method writes the specified expression to a property.

New Visual FoxPro Extensions That Support Active Documents

GETHOST() and ISHOSTED()were added to Visual FoxPro in version 6. These functions provide information relating to the host of an Active document. The GETHOST() function returns an object reference to the host of an Active document. The ISHOSTED() returns a true (.T.) value if the host of an Active Document exists. Otherwise, it returns a false (.F.) value.

In addition, various properties, events, and functions were added to form objects to support Active document applications.

Running Active Documents

Visual FoxPro Active documents require Vfp6.exe and Vfp6run.exe, or Vfp6run.exe, Vfp6r.dll, and Vfp6renu.dll (enu denotes the English version) to run. These files must be installed and registered on the computer on which IE is installed. When Visual FoxPro is installed, Vfp6.exe is installed in the Visual FoxPro directory, and the remaining files are installed in the Windows 95/98 Windows\System directory or the Windows NT WinNT\System32 directory.

The following options are available:

You can also run an Active document by opening it from the Open File dialog box in IE, or by navigating to the Active document from another Web page with a hyperlink to the Active document.

The Visual FoxPro Runtime and Active Documents  From Visual FoxPro you can run an Active document by double-clicking the Active Document icon in the Windows Explorer. You can also run an Active document from a Visual FoxPro runtime application. The Visual FoxPro runtime consists of two files, Vfp6run.exe and Vfp6r.dll. Both must be installed and registered to run Active documents. The runtime can also be used to run other Visual FoxPro distributable files such as compiled Visual FoxPro programs (.fxp files).

Vfp6run.exe, once registered, can be used to run Active documents (and other Visual FoxPro distributable files) directly.

Here is the syntax for Vfp6run.exe:

VFP6RUN [/embedding] [/regserver] [/unregserver] [/security]
[/s] [/version] [FileName]

The arguments are described as follows:

/embeddingLoads Vfp6run.exe as an Active document server. In this mode, Vfp6run.exe is registered as a COM server capable of creating a Visual FoxPro Active document object. Without this argument, Vfp6run.exe doesn't act as a COM server.

TIP
Chapter 22, "Creating COM Servers with Visual FoxPro," contains detailed information on Visual FoxPro's COM capabilities.

/regserverRegisters Vfp6run.exe.
/unregserverUnregisters Vfp6run.exe.
/securityDisplays the Application Security Settings dialog box, enabling you to specify the security settings for Active documents and other application (.APP) files.
/sSilent. Specifies that an error is generated if Vfp6run.exe is unable to load the Vfp6r.dll runtime component.
/versionDisplays Vfp6run.exe and the Vfp6r.dll version information.
FileNameSpecifies the Visual FoxPro file to run.

Vfp6run.exe requires that the runtime support dynamic link library Vfp6r.dll be installed and registered. To register Vfp6r.dll, run Regsvr32 with the name of the runtime:

Regsvr32 Vfp6r.dll.

© Copyright, Sams Publishing. All rights reserved.