The WebSearch program on the companion CD, available as aCGI application or a Web App Debugger executable, has an action that simply returns theHTML retrieved by the search engine and a
Trang 1database table, we’ll need to update the element list automatically As a final solution, we candesign the ISAPI DLL to produce a form on-the-fly, and we can fill the selection controlswith the available elements.
We’ll generate the HTML for this page in the /formaction, which we’ve connected to aPageProducer component The PageProducer contains the following HTML text, whichembeds two special tags:
<h4>Customer QueryProducer Search Form</h4>
<form action=”CustQueP.dll/search” method=”POST”>
procedure TWebModule1.PageProducer1HTMLTag(Sender: TObject; Tag: TTag;
const TagString: String; TagParams: TStrings; var ReplaceText: String);
Trang 2This method used a second Query component, which I manually placed on the form andconnected to the DBDEMOS database, and it produces the output shown in Figure 22.6.
Finally, this Web server extension, like many others we’ve built, allows the user to view thedetails of a specific record As in the last example, we can accomplish this by customizing theoutput of the first column (column zero), which is generated by the QueryTableProducercomponent:
procedure TWebModule1.QueryTableProducer1FormatCell(
Sender: TObject; CellRow, CellColumn: Integer;
var BgColor: THTMLBgColor; var Align: THTMLAlign;
var VAlign: THTMLVAlign; var CustomAttrs, CellData: String);
begin
if (CellColumn = 0) and (CellRow <> 0) then
CellData := ‘<a href=”’ + Request.ScriptName + ‘/record?’ + CellData +
‘“>’ + CellData + ‘</a>’#13;
if CellData = ‘’ then
CellData := ‘ ’;
end;
TIP When you have an empty cell in an HTML table, most browsers render it without the border.
For this reason, I’ve added a “nonbreaking space” symbol ( ) into each empty cell This
is something you’ll have to do in each HTML table generated with Delphi’s table producers.
Trang 3The action for this link is /record, and we’ll pass a specific element after the ?parameter(without the parameter name, which is slightly nonstandard) The code we use to producethe HTML tables for the records doesn’t use the producer components as we’ve been doing;instead, it is very similar to the code of an early ISAPI example:
procedure TWebModule1.RecordAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
Query2.SQL.Add (‘select * from customer ‘ +
‘where Company=”’ + Request.QueryFields[0] + ‘“‘);
Response.Content := Response.Content + ‘</table><hr>’#13 +
// pointer to the query form
‘<a href=”’ + Request.ScriptName + ‘/form”>’ +
‘ Next Query </a>’#13 + ‘</body></html>’#13;
end;
end;
Debugging with the Web App Debugger
Debugging Web applications written in Delphi is often quite difficult In fact, you cannotsimply run the program and set breakpoints in it, but should convince the Web server to runyour CGI program or library within the Delphi debugger This can be accomplished byindicating a Host application in Delphi’s Run Parameters dialog box, but it implies lettingDelphi run the Web server (which is often a Windows service, not a stand-alone program)
To solve all of these issues, Borland has added to Delphi 6 a specific Web App Debuggerprogram This tool, activated by the corresponding item of the Tools menu, is a Web server,which waits for requests on a port you can set up (1024 by default) When a request arrives,the program can forward it to a stand-alone executable, using COM-based techniques This
Trang 4means you can run the Web server application from within the Delphi IDE, set all the points you need, and then (when the program is activated through the Web App Debugger)debug the program as you’ll do for a plain executable file.
break-The Web App Debugger does also a good job in logging all the received requests and theactual responses returned to the browser, as you can see in Figure 22.7 The program also has
a Statistics page, which interestingly tracks the time required for each response, allowing you
to test the efficiency of an application in different conditions
By using the corresponding option of the New Web Server Application dialog, you caneasily create a new application compatible with the debugger This defines a standard project,which creates both a main form and a data module The (useless) form includes code for reg-istering the application as an OLE automation server, as:
The log of the Web
App Debugger with its
LogDetail window
Trang 5only those running In fact, the use of COM Automation accounts for the automatic tion of a server Not that this is a good idea, though, as running and terminating the programeach time will make the process much slower Again, the idea is to run the program withinthe Delphi IDE, to be able to debug it easily Notice, though that the list can be expandedwith the detailed view, which includes a list of the actual executable files and many otherdetails.
activa-The data module for this type of project has some initialization code as well:
if WebRequestHandler <> nil then
WebRequestHandler.WebModuleClass := // Web module class
F I G U R E 2 2 8 :
A list of applications
registered with the Web
App Debugger is displayed
when you hook to its
home page.
Trang 6WARNING By doing this for the CustQueP example (it is the CustQueDebug project), I realized that some
of the Web request settings are different So instead of using the ScriptName property of the request (set to empty for a Web debug application), you have to use the InternalScript- Name property.
There are other two interesting elements in the use of the Web App Debugger The first isthat you can test your programs without having a Web server installed and without having totweak its settings In other words, you don’t have to deploy your programs to test them—yousimply try them out right away Another advantage is that, contrary to doing early development
of the applications as CGI, you can start experimenting with a multithreaded architecture rightaway, without having to deal with the loading and unloading of libraries, which often impliesshutting down the Web server and possibly even the computer
NOTE If your aim is to build an ISAPI application, you can also use a specific ISAPI DLL debugging
tool One such tool, called IntraBob, has been built by Bob Swart and is available on his Web site (www.drbob42.com) as freeware.
Working with Apache
If you plan on using Apache instead of IIS or another Web server, you can certainly takeadvantage of the common CGI technology to deploy your applications on almost any Webserver However, using CGI means some reduced speed and some trouble handling stateinformation (as you cannot keep any data in memory) This is a good reason for writing anISAPI application or a dynamic Apache module Using Delphi’s WebBroker technology, youcan also easily compile the same code for both technologies, so that moving your program to
a different Web platform becomes much simpler Finally, you can also recompile a CGI gram or a dynamic Apache module with Kylix and deploy it on a Linux server
pro-As I’ve mentioned, Apache can run traditional CGI applications but has also a specifictechnology for keeping the server extension program loaded in memory at all times for fasterresponse To build such a program in Delphi 6, you can simply use the Apache Shared Mod-ule option of the New Web Server Application dialog box You end up with a library havingthis type of source code for its project:
Trang 7configu-ModuleName := ‘Apache1_module’;
ContentType:= ‘Apache1-handler’;
If you don’t set them, Delphi will assign them some default values, which are built adding
the _module and -handler strings to the project name, ending up with the two names I’ve used
above
An Apache module is generally not deployed within a script folder, but within the modulessubfolder of the server itself (by default, c:\Program Files\Apache\modules) Then you have
to edit the http.conffile, adding a line to load the module, as:
LoadModule apache1_module modules/apache1.dll
Finally, you have to indicate when the module is invoked The handler defined by the ule can be associated with a given file extension (so that your module will process all of thefiles having a given extension) or with a physical or virtual folder In the latter case, the folderdoesn’t exist, but Apache pretends it is there This is how you can set up a virtual folder for thesimple Apache1 module:
mod-<Location /Apache1>SetHandler Apache1-handler</Location>
As Apache is inherently case sensitive (because of its Linux heritage), you might also want
to add a second, lowercase virtual folder:
<Location /apache1>SetHandler Apache1-handler</Location>
Now you can invoke the sample application with the URL http://localhost/Apache1 Agreat advantage of using virtual folder in Apache is that a user doesn’t really distinguish betweenthe physical and dynamic portions of your site, as we’ll better see in the next example
Because the development of Apache modules with WebBroker is almost identical to thedevelopment of other types of programs, instead of building an actual application (besidesthe over-simplistic Apache1 example) I’ve created a new version of the BrokDemo example,already available as a CGI or ISAPI program To do this, I’ve taken the project file of an
Trang 8Apache module from that example, added the local Web modules to it, and modified the project source code to reflect the proper module name and handler I’ve actually definedthem differently than the default, as the following code excerpt demonstrates:
http://localhost/scripts/brokcgi.exe/table
http://localhost/brokdemo/table
Not only is the latter URL simpler, but it hides the fact that we are running an applicationwith a /tableparameter In fact, it seems we are accessing a specific folder of the server Actu-ally, the Apache configuration file can be modified to also invoke CGI applications throughvirtual folders, which explains why CGI applications have a path-like command prefixing therequest Another related explanation is that Linux CGI applications, like any other executablefile, have no extension whatsoever, so their names still seem to be part of a path
Practical Examples
After this general introduction to the core idea of the development of server-side applicationswith WebBroker, let me end this part of the chapter with two simple practical examples Thefirst is a classic Web counter The second is an extension of the WebFind program presented
in the preceding chapter to produce a dynamic page instead of filling a list box
A Web Hit Counter
The server-side applications we’ve built up to now were based only on text Of course, youcan easily add references to existing graphics files What’s more interesting, however, is tobuild server-side programs capable of generating graphics that change over time
A typical example is a page hit counter To write a Web counter, we save the current number
of hits to a file and then read and increase the value every time the counter program is called.How do we return this information? If all we need is some HTML text with the number ofhits, the code is straightforward:
procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
Trang 9// read if the file exists
if FileExists (LogFileName) then begin
Reset (LogFile);
Readln (LogFile, nHit);
Inc (nHit);
end else
WARNING This simple file handling does not scale When multiple visitors hit the page at the same time,
this code may return false results or fail with a file I/O error because a request in another thread has the file open for reading while this thread tries to open the file for writing To sup- port a similar scenario, you’ll need to use a mutex (or a critical section in a multithreaded pro- gram) to let each subsequent thread wait until the thread currently using the file has completed its task.
What’s a little more interesting is to create a graphical counter that can be easily embeddedinto any HTML page There are basically two approaches for building a graphical counter: youcan prepare a bitmap for each digit up front and then combine them in the program, or you cansimply let the program draw over a memory bitmap to produce the graphic you want toreturn In the WebCount program, I’ve chosen this second approach
Basically, we can create an Image component that holds a memory bitmap, which we canpaint on with the usual methods of the TCanvasclass Then we can attach this bitmap to aTJpegImageobject Accessing the bitmap through the JpegImage component converts the
Trang 10image to the JPEG format At this point, we can save the JPEG data to a stream and return
it As you can see, there are many steps, but the code is not really complex:
// create a bitmap in memory
FormatFloat (‘###,###,###’, Int (nHit)));
// convert to JPEG and output
You can see the effect of this program in Figure 22.9 To obtain it, I’ve added the followingcode to an HTML page:
<img src=”http://localhost/scripts/webcount.exe” border=0 alt=”hit counter”>
Trang 11Searching with a Web Search Engine
In Chapter 21, I discussed the use of the Indy HTTP client component to retrieve the result
of a search on the Google Web site Now I’m going to extend the example a little, turning itinto a server-side application The WebSearch program on the companion CD, available as aCGI application or a Web App Debugger executable, has an action that simply returns theHTML retrieved by the search engine and a second action that fills a client data set compo-nent, then hooked to a table page producer This is the code of this second action:
const
strSearch = ‘http://www.google.com/search?as_q=borland+delphi&num=100’;
procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
Trang 12The GrabHtmlmethod is identical to the WebFind example, while the HtmlStringToCdsmethod is similar to corresponding method (which adds the items to a list box) and adds theaddresses and their textual descriptions by calling:
cds.InsertRecord ([0, strAddr, strText]);
The ClientDataSet component, in fact, is set up with three fields: the two strings plus aline counter This extra empty field is used to have the extra column in the table producer.The code fills the column in the cell-formatting event, which also adds the hyperlink:
procedure TWebModule1.DataSetTableProducer1FormatCell(Sender: TObject; CellRow, CellColumn: Integer; var BgColor: THTMLBgColor; var Align: THTMLAlign;
var VAlign: THTMLVAlign; var CustomAttrs, CellData: String);
begin
if CellRow <> 0 then
case CellColumn of
0: CellData := IntToStr (CellRow);
1: CellData := ‘<a href=”’ + CellData + ‘“>’ + SplitLong(CellData) + ‘</a>’;
2: CellData := SplitLong (CellData);
end;
end;
The call to SplitLongis used to add some extra spaces within the output text, to avoid ing grid columns that are too large, as the browser won’t split the text on multiple linesunless it contains spaces or other special characters The result of this program is a ratherslow application (because of the multiple HTTP requests it must forward) producing outputlike Figure 22.10
hav-F I G U R E 2 2 1 0 :
The WebSearch program
shows the result of the
multiple searches done
on Google.
Trang 13Active Server Pages
Another approach to the development of server-side applications is the use of scripting Beforelooking at the scripting technology embedded in the WebSnap framework, let me shortly dis-cuss Microsoft’s Active Server Pages (ASP) technology and how you can use Delphi to support
it The idea behind ASP is to add scripts to the HTML code, so that part of the text on a Webpage is directly available while other information can be added at run time on the server Theclient receives a plain HTML file The difference between this approach and ISAPI is thatyou don’t need to recompile a program on the server to see a change; you simply update thescript ASP offers a complex model, where you can attach persistent data to a session (forexample, a user moving from page to page of a section of your Web site) and to the entireapplication (the section of the Web site, regardless of the user)
ASP is quite a complex technology, and here I can only discuss it in relation to Delphi gramming One of the features of ASP is that it allows you to create COM objects within ascript, and you can write those COM objects in Delphi The Delphi IDE even provides spe-cific support classes and a wizard to help you build ASP objects Compared to ISAPI or CGI,one of the advantages is that your ASP object built in Delphi can get access to session andapplication information, exactly as an ASP script does This means we automatically get extrafeatures such as persistent user data built into our server-side object By building a compiledASP object, we can also increase the speed of complex server-side code (ASP scripts are notalways the best solution in term of performance.) But, again, I don’t want to discuss ASP indetail, only focus on Delphi support
pro-To try this out, simply create a new ActiveX library, and then start the Active Server ObjectWizard (from the ActiveX page of the File ➢ New dialog box) As you can see in Figure 22.11,the wizard has a couple of options You can build an object integrated with the ASP script byselecting the Page-Level Event Methods radio button, or an internal object (which can beinstalled as an MTS object) by using the Object Context option Only in the first case doesthe object automatically handle the OnStartPagemethod, which receives as parameter the
scripting context In both cases, however, the VCL classes you inherit from (TASPObjectandTASPMTSObject, respectively) have properties to access the Request, Response, Session, Server, and ApplicationASP objects
Trang 14Once you’ve created the ASP object with the wizard (I’ve used the Page-Level EventMethods option for the AspTest example), Delphi will bring up the Type Library editor,where you can prepare a list of properties and methods for your ASP object Simply add thefeatures you need, and then write their code For example, you can write the followingsimple test method:
I’ve written the code used to set the property and the method invocation one after the
other, but they can even be in different pages This new dynamic property (Microsoft’s term
F I G U R E 2 2 1 1 :
The new Active Server
Object wizard
Trang 15for these values added to an object) is saved in the session, so it depends on the current user.The Hellomethod can use the username to welcome them:
procedure Tasptest.Hello;
var
strName: string;
begin
strName := Session [‘UserName’];
Response.Write (‘<h3>Hello, ‘ + strName + ‘</h3>’);
Response.Write (‘<p>Page started at ‘ + TimeToStr (StartTime) + ‘</p>’);
end;
You can see the result of this and the previous method combined in Figure 22.12 The lastline of the method uses a variable that’s set when the page is first loaded, in the OnStartPagemethod (despite the name, this is not an event handler, but a method the ASP engine will call
as the page containing the object is activated):
procedure Tasptest.OnStartPage(const AScriptingContext: IUnknown);
The Web page generated by
the AspTest object I’ve built
with Delphi
Trang 16Technically, this method retrieves the scripting context The TASPObjectbase class uses themethod to initialize all the ASP objects (including the two, Responseand Session, I use inthe code), surfacing them as properties.
To generate more complex HTML from the Delphi ASP object, you can use Producercomponents, optionally connecting them to a dataset In the AspTest example, I’ve added aTable component and a DataSetTableProducer, connected them as usual, and written the fol-lowing code to activate it:
to limit their expense to the Professional version of Delphi 6, which includes WebBroker).WebSnap has a few definitive advantages over the plain WebBroker, such as allowing formultiple pages, integrating server-side scripting, and XSL and Delphi 5 Internet Expresstechnology (these last two elements will be covered in the next chapter) Moreover, there aremany ready-to-use components for handling common tasks, such as users’ login, sessionmanagement, and so on Instead of listing all the features of WebSnap right away, though,I’ve decided to cover them in a sequence of simple and focused applications All of theseapplications have been built using the Web App Debugger, for testing purposes, but you’ll beable to easily deploy them using one of the other available technologies
Trang 17The starting point of the development of a WebSnap application is a dialog box that youcan invoke either in the WebSnap page of the New items dialog box (File ➢ New ➢ Other) orusing the new Internet toolbar of the IDE The resulting dialog box, shown in Figure 22.13,allows you to choose the type of application (like in a WebBroker application) and to cus-tomize the initial application components (but you’ll be able to add more later on) The bot-tom portion of the dialog determines the behavior of the first page, usually the default orhome page of the program A similar dialog box is displayed also for subsequent pages.
If you go ahead, choosing the defaults and typing in a name for the home page, the dialogbox will create a project and open up a TWebAppPageModulefor you This module contains thecomponents you’ve chosen, by default:
• A WebAppComponents component is a container of all of the centralized services ofthe WebSnap application, such as the user list, core dispatcher, session services, and so
on Not all of its properties must be available, as an application might not need all ofthe available services
• One of these core services is offered by the PageDispatcher component, which ically) holds a list of the available pages of the application and defines the default one
(automat-• Another core service is given by the AdapterDispatcher component, which handlesHTML form submissions and image requests
• The ApplicationAdapter is the first component we encounter of the adapters family.
These components offer fields and actions to the server-side scripts evaluated by the
F I G U R E 2 2 1 3 :
The options offered by the
New WebSnap Application
dialog box include the type
of server and a button for
the selection of the core
application components.
Trang 18program Specifically, the ApplicationAdapter is a fields adapter that exposes the value
of its own ApplicationTitleproperty By entering a value for this property, it will bemade available to the scripts
• Finally, the module hosts a PageProducer that includes the HTML code of the page—
in this case, the default page of the program Unlike WebBroker applications, theHTML for this component is not stored inside its HTMLDocstring list property or refer-enced by its HTMLFileproperty The HTML file is an external file, stored by default inthe folder hosting the source code of the project and referenced from the applicationusing a statement similar to a resource include statement: {*.html}
Because the HTML file included by the PageProducer is kept as a separate file (theLocateFileService component will eventually help you for its deployment), you can edit it tochange 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 tosome of its dynamic content, thanks to the support for server-side scripting The defaultHTML file, based on a standard template, actually already has some scripting in it
The HTML file is visible within the Delphi editor with reasonably good syntax ing, simply by selecting the corresponding lower tab, such as WSnapDM.html in my simpleexample, shown in Figure 22.14 The editor also has other pages for a WebSnap module,including by default an HTML Result page, where you can see the HTML generated afterevaluating the scripts, and a Preview page hosting what a user will see inside a browser
highlight-F I G U R E 2 2 1 4 :
The Delphi 6 editor for a
WebSnap module includes
a simple HTML editor and a
preview of its output.
Trang 19TIP If you prefer editing the HTML of your Web application with another more sophisticated
edi-tor, you can set up your choice in the Internet page of the Environment Options dialog box Within this page, you can see a list of file extensions Selecting the Edit button for one of these groups of extensions, you can choose an external editor to use for these files At this point, the External Editor button of the Internet toolbar will become active.
The standard HTML template used by WebSnap adds to any page of the program its titleand the application title, using simple script lines such as:
<h1><%= Application.Title %></h1>
<h2><%= Page.Title %></h2>
We’ll get back to the scripting in a while But let me start the development of the WSnap1example by simply creating a program with multiple pages Before I do this, let me finish thisoverview by showing you the extra source code of a sample Web page module:
uses WebReq, WebCntxt, WebFact, Variants;
function home: Thome;
of the page and its behavior
Trang 20Managing Multiple Pages
The first notable difference between WebSnap and WebBroker is that, instead of having a singledata module with multiple actions eventually connected to producer components, WebSnaphas multiple data modules, each corresponding to an action and having a producer componentwith an HTML file attached to it Actually, you can still add multiple actions to a page/mod-ule, but the idea is that you structure applications around pages and not around actions Likeactions, the name of the page is indicated in the request path
As an example, I’ve added to the WebSnap application, built with default settings, twomore pages For the first, in the New WebSnap Page Module dialog (see Figure 22.15), I’ve
chosen a standard page producer and given to it the name date For the second, I’ve gone with a DataSetPageProducer and given it the name country After saving the files, you can
start testing the application Thanks to some of the scripting I’ll discuss later, each page listsall of the available pages (unless you’ve unchecked the Published check box in the New Web-Snap Page Module dialog)
All of the pages will be rather empty, but at least we have the structure in place To plete the home page, I’ve simply edited its linked HTML file directly For the date page, I’ve
com-F I G U R E 2 2 1 5 :
The New WebSnap Page
Module dialog box
Trang 21employed the same approach as a WebBroker application I’ve added to the HTML textsome custom tags, as in:
<p>The time at this site is <#time>.</p>
and I’ve added some code to the OnTagevent handler of the producer component to replacethis tag with the current time
For the third page, the country page, I’ve modified the HTML to include tags for the ous fields of the country table, as in:
Table1.Open;
Table1.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 quite important, in case you want to port existing Web-Broker code to this new architecture You can even port your existing DataSetTableProducercomponents to the new architecture Technically, you can generate a new page, remove its pro-ducer component, replace it with a DataSetTableProducer, and hook this component to thePageProducerproperty of the Web page module In practice, this approach would cut out theHTML file of the page and its scripts
In the WSnap1 program, I’ve used a better technique I’ve added a custom tag
(<#htmltable>) to the HTML file and used the OnTagevent of the page producer to add tothe HTML the result of the data set table:
if TagString = ‘htmltable’ then
ReplaceText := DataSetTableProducer1.Content;
Trang 22Server-Side Scripts
If having multiple pages in a server-side program, each associated with a different page ule, changes the way you write a program, having the server-side scripts at hand offers aneven more powerful approach For example, the standard scripts of the WSnap1 exampleaccount for the application and page titles, and for the index of the pages This is generated
mod-by an enumerator, the technique used to scan a list from within a WebSnap script code Let’shave a 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>1) Response.Write(s)
%>
</td></table>
NOTE Typically, WebSnap scripts are written in JavaScript, an object-based language very 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 Actually, 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 astring with the Reponse.Writecommand This string is built with a forloop over an enumer-ator of the pages of the application, stored in the Pagesglobal entity The title of each page isadded to the string, only if the page is published and using an hyperlink only for pages differ-ent than the current one Having this code in a script, instead of hard-coded into a Delphicomponent, allows you to pass it over to a good Web designer to turn it into something a littlemore visually appealing
Trang 23TIP 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 Simply comment or uncomment this flag to obtain the desired effect.
As a sample of what you can do with scripting, I’ve added to the WSnap2 example (an
exten-sion of the WSnap1 example) a demoscript page The script of this page can generate a full table
of multiplied values with the following scripting code (see Figure 22.16 for its output):
<table border=1 cellspacing=0>
The WSnap2 example has a
custom menu stored in an
included file reference by
each page.
Trang 24In this script, the <%=symbol replaces the longer Response.Writecommand Anotherimportant feature of server-side scripting is the inclusion of pages within other pages Forexample, if you plan on modifying 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 isgenerally done with a statement like:
<! #include file=”menu.html” >
In Listing 22.1, you can find the complete source code of the include file for the menu, erenced by all the other HTML files of the project In Figure 22.16, you can see an example
ref-of this menu, across the top ref-of the page with the table generation script mentioned earlier
➲ Listing 22.1: The menu.html file included in each page of the WSnap2 example
else Response.Write (‘<td>’ + e.item().Title + ‘</td>’) }
Trang 25Besides these global objects, within a script you can access all the adapters available in thecorresponding Web page module (Adapters in other modules, including shared Web datamodules, must be referenced by prefixing their name with the Modulesobject and the corre-sponding module.) The idea is that adapters allow you to pass information from your com-piled Delphi code to the interpreted script, providing a scriptable interface to your Delphiapplication Adapters contain fields that represent data and host actions that represent com-mands The server-side scripts can access these values and issue these commands, passingspecific parameters to them
NOTE Technically, adapters implement an IDispatch interface that can be accessed by the script
through an Active Scripting engine language, such as JavaScript The page producer nent is responsible for invoking the Active Scripting engine and has a property indicating the language of the script Because of this, you’ll have to register two type libraries (and deploy the corresponding DLLs) to make this work on a machine where Delphi is not installed: Web- BrokerScript.tlb and stdvcl40.dll As the first is a type library, it must be installed with Delphi’s TRegSvr utility (available in the bin subfolder) rather than Microsoft’s RegSvr32 pro- gram Of course, the server computer must also have Microsoft Active Scripting Engine installed in order to work.
compo-Adapter Fields
For simple customizations, you can simply add new fields to the specific adapters For instance,
in the WSnap2 example, I’ve added a custom field to the application adapter After selectingthis component, you can either open up its Fields editor (accessible via its local menu) or simplywork within the Object TreeView After adding a new field (called Countin the example), youcan assign a value to it in its OnGetValueevent As I want to count the hits (or requests) on anypage of the Web application, I’ve also handled the OnBeforePageDispatchevent of the global
PageDispatcher component Here is the code of the two methods:
procedure Thome.PageDispatcherBeforeDispatchPage(Sender: TObject;
const PageName: String; var Handled: Boolean);
Trang 26Of course, I could have used the page name to also count hits on each specific page (and Icould have added some support for persistency, as the count is reset every time you run a newinstance of the application) Now that I’ve added a custom field to an existing adapter (corre-sponding to the Applicationscript object), I can access it from within any script, like this:
<p>Application hits since last activation:
<%= Application.Count.Value %></p>
Adapter Components
In the same way, you can also 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 globalApplicationAdapter we’ve already used) include these:
• The PagedAdapter component has built-in support for showing its content over multiplepages
• The DataSetAdapter component is used to access a Delphi dataset from a script and iscovered in the next section
• The StringValuesList holds a list of name/value pairs, like a string list, and can be useddirectly or to provide a list of values to an adapter field The inherited DataSetValues-List 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 forthe application, automatically tied to the users list I’ll cover these adapters in the sec-tion “Sessions, Users, and Permissions” later in this chapter
Using the AdapterPageProducer
Most of these components are used in conjunction with an AdapterPageProducer nent The AdapterPageProducer, in fact, can generate portions of script after you visually
compo-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
Trang 27object TAdapterFields
object Text: TAdapterField
OnGetValue = TextGetValue
end object Auto: TAdapterBooleanField
OnGetValue = AutoGetValue
end end
end
The adapter has also a couple of actions, used to post the current user input and to add aplus sign to the text The same plus sign is added anyway when the Autofield is enabled.Developing the user interface for this form, and the related scripting, would take some timeusing plain HTML But the AdapterPageProducer component (used in this page) has anintegrated HTML designer, which Borland calls Web Surface Designer Using this tool, youcan visually add a form to the HTML page and add an AdapterFieldGroup to it Connectthis field group to the adapter to have editors for the two fields automatically displayed.Then you can add an AdapterCommandGroup and connect it to the AdapterFieldGroup, tohave buttons for all of the actions of the adapter You can see an example of this designer inFigure 22.17
F I G U R E 2 2 1 7 :
The Web Surface Designer
of Delphi 6 for the inout
page of the WSnap2
example, at design time
Trang 28To be more precise, the fields and buttons are automatically displayed if the Fieldsand AddDefaultCommandsproperties of the field group and command group are set.The effect of the visual operations I’ve done to build this form are summarized in the follow-ing DFM snippet:
AddDefault-object AdapterPageProducer: TAdapterPageProducer
object AdapterForm1: TAdapterForm
object AdapterFieldGroup1: TAdapterFieldGroup
of the buttons is pressed, we have to retrieve the text passed by the user, which is not matically copied into the corresponding adapter field You can obtain this effect by looking atthe ActionValueproperty of these fields, which is set only if something was entered (for thisreason, when nothing is entered we set the Boolean field to False) To avoid repeating thiscode for both actions, I’ve placed it in the OnBeforeExecuteActionevent of the Web pagemodule:
auto-procedure Tinout.Adapter1BeforeExecuteAction(Sender, Action: TObject;
Params: TStrings; var Handled: Boolean);
Trang 29Notice that each action can have multiple values (in case of components allowing multipleselections); but this is not the case, so we can simply grab the first element Finally, I’ve writ-ten the code for the OnExecuteevents of the two actions:
procedure Tinout.AddPlusExecute(Sender: TObject; Params: TStrings);
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 Styles string list Any element of the editor of the items of the producer, at this point, can define a specific style
or choose one of the styles of the attached CSS This last operation (which is the suggested approach) is accomplished using the StyleRule property.
Scripts Rather Than Code?
Even this simple 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 big drawback By letting the components generate the script (in the HTML, you have onlythe <#SERVERSCRIPT>tag), you save a lot of development time, but at the same time you end
up mixing the script with the code, so that changes to the user interface will require updatingthe program The division of responsibilities between the Delphi application developer and theHTML/script designer is lost And, ironically, we end up having to run a script to accom-plish something the Delphi program could have done right away, possibly even much faster!
So my opinion is that this is a very powerful architecture and a huge step forward fromWebBroker, but it has to be used with some care, to avoid misusing some of these technolo-gies just because they are simple and powerful (and they are indeed) For example, it might
be worth using the designer of the AdapterPageProducer to generate the first version of apage, then grabbing the generated script and copying to the HTML of a plain PageProducer,
so that a Web designer can modify the script with a specific tool
Trang 30For nontrivial applications, I tend to prefer the possibilities offered by XML and XSL,which are available within this architecture even if they don’t have a central role More onthis specific topic in the next chapter.
WebSnap and Databases
One of the areas where Delphi has always shined is database programming For this reason,
it is not surprising to see a lot of support for handling datasets within the WebSnap work Specifically, you can use the DataSetAdapter component to connect to a dataset anddisplay its values in a form or a table using the visual editor of the AdapterPageProducercomponent
frame-A WebSnap Data Module
As an example, I’ve built a new WebSnap application (called WSnapTable) with an PageProducer as its main page to display a table in a grid and another AdapterPageProducer
Adapter-in a secondary page to show a form with a sAdapter-ingle record I’ve also added to the application aWebSnap Data Module, as a container of the dataset components The data module has aClientDataSet wired to a dbExpress dataset through a provider and based on an InterBaseconnection, as shown here:
object ClientDataSet1: TClientDataSet
‘select CUST_NO, CUSTOMER, ADDRESS_LINE1, CITY, STATE_PROVINCE, ‘ +
‘ COUNTRY from CUSTOMER’
Trang 31Delete, Edit, and Apply) You can add them explicitly to the Actionsand Fieldscollections
to exclude some of them and customize their behavior, but this is not always required.Like the PagedAdapter, the DataSetAdapter has a PageSizeproperty where you can indi-cate the number of elements to display in each page The component also has commandsthat you can use to navigate among pages This approach is particularly suitable when youwant to display a large dataset in a grid These are the adapter settings for the main page ofthe WSnapTable example:
object DataSetAdapter1: TDataSetAdapter
of numbers for the pages, so that a user can jump to each of them directly The AdapterGridcomponent has the default columns plus an extra one hosting a couple of commands, Editand Delete The bottom command group has a button used to create a new record You cansee an example of the output of the table in Figure 22.18 and the complete settings of theAdapterPageProducer in Listing 22.2
➲
F I G U R E 2 2 1 8 :
The page shown by the
WSnapTable example at
start up includes the initial
portion of a paged table.
Trang 32object AdapterCommandGroup1: TAdapterCommandGroup
Trang 33end object AdapterCommandGroup2: TAdapterCommandGroup
end
In this rather long listing, there are a few things to notice First, the grid has the Modeproperty set to Browse, other possibilities being Edit, Insert, and Query This datasetdisplay mode for adapters determines the type of user interface (text or edit boxes and otherinput controls) and the visibility of other buttons (for example, Apply and Cancel buttons areonly present in the edit view, the opposite for the Edit command)
Adapter-NOTE The adapter mode can also be modified using server-side script and accessing Adapter.Mode.
Second, I’ve modified the display of the commands inside the grid, using the ctAnchorvalue for the DisplayTypeproperty instead of the default button style Similar properties areavailable in most components of this architecture to tweak the HTML code they produce
Editing the Data in a Form
Finally, some of the commands are connected to a different page, the page that is going to bedisplayed after the commands are invoked For example, the editcommand has its PageNameproperty set to formview This second page of the application has an AdapterPageProducerwith components hooked to the same DataSetAdapter of the other table, so that all of therequest will be automatically synchronized Selecting the edit command, in fact, the programwill open the secondary page displaying the data of the record corresponding to the command.Listing 22.3 shows the details of the page producer of the second page of the program Again,building the HTML form visually using the Delphi specific designer (see Figure 22.19) was avery fast operation
Trang 34➲ Listing 22.3: AdapterPageProducer settings for the formview page
object AdapterPageProducer: TAdapterPageProducer
object AdapterForm1: TAdapterForm
object AdapterErrorList1: TAdapterErrorList
Adapter = table.DataSetAdapter1
end object AdapterCommandGroup1: TAdapterCommandGroup
ActionName = ‘Cancel’
PageName = ‘table’
end object CmdDeleteRow: TAdapterActionButton
ActionName = ‘DeleteRow’
Caption = ‘Delete’
F I G U R E 2 2 1 9 :
The formview page shown
by the WSnapTable example
at design time, in the Web
Surface Designer (or
AdapterPageProducer
editor)
Trang 35PageName = ‘table’
end end object AdapterFieldGroup1: TAdapterFieldGroup
DisplayWidth = 27
FieldName = ‘CUSTOMER’
end object FldADDRESS_LINE1
object FldCITY
object FldSTATE_PROVINCE
object FldCOUNTRY
end end
end
In the listing, you can see that all the operations send the user back the main page and thatthe AdapterModeis set to Edit, unless there are update errors or conflicts In this case, thesame page is displayed again, with a description of the errors obtained by adding an Adapter-ErrorList component at the top of the form
The second page is not published, because selecting it without referring to a specificrecord would make very little sense To unpublish the page, I’ve simply commented the cor-responding flag in the initialization code Finally, to make the changes to the database persis-tent, you can call the ApplyUdpatesmethod in the OnAfterPostand OnAfterDeleteevents ofthe ClientDataSet component hosted by the data module Another problem (which I haven’tfixed) relates to the fact that the SQL server assigns the ID of each customer, so that whenyou enter a new record, the data in the ClientDataSet and in the actual database are notaligned any more This can cause Record Not Found errors, a problem I’ve not fixed in theexample
Master/Detail in WebSnap
The DataSetAdapter component has specific support for master/detail relationships betweendatasets After you’ve created the relationship among the datasets, as usual, define an adapterfor each dataset and then connect the MasterAdapterproperty of the adapter of the detaildataset Setting up the master/detail relationship between the adapters makes them work in a
Trang 36more seamless way For example, when you change the work mode of the master, or enternew records, the detail automatically enters into Edit mode or is refreshed.
In the WSnapMD example, I’ve defined such a relationship using two SQLClientDataSetcomponents connected with an InterBase database via dbExpress All these components andthe related adapters are in a Web data module, which has the structure displayed in the designview in Figure 22.20 I haven’t provided a complete listing of the details of these components,
as it shouldn’t be too difficult for you to rebuild it after looking at the example itself
The only page of this WebSnap application has an AdapterPageProducer component hooked
to both dataset adapters The form of this page, in fact, has both a field group hooked to themaster and a grid connected with the detail Unlike other examples, I’ve tried to improve theuser interface by adding custom attributes for the various elements, as you can see in the fol-lowing detailed excerpt:
object AdapterPageProducer: TAdapterPageProducer
object AdapterForm1: TAdapterForm
Custom = ‘Border=”1” CellSpacing=”0” CellPadding=”10” ‘ +
‘BgColor=”Silver” align=”center”’
object AdapterCommandGroup1: TAdapterCommandGroup
DisplayComponent = AdapterFieldGroup1
F I G U R E 2 2 2 0 :
The design view of the
Web data module of the
WSnapMD example.
Both the datasets and
the adapters have a
master/detail relationship.
Trang 37ActionName = ‘PrevRow’
Caption = ‘ Previous ‘
end object CmdNextRow: TAdapterActionButton
ActionName = ‘NextRow’
Caption = ‘ Next ‘
end object CmdLastRow: TAdapterActionButton
ActionName = ‘LastRow’
Caption = ‘ Last ‘
end end object AdapterFieldGroup1: TAdapterFieldGroup
Custom = ‘BgColor=”Silver”’
Adapter = WDataMod.dsaDepartment
AdapterMode = ‘Browse’
end object AdapterGrid1: TAdapterGrid
TableAttributes.BgColor = ‘Silver’
TableAttributes.CellSpacing = 0 TableAttributes.CellPadding = 3
HeadingAttributes.BgColor = ‘Gray’
Adapter = WDataMod.dsaEmployee
AdapterMode = ‘Browse’
object ColEMP_NO: TAdapterDisplayColumn
object ColFIRST_NAME: TAdapterDisplayColumn
object ColLAST_NAME: TAdapterDisplayColumn
object ColDEPT_NO: TAdapterDisplayColumn
object ColJOB_CODE: TAdapterDisplayColumn
object ColJOB_COUNTRY: TAdapterDisplayColumn
object ColSALARY: TAdapterDisplayColumn
end end
end
I’ve used a gray background, displayed some of the grid borders (HTML grids are usedvery often by the Web surface designer), centered most of the elements, and added somespacing Notice that I’ve added some extra spaces to the button captions, to avoid them beingtoo small The effect of these settings (and the master/detail structure) is visible at run time
in Figure 22.21
Trang 38Sessions, Users, and Permissions
Another very interesting area of the WebSnap architecture is its support for sessions andusers Sessions are supported using a classic approach: temporary cookies These cookies aresent to the browser, so that following requests from the same user can be acknowledged bythe system By adding data to a session instead of an application adapter, you can have datathat depends on the specific session or user (although a user can possibly run multiple ses-sions by opening multiple browser windows on the same computer) For supporting sessions,the application keeps data in memory, so this feature is not available in case of CGI pro-grams
Using Sessions
To underline the importance of this type of support, I’ve built a WebSnap application with asingle page showing both the total number of hits and the total number of hits for each ses-sion The program has a SessionService component with default values for its MaxSessionsand DefaultTimeoutproperties For every new request, the program increases both an nHitsprivate field of the page module and the SessionHitsvalue of the current session:
procedure TSessionDemo.WebAppPageModuleBeforeDispatchPage(Sender: TObject;
Trang 39const PageName: String; var Handled: Boolean);
NOTE The WebContext object (of type TWebContext) is a thread variable, created by WebSnap for
each request, which provides thread-safe access to other global variables used by program.The associated HTML displays status information both by using some custom tags evalu-ated by the OnTagevent of the page producer and some script, evaluated by the engine Here
is the core portion of the HTML file:
<h3>Plain Tags</h3>
<p>Session id: <#SessionID>
<br>Session hits: <#SessionHits></p>
<h3>Script</h3>
<p>Session hits (via application): <%=Application.SessionHits.Value%>
<br>Application hits: <%=Application.Hits.Value%></p>
The parameters of the output are provided by the OnTagevent handler and the OnGetValueevents of the fields:
procedure TSessionDemo.PageProducerHTMLTag(Sender: TObject; Tag: TTag;
const TagString: String; TagParams: TStrings; var ReplaceText: String); begin
if TagString = ‘SessionID’ then
ReplaceText := WebContext.Session.SessionID
else if TagString = ‘SessionHits’ then
ReplaceText := WebContext.Session.Values [‘SessionHits’]
Trang 40TIP In this example, I’ve voluntarily used both the traditional WebBroker tag replacement and the
newer WebSnap adapter fields and scripting, so that you can compare the two approaches and keep in mind that they are both available in a WebSnap application.
Requesting Login
Besides generic sessions, WebSnap also has specific support for users and login-based authorizedsessions You can add to an application a list of users (with the WebUserList component), eachwith a name and a password My impression is that this component is rather rudimentary in thedata it can store Instead of filling it with your list of users, however, you can keep the list in adatabase table (or in some other proprietary format) and use the events of the WebUserListcomponent to retrieve your custom users data and check the user passwords
You’ll generally also add to the application the SessionService and Adapter components At this point, you can ask the users to log in, indicating for each pagewhether it can be viewed by everyone or only by logged-in users This is accomplished bysetting the wpLoginRequired flag in the constructor of the TWebPageModuleFactoryandTWebAppPageModuleFactoryclasses in the initialization code of the Web page unit
EndUserSession-F I G U R E 2 2 2 2 :
Two instances of the
browser operate on two
different sessions of the
same WebSnap application.