Delphi Programming Guide
Delphi Programmer 

Menu  Table of contents

Part I - Foundations
  Chapter 1 – Delphi 7 and Its IDE
  Chapter 2 – The Delphi Programming Language
  Chapter 3 – The Run-Time Library
  Chapter 4 – Core Library classes
  Chapter 5 – Visual Controls
  Chapter 6 – Building the User Interface
  Chapter 7 – Working with Forms
Part II - Delphi Object-Oriented Architectures
  Chapter 8 – The Architecture of Delphi Applications
  Chapter 9 – Writing Delphi Components
  Chapter 10 – Libraries and Packages
  Chapter 11 – Modeling and OOP Programming (with ModelMaker)
  Chapter 12 – From COM to COM+
Part III - Delphi Database-Oriented Architectures
  Chapter 13 – Delphi's Database Architecture
  Chapter 14 – Client/Server with dbExpress
  Chapter 15 – Working with ADO
  Chapter 16 – Multitier DataSnap Applications
  Chapter 17 – Writing Database Components
  Chapter 18 – Reporting with Rave
Part IV - Delphi, the Internet, and a .NET Preview
  Chapter 19 – Internet Programming: Sockets and Indy
  Chapter 20 – Web Programming with WebBroker and WebSnap
  Chapter 21 – Web Programming with IntraWeb
  Chapter 22 – Using XML Technologies
  Chapter 23 – Web Services and SOAP
  Chapter 24 – The Microsoft .NET Architecture from the Delphi Perspective
  Chapter 25 – Delphi for .NET Preview: The Language and the RTL
       
  Appendix A – Extra Delphi Tools by the Author
  Appendix B – Extra Delphi Tools from Other Sources
  Appendix C – Free Companion Books on Delphi
       
  Index    
  List of Figures    
  List of tables    
  List of Listings    
  List of Sidebars  

 
Previous Section Next Section

WebSnap

Now that I've introduced the core elements of developing web server applications with Delphi, let's move to a more complex architecture available since Delphi 6: WebSnap. There are two good reasons I didn't jump into this topic at the beginning of this chapter. First, WebSnap builds on the foundation offered by WebBroker, so you cannot learn how to use the new features if you don't understand the underlying ones. For example, a WebSnap application is technically a CGI program, or an ISAPI or Apache module. Second, because WebSnap is included only in the Enterprise Studio version of Delphi, not all Delphi programmers have the chance to use it.

WebSnap has a few definite advantages over WebBroker, such as allowing for multiple web modules each corresponding to a page, integrating server-side scripting, XSL, and the Internet Express technology (these last two elements will be covered in Chapter 22, "Using XML Technologies"). Moreover, many ready-to-use components are available for handling common tasks, such as user logins, session management, and so on. Instead of listing all the features of WebSnap, though, I've decided to cover them in a sequence of simple and focused applications. I built these applications using the Web App Debugger, for testing purposes, but you can easily deploy them using one of the other available technologies.

When you're developing a WebSnap application, the starting point is a dialog box you can invoke either in the WebSnap page of the New Items dialog box (File ® New ® Other) or using the Internet toolbar in the IDE (which is not visible by default). The resulting dialog box, shown in Figure 20.6, allows you to choose the type of application (as in a WebBroker application) and to customize the initial application components (you'll be able to add more later). The bottom portion of the dialog determines the behavior of the first page (usually the default or home page of the program). A similar dialog box is displayed for subsequent pages.


Figure 20.6: The options offered by the New Web-Snap Application dialog box include the type of server and a button that lets you select the core appli-cation components.

If you choose the defaults and type in a name for the home page, the dialog box will create a project and open a TWebAppPageModule. This module contains the components you've chosen, by default:

  • A WebAppComponents component is a container for all the centralized services of the WebSnap application, such as the user list, core dispatcher, session services, and so on. Not all of its properties must be set up, because an application might not need all the available services.

  • One of these core services is offered by the PageDispatcher component, which (automatically) holds a list of the application's available pages and defines the default page.

  • Another core service is given by the AdapterDispatcher component, which handles HTML form submissions and image requests.

  • The ApplicationAdapter is the first component you encounter from the adapters family. These components offer fields and actions to the server-side scripts evaluated by the program. Specifically, the ApplicationAdapter is a field adapter that exposes the value of its own ApplicationTitle property. If you enter a value for this property, it will be made available to the scripts.

  • The module hosts a PageProducer that includes the HTML code of the page—in this case, the program's default page. Unlike in WebBroker applications, the HTML for this component is not stored inside its HTMLDoc string list property or referenced by its HTMLFile property. The HTML file is an external file, stored by default in the folder hosting the project's source code and referenced from the application using a statement similar to a resource include statement: {*.html}.

  • Because the HTML file included by the PageProducer is kept as a separate file (the LocateFileService component will eventually help you for its deployment), you can edit it to change the output of a page of your program without having to recompile the application. These possible changes relate not only to the fixed portion of the HTML file but also to some of its dynamic content, thanks to the support for server-side scripting. The default HTML file, based on a standard template, already contains some scripting.

Warning 

The similarity between resource inclusion and HTML reference is mostly syntactical. The HTML reference is used only for the design-time location of the file, whereas a resource include directive links the data it refers into the executable file.

You can view the HTML file in the Delphi editor thanks to that directive (with reasonably good syntax highlighting) by selecting the corresponding lower tab. The editor also has pages for a WebSnap module, including by default an HTML Result page where you can see the HTML generated after evaluating the scripts, and a Preview page hosting what a user will see in a browser. The Delphi 7 editor for a WebSnap module includes a much more powerful HTML editor than Delphi 6 had; it includes better syntax highlighting and code completion. If you prefer to edit your web application's HTML with a more sophisticated editor, you can set up your choice in the Internet page of the Environment Options dialog box. Click the Edit button for the HTML extension, and you can choose an external editor from the editor's shortcut menu or a specific button on Delphi's Internet toolbar.

The standard HTML template used by WebSnap adds to any page of the program its title and the application title, using script lines such as these:

<h1><%= Application.Title %></h1>
<h2><%= Page.Title %></h2>

We'll get back to the scripting in a while; we'll begin developing the WSnap1 example in the next section by creating a program with multiple pages. But first, I'll finish this overview by showing you the source code for a sample web page module:

type
  Thome = class(TWebAppPageModule)
    ...
  end;
   
function home: Thome;
   
implementation
   
{$R *.dfm}  {*.html}
   
uses WebReq, WebCntxt, WebFact, Variants;
   
function home: Thome;
   
begin
  Result := Thome(WebContext.FindModuleClass(Thome));
end;
   
initialization
  if WebRequestHandler <> nil then
   
    WebRequestHandler.AddWebModuleFactory(TWebAppPageModuleFactory.Create(
      Thome, TWebPageInfo.Create([wpPublished {, wpLoginRequired}], '.html'),
        caCache));
end.

The module uses a global function instead of a form's typical global object to support page caching. This application also has extra code in the initialization section (particularly registration code) to let the application know the role of the page and its behavior.

Tip 

Unlike this chapter's WebBroker examples, the WebSnap examples compile each in its own folder. This is because the HTML files are needed at runtime and I preferred simplifying the deployment as much as possible.

Managing Multiple Pages

The first notable difference between WebSnap and WebBroker is that instead of having a single data module with multiple actions eventually connected to producer components, WebSnap has multiple data modules, each corresponding to an action and having a producer component with an HTML file attached to it. You can add multiple actions to a page/module, but the idea is that you structure applications around pages rather than actions. As is the case with actions, the name of the page is indicated in the request path.

As an example, I added two more pages to the WebSnap application (which was built with default settings). For the first page, in the New WebSnap Page Module dialog (see Figure 20.7) choose a standard page producer and name it date. For the second, choose a DataSetPageProducer and name it country. After saving the files, you can begin testing the application. Thanks to some of the scripting I'll discuss later, each page lists all the available pages (unless you've unchecked the Published check box in the New WebSnap Page Module dialog).


Figure 20.7:  The New WebSnap Page Module dialog box

The pages will be rather empty, but at least you have the structure in place. To complete the home page, you'll edit its linked HTML file directly. For the date page, use the same approach as a WebBroker application. Add some custom tags to the HTML text, like the following:

<p>The time at this site is <#time>.</p>

I also added code to the producer component's OnTag event handler to replace this tag with the current time.

For the third page (the country page), modify the HTML to include tags for the various fields of the country table, as in:

<h3>Country: <#name></h3>

Then attach the ClientDataSet to the page producer:

object DataSetPageProducer: TDataSetPageProducer
  DataSet = cdsCountry
end
object cdsCountry: TClientDataSet
  FileName = 'C:\Program Files\Common Files\Borland
    Shared\Data\country.cds'
end

To open this dataset when the page is first created and reset it to the first record in further invocations, you handle the OnBeforeDispatchPage event of the web page module, adding this code to it:

cdsCountry.Open;
cdsCountry.First;

The fact that a WebSnap page can be very similar to a portion of a WebBroker application (basically, an action tied to a producer) is important, if you want to port existing WebBroker code to this new architecture. You can even port your existing DataSetTableProducer components to the new architecture. Technically, you can generate a new page, remove its producer component, replace it with a DataSetTableProducer, and hook this component to the PageProducer property of the web page module. In practice, this approach would cut out the HTML file of the page and its scripts.

In the WSnap1 program, I used a better technique. I added a custom tag (<#htmltable>) to the HTML file and used the OnTag event of the page producer to add to the HTML the result of the data set table:

if TagString = 'htmltable' then
  ReplaceText := DataSetTableProducer1.Content;

Server-Side Scripts

Having multiple pages in a server-side program—each associated with a different page module—changes the way you write a program. Having the server-side scripts at hand offers an even more powerful approach. For example, the standard scripts of the WSnap1 example account for the application and page titles, and for the index of the pages. This index is generated by an enumerator, the technique used to scan a list from within WebSnap script code. Let's look at it:

<table cellspacing="0" cellpadding="0"><td>
<%  e = new Enumerator(Pages)
    s = ''
    c = 0
    for (; !e.atEnd(); e.moveNext())
    {
      if (e.item().Published)
      {
        if (c > 0) s += '&nbsp;|&nbsp;'
        if (Page.Name != e.item().Name)
          s += '<a href="' + e.item().HREF + '">' + e.item().Title + '</a>'
        else
          s += e.item().Title
        c++
      }
    }
    if (c>1) Response.Write(s)
%>
</td></table>
Note 

Typically, WebSnap scripts are written in JavaScript, an object-based language common for Internet programming because it is the only scripting language generally available in browsers (on the client side). JavaScript (technically indicated as ECMAScript) borrows the core syntax of the C language and has almost nothing to do with Java. WebSnap uses Microsoft's ActiveScripting engine, which supports both JScript (a variation of JavaScript) and VBScript.

Inside the single cell of this table (which, oddly enough, has no rows), the script outputs a string with the Response.Write command. This string is built with a for loop over an enumerator of the application's pages, stored in the Pages global entity. The title of each page is added to the string only if the page is published. Each title uses a hyperlink with the exclusion of the current page. Having this code in a script, instead of hard-coded into a Delphi component, allows you to pass it to a good web designer, who can turn it into something a more visually appealing.

Tip 

To publish or unpublish a page, don't look for a property in the web page module. This status is controlled by a flag of the AddWebModuleFactory method called in the web page module initialization code. You can comment or uncomment this flag to obtain the desired effect.

As a sample of what you can do with scripting, I added to the WSnap2 example (an extension of the WSnap1 example) a demoscript page. The page's script can generate a full table of multiplied values with the following scripting code (see Figure 20.8 for its output):

<table border=1 cellspacing=0>
<tr>
  <th>&nbsp;</th>
  <% for (j=1;j<=5;j++) { %>
  <th>Column <%=j %></th>
  <% } %>
</tr>
<% for (i=1;i<=5;i++) { %>
<tr>
  <td>Line <%=i %></td>
  <% for (j=1;j<=5;j++) { %>
  <td>Value= <%=i*j %></td>
  <% } %>
</tr>
<% } %>
</table>
Click To expand
Figure 20.8: The WSnap2 example features a plain script and a custom menu stored in an include file.

In this script, the <%= symbol replaces the longer Response.Write command. Another important feature of server-side scripting is the inclusion of pages within other pages. For example, if you plan to modify the menu, you can include the related HTML and script in a single file, instead of changing it and maintaining it in multiple pages. File inclusion is generally done with a statement like this:

<!-- #include file="menu.html" -->

In Listing 20.1, you can find the complete source code of the include file for the menu, which is referenced by all of the project's other HTML files. Figure 20.9 shows an example of this menu, which is displayed across the top of the page using the table-generation script mentioned earlier.

Listing 20.1: The menu.html File Included in Each Page of the WSnap2 Example
Start example
<html>
<head>
<title><%= Page.Title %></title>
</head>
<body>
<h2><%= Application.Title %></h2>
<table cellspacing="0" cellpadding="2" border="1" bgcolor="#c0c0c0">
<tr>
<%  e = new Enumerator(Pages)
    for (; !e.atEnd(); e.moveNext())
    {
      if (e.item().Published)
      {
        if (Page.Name != e.item().Name)
          Response.Write ('<td><a href="' + e.item().HREF + '">' +
            e.item().Title + '</a></td>')
        else
          Response.Write ('<td>' + e.item().Title + '</td>')
      }
    }
%>
</tr>
</table>
<hr>
<h1><%= Page.Title %></h1>
<p>
End example
Click To expand
Figure 20.9:  The Web Surface Designer for the inout page of the WSnap2 example, at design time

This script for the menu uses the Pages list and the Page and Application global scripting objects. WebSnap makes available a few other global objects, including EndUser and Session objects (in case you add the corresponding adapters to the application), the Modules object, and the Producer object, which allows access to the Producer component of the web page module. The script also has available the Response and Request objects of the web module.

Adapters

In addition to these global objects, within a script you can access all the adapters available in the corresponding web page module. (Adapters in other modules, including shared web data modules, must be referenced by prefixing their name with the Modules object and the corresponding module.) Adapters allow you to pass information from your compiled Delphi code to the interpreted script, providing a scriptable interface to your Delphi application. Adapters contain fields that represent data and host actions that represent commands. The server-side scripts can access these values and issue these commands, passing specific parameters to them.

Adapter Fields

For simple customizations, you can add new fields to specific adapters. For instance, in the WSnap2 example, I added a custom field to the application adapter. After selecting this component, you can either open its Fields editor (accessible via its shortcut menu) or work in the Object TreeView. After adding a new field (called AppHitCount in the example), you can assign a value to it in its OnGetValue event. Because you want to count the hits (or requests) on any page of the web application, you can also handle the OnBeforePageDispatch event of the global PageDispatcher component to increase the value of a local field, HitCount. Here is the code for the two methods:

procedure Thome.PageDispatcherBeforeDispatchPage(Sender: TObject;
  const PageName: String; var Handled: Boolean);
begin
  Inc (HitCount);
end;
   
procedure Thome.AppHitCountGetValue(Sender: TObject; var Value: Variant);
begin
  Value := HitCount;
end;

Of course, you could use the page name to also count hits on each page (and you could add support for persistency, because the count is reset every time you run a new instance of the application). Now that you've added a custom field to an existing adapter (corresponding to the Application script object), you can access it from within any script, like this:

<p>Application hits since last activation:
<%= Application.AppHitCount.Value %></p>

Adapter Components

In the same way, you can add custom adapters to specific pages. If you need to pass along a few fields, use the generic Adapter component. Other custom adapters (besides the global ApplicationAdapter you've already used) include these:

  • The PagedAdapter component has built-in support for showing its content over multiple pages.

  • The DataSetAdapter component is used to access a Delphi dataset from a script and is covered in the section "WebSnap and Databases."

  • The StringValuesList holds a list of name/value pairs, such as a string list, and can be used directly or to provide a list of values to an adapter field. The inherited DataSetValuesList adapter has the same role but grabs the list of name/value pairs from a dataset, providing support for lookups and other selections.

  • User-related adapters, such as the EndUser, EndUserSession, and LoginForm adapters, are used to access user and session information and to build a login form for the application that is automatically tied to the users list. I'll discuss these adapters in the section "Sessions, Users, and Permissions" later in this chapter.

Using the AdapterPageProducer

Most of these components are used in conjunction with an AdapterPageProducer component. The AdapterPageProducer can generate portions of script after you visually design the desired result. As an example, I've added to the WSnap2 application the inout page, which has an adapter with two fields, one standard and one Boolean:

object Adapter1: TAdapter
  OnBeforeExecuteAction = Adapter1BeforeExecuteAction
  object TAdapterActions
    object AddPlus: TAdapterAction
      OnExecute = AddPlusExecute
    end
    object Post: TAdapterAction
      OnExecute = PostExecute
    end
  end
  object TAdapterFields
    object Text: TAdapterField
      OnGetValue = TextGetValue
    end
    object Auto: TAdapterBooleanField
      OnGetValue = AutoGetValue
    end
  end
end

The adapter also has a couple of actions that post the current user input and add a plus sign (+) to the text. The same plus sign is added when the Auto field is enabled. Developing the user interface for this form and the related scripting would take some time using plain HTML. But the AdapterPageProducer component (used in this page) has an integrated HTML designer, which Borland calls Web Surface Designer. Using this tool, you can visually add a form to the HTML page and add an AdapterFieldGroup to it. Connect this field group to the adapter to automatically display editors for the two fields. Then you can add an AdapterCommandGroup and connect it to the AdapterFieldGroup, to provide buttons for all of the adapter's actions. You can see an example of this designer in Figure 20.9.

To be more precise, the fields and buttons are automatically displayed if the AddDefaultFields and AddDefaultCommands properties of the field group and command group are set. The effect of these visual operations to build this form are summarized in the following DFM snippet:

object AdapterPageProducer: TAdapterPageProducer
  object AdapterForm1: TAdapterForm
    object AdapterFieldGroup1: TAdapterFieldGroup
      Adapter = Adapter1
      object FldText: TAdapterDisplayField
        FieldName = 'Text'
      end
      object FldAuto: TAdapterDisplayField
        FieldName = 'Auto'
      end
    end
    object AdapterCommandGroup1: TAdapterCommandGroup
      DisplayComponent = AdapterFieldGroup1
      object CmdPost: TAdapterActionButton
        ActionName = 'Post'
      end
      object CmdAddPlus: TAdapterActionButton
        ActionName = 'AddPlus'
      end
    end
  end
end

Now that you have an HTML page with some scripts to move data back and forth and issue commands, let's look at the source code required to make this example work. First, you must add to the class two local fields to store the adapter fields and manipulate them, and you need to implement the OnGetValue event for both, returning the field values. When each button is clicked, you must retrieve the text passed by the user, which is not automatically copied into the corresponding adapter field. You can obtain this effect by looking at the ActionValue property of these fields, which is set only if something was entered (for this reason, when nothing is entered you set the Boolean field to False). To avoid repeating this code for both actions, place it in the OnBeforeExecuteAction event of the web page module:

procedure Tinout.Adapter1BeforeExecuteAction(Sender, Action: TObject;
  Params: TStrings; var Handled: Boolean);
begin
  if Assigned (Text.ActionValue) then
    fText := Text.ActionValue.Values [0];
  fAuto := Assigned (Auto.ActionValue);
end;

Notice that each action can have multiple values (in case components allow multiple selections); but this is not the case, so you can grab the first element. Finally, here is the code for the OnExecute events of the two actions:

procedure Tinout.AddPlusExecute(Sender: TObject; Params: TStrings);
begin
  fText := fText + '+';
end;
   
procedure Tinout.PostExecute(Sender: TObject; Params: TStrings);
begin
  if fAuto then
    AddPlusExecute (Self, nil);
end;

As an alternative, adapter fields have a public EchoActionFieldValue property you can set to get the value entered by the user and place it in the resulting form. This technique is typically used in case of errors, to let the user change the input starting with the values already entered.

Note 

The AdapterPageProducer component has specific support for Cascading Style Sheets (CSS). You can define the CSS for a page using either the StylesFile property or the Styles string list. Any element of the editor of the producer's items can define a specific style or choose a style from the attached CSS. You accomplish this last operation (which is the suggested approach) using the StyleRule property.

Scripts Rather Than Code?

Even this example of the combined use of an adapter and an adapter page producer, with its visual designer, shows the power of this architecture. However, this approach also has a drawback: By letting the components generate the script (in the HTML, you have only the <#SERVERSCRIPT> tag), you save a lot of development time; but you end up mixing the script with the code, so changes to the user interface will require updating the program. The division of responsibilities between the Delphi application developer and the HTML/script designer is lost. And, ironically, you end up having to run a script to accomplish something the Delphi program could have done right away, possibly much faster.

In my opinion, WebSnap is a powerful architecture and a huge step forward from WebBroker, but it must be used with care to avoid misusing some of these technologies just because they are simple and powerful. For example, it might be worth using the AdapterPageProducer designer to generate the first version of a page, and then grabbing the generated script and copying it to a plain Page-Producer's HTML, so that a web designer can modify the script with a specific tool.

For nontrivial applications, I prefer the possibilities offered by XML and XSL, which are available within this architecture even if they don't have a central role. You'll find more on this topic in Chapter 22.

Locating Files

When you have written an application like the one just described, you must deploy it as a CGI or dynamic library. Instead of placing the templates and include files in the same folder as the executable file, you can devote a subfolder or custom folder to host all the files. The LocateFileService component handles this task.

The component is not intuitive to use. Instead of having you specify a target folder as a property, the system fires one of this component's events any time it has to locate a file. (This approach is much more powerful.) There are three events: OnFindIncludeFile, OnFindStream, and OnFindTemplateFile. The first and last events return the name of the file to use in a var parameter. The OnFindStream event allows you to provide a stream directly, using one you already have in memory or that you've created on the fly, extracted from a database, obtained via an HTTP connection, or gotten any other way you can think of. In the simplest case of the OnFindIncludeFile event, you can write code like the following:

procedure TPageProducerPage2.LocateFileService1FindIncludeFile(
  ASender: TObject; AComponent: TComponent; const AFileName: String;
  var AFoundFile: String; var AHandled: Boolean);
begin
  AFoundFile := DefaultFolder + AFileName;
  AHandled := True;
end;

 
Previous Section Next Section


 


 

Delphi Sources


Copyright © 2004-2024 "Delphi Sources" by BrokenByte Software. Delphi Programming Guide
ร๐๓๏๏เ ยส๎ํ๒เ๊๒ๅ   Facebook   ั๑๛๋๊เ ํเ Twitter