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 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.
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.
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.
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.
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.
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.
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:
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.
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.
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.
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 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:
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.
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:
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.
<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.
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.
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.
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.
#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.
cFormVars | This parameter contains all variable names and their contents in encoded form. |
CIniFile | FoxISAPI 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. |
NreleaseFlag | Passed 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.
************************************************************************ * 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.
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.
************************************************************************ * 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.
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.
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.
************************************************************************ 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.
************************************************************************ 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.
************************************************************* 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).
************************************************************************ * 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:
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:
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.
<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).
************************************************************************ * 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.
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 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 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. |
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.
To create a form that contains the Mabry ActiveX FTP control, use the following steps:
* 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()
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:
*- 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.
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.
* 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.
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.
* 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.
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:
Generates an output file. | |
Generates an output file and display file in the Visual FoxPro editor. | |
Generates an output file and display file in IE. | |
Displays a Save As dialog box and lets the user name the output file. | |
Generates an output file and creates a PUBLIC _oHTML object. | |
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:
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.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.
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.
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:
MessageBox("Do not Panic")
CLEAR EVENTS
LOCAL odMyForm oMyForm = NewObject('myform','_base.vcx') oMyForm.SHOW() READ EVENTS
CLEAR EVENTS CLEAR ALL
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.
The ActiveDoc class contains properties, events, and
methods, which are defined in Tables 25.3, 25.4, and 25.5, respectively.
BaseClass | Contains the name of the Visual FoxPro base class on which the referenced object is based. |
Caption | Specifies the text displayed in an object's caption. |
Class | Contains the name of the class on which an object is based. |
ClassLibrary | Contains the filename of the user-defined class library that contains the object's class. |
Comment | Stores information about an object. |
ContainerReleaseType | Specifies whether an Active document remains open and running when it is released by its host. |
Name | Specifies the name used to reference an object in code. |
Parent | References the container object of a control. |
ParentClass | Specifies the name of the class on which the object's class is based. |
Tag | Stores any extra data needed for your program. |
CommandTargetExec | This event is triggered when an Active document host notifies an Active document that a command is to be executed. |
CommandTargetQuery | When an Active document host updates its user interface, this event is triggered. |
ContainerRelease | When a host releases an Active document, this event is triggered. |
Destroy | When the Active document is released, this event is triggered. |
Error | When a runtime error in an Active document's method occurs, this event is triggered. |
HideDoc | When the user navigates from an Active document, this event is triggered. |
Init | This event is triggered when the Active document is created. |
Run | This event is triggered when an Active document is prepared to run user-defined code. The Run command passes a URL to the Run event. |
ShowDoc | This event is triggered when the user navigates to an Active document. |
Method | Description |
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. |
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.
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:
/embedding | Loads 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. |
/regserver | Registers Vfp6run.exe. |
/unregserver | Unregisters Vfp6run.exe. |
/security | Displays the Application Security Settings dialog box, enabling you to specify the security settings for Active documents and other application (.APP) files. |
/s | Silent. Specifies that an error is generated if Vfp6run.exe is unable to load the Vfp6r.dll runtime component. |
/version | Displays Vfp6run.exe and the Vfp6r.dll version information. |
FileName | Specifies 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.