Bob Swart (aka Dr.Bob)
Delphi WebBroker (Web Modules)

Delphi Web Modules, oftewel de Delphi WebBroker technologie, bestaat uit een aantal wizards en speciale componenten om internet CGI, WinCGI of ISAPI/NSAPI toepassingen mee te ontwikkelen. Web Modules zijn beschikbaar in de Client/Server versie van Delphi 3 en Delphi 4, of als aparte Add-On voor Delphi 4 Professional.
De Web Modules bestaan uit een "Web Server Application" Wizard in de repository (zie figuur 1) en een viertal componenten op de internet tab van het component palette.

Direkt bij het opstarten van de "Web Server Application" Wizard moeten we specificeren wat voor soort Web Server toepassing we denken te gaan ontwikkelen. De keuze bestaat uit CGI, WinCGI en ISAPI/NSAPI (de Microsoft Internet Server resp. Netscape Server APIs). Voor CGI en WinCGI geldt dat het resultaat een executable is, die kwa performance sterk achterblijft bij een ISAPI/NSAPI DLL oplossing. De laatste zal namelijk slechts éénmaal geladen hoeven worden, om vervolgens in het geheugen van de Web Server beschikbaar te blijven. Het nadeel van ISAPI/NSAPI DLLs is dat ze zelfs binnen dezelfde "Memory Space" van de Web Server draaien, zodat een crash of run-time error binnen de ISAPI/NSAPI DLL er helaas ook voor zorgt dat de aanroepende toepassing (de Web Server) ook uit de lucht gaat. Goed testen dus voordat je een ISAPI/NSAPI DLL daadwerkelijk neerzet. En dat is het tweede nadeel: voor het vervangen van de ISAPI/NSAPI DLL zal de Web Server vaak eveneens (met de hand) uit de lucht gebracht moeten worden.

Als vuistregel adviseer ik altijd om te beginnen met een CGI (of WinCGI) toepassing, en die dan later - als alle tests en aanpassingen zijn gedaan - om te zetten naar ISAPI/NSAPI voor optimale performance. Aan het eind van dit artikel zullen we zien dat het omzetten van een Web Modules toepassing van CGI of WinCGI naar ISAPI/NSAPI een fluitje van een cent is, en bestaat uit het aanpassen van twee of drie regels source code! Bovendien kunnen we de "IntraBob CGI Tester v2.5" (te vinden op mijn website) gebruiken om CGI en WinCGI toepassingen te testen zonder ze te hoeven uploaden naar een Web Server. Scheelt weer test en ontwikkeltijd. De keuze voor WinCGI betekent bovendien dat de verzonden data (van het CGI Formulier naar de Web Server) ook nog eens in een .INI bestand bewaard wordt, wat het vinden van fouten extra ondersteunt.

Web Module
Een Web Module kan gezien worden als een soort Data Module; een onzichtbaar Form dat gebruikt kan worden om bijvoorbeeld tabellen en andere onzichtbare (non-visuele) componenten op te zetten. In feite is dat ook precies wat een Web Module is: een Data Module met een "Action Dispatcher" geïntegreerd. De Action Dispatcher zorgt ervoor dat de Web Module toepassing zelf voor ieder verzoek (request) kan bepalen welke "actie" moet worden uitgevoerd. Dit betekent weer een internet toepassing die toevallig twee verschillende dingen doet, niet langer ook uit twee verschillende sub-toepassingen bestaat, doch slechts uit één Web Module toepassing hoeft te bestaan, met twee verschillende "actions" en bijbehorende OnAction event handlers om deze acties ieder voor zich af te handelen. Door met de rechter muisknop op de Web Module te klikken komen we in de Action Editor terecht. Hierin kunnen we de verschillende acties van onze internet toepassing opgeven. Voor iedere actie moeten we vervolgens in de OnAction event de code schrijven die specifiek voor deze actie uitgevoerd moet worden. Daarnaast kan iedere actie echter (gemeenschappelijk) gebruik maken van alle andere (onzichtbare) componenten die we op de Web Module plaatsen, zoals een aantal tables en/of queries. Het onderscheid tussen de verschillende Web Module Actions wordt gemaakt op basis van de zgn. "PathInfo", een stukje informatie beginnend met een "/" dat direct na de "request URL" wordt geplaatst. Dus als onze CGI toepassing bijvoorbeeld "iprog.exe" heet en in de "cgi-bin" directory staat van de "www.domein.nl" webserver, dan is de normale aanroep van de CGI toepassing als volgt: http://www.domein.nl/cgi-bin/iprog.exe. Als we de PathInfo "/table" mee willen geven dat wordt dit: http://www.domein.nl/cgi-bin/iprog.exe/table", en op die manier kan de Action Dispatcher dus bepalen welke Action uitgevoerd moet worden.

Binnen de OnAction event van een Action item moeten we de "Response" parameter van een waarde voorzien. Response heeft onder andere een veld genaamd "Content" waar de inhoud (als String) direkt in geschreven kan worden, bijvoorbeeld als volgt:

  procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject;
    Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
  begin
    Response.Content := '<HTML><BODY>Hello, world</BODY></HTML>'
  end;
Natuurlijk kunnen we behalve het zelf schrijven van HTML ook gebruik maken van een drietal speciale Web Module componenten met speciale functionaliteiten. Het betreft de TPageProducer, de TDataSetTableProducer en de TQueryTableProducer.

TPageProducer
De TPageProducer component kan gebruikt worden voor het genereren van dynamische HTML waarbij een groot deel van de HTML-code in feite al bij voorbaat vast staat, en slechts een paar onderdelen nog dynamisch bepaald en/of ingevuld moeten worden. Hierbij moeten we allereerst de Response.Content koppelen aan de PageProducer1.Content property:

  procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject;
    Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
  begin
    Response.Content := PageProducer1.Content
  end;
Nu moeten we alleen nog de PageProducer1 zelf "programmeren". Er zijn twee properties die we kunnen gebruiken voor het opgeven van de HTML "basis" tekst van de TPageProducer. De eerste heet "HTMLFile" en bevat de naam van het bestand waarin de HTML staat, de tweede heet "HTMLDoc" en is een TStringList waarin de HTML zelf staat. Afhankelijk van de situatie is de ene dan wel de andere property beter bruikbaar, uiteraard. Welke van de twee we ook gebruiken, binnen de HTML code kunnen we naast "normale" HTML ook een speciale "tag" neerschrijven, van de volgende vorm:
  <#TagNaam Parameter1=Waarde1 Parameter2=Waarde2 ...>
De <#TagNaam mag zelf geen spaties bevatten, maar wel verschillende parameters die een initiële waarde kunnen hebben. De TPageProducer component zelf kent nu een OnHTMLTag event, die wordt uitgevoerd zodra bij het verwerken van de inhoud van de HTMLDoc of HTMLFile property een "tag" wordt gevonden. Binnen deze OnHTMLTag event kunnen we de oorspronkelijke "tag" vervangen door een dynamische waarde, bijvoorbeeld de huidige datum en tijd (zodat we "goede morgen" of "goede middag" kunnen neerzetten), of bijvoorbeeld een bepaald record uit een database (de laatste bezoeker van de website, bijvoorbeeld). Binnen de OnHTMLTag event moeten we eerst controleren of de (input) TagString we de "tag" is die we op dat moment willen vervangen. Als dat zo is, dan kunnen we de nieuwe waarde (een string weer) toekennen aan de parameter ReplaceText, bijvoorbeeld als volgt:
  procedure TWebModule1.PageProducer1HTMLTag(Sender: TObject; Tag: TTag;
    const TagString: String; TagParams: TStrings; var ReplaceText: String);
  begin
    if TagString = 'TAG' then
      ReplaceText := 'Welcome to Dr.Bob''s Delphi Clinic'
    else
      ReplaceText := 'Error: Unknown tag offered...'
  end;
Op deze manier kunnen we daadwerkelijk dynamische HTML pagina's genereren. Het wordt zelfs nog leuker als we bedenken dat we ook al Tables en Queries kunnen openen, bevragen en bewerken binnen de PageProducer of de OnAction event code. Hiervoor kunnen we echter beter de TDataSetTableProducer gebruiken, die beter geschikt is om met tables te werken.

TDataSetTableProducer
De TDataSetTableProducer is met name geschikt voor het weergeven van de inhoud van een tabel in een soort "grid" or "tabel" vorm (vandaar het stuk "TableProducer" in de naam). En dat geheel automatisch, zodat we in feite helemaal niks meer van HTML hoeven af te weten om met Web Modules toch mooie HTML output te kunnen (laten) genereren. Voordat we met de TDataSetTableProducer zelf iets kunnen doen, moeten we er eerst eentje op on ze Web Module plaatsen, en in een volgende OnAction event (van een tweede actie) de volgende code schrijven - volledig analoog aan het gebruik van de TPageProducer:

  procedure TWebModule1.WebModule1WebActionItem2Action(Sender: TObject;
    Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
  begin
    Response.Content := DataSetTableProducer1.Content;
  end;
Vervolgens moet de DataSetTableProducer verbonden worden met een DataSet. De TDataSet is "vader" control van de TTable en de TQuery, zodat we een van beide op de Web Module moeten plaatsen en moeten instellen zoals gewenst. Bijvoorbeeld de TTable component die we naar de DBDEMOS alias (DatabaseName) laten wijzen, en daarbinnen de BIOLIFE.DB tabel (TableName). Als DataSet property van de TDataSetTableProducer kunnen we dan deze tabel nemen. Het enige dat we nog moeten doen is de Active property op "True" zetten zodat we de data in de tabel ook meteen (en tijdens design-time) kunnen zien.

Nu begint het leuk te worden: om aan te geven welke kolommen (velden) van de records in de tabel we willen laten zien op de dynamische HTML pagine, moeten we op de "Columns" property klikken van de TDataSetTableProducer. Dit heeft tot gevolg dat een speciale property editor wordt gestart, waarbinnen we precies kunnen aangeven welke velden we willen laten zien, welke achtergrond kleur we hiervoor moeten gebruiken, hoe de velden vertoond moeten worden (links, rechts of gecentreerd), etc.

Merk op dat het onderste deel van deze (schermvullende) property editor al een soort voorproefje geeft op datgene wat we in een echte browser zullen zien. Als we het Web Module project opnieuw compileren en de tweede actie uitvoeren, dan zien we de records uit de BIOLIFE.DB tabel, met de daarbij behorende velden, layout en kleuren zoals we die al hadden opgegeven. En dat alles zonder ook maar één regel HTML-code te hebben geschreven.

Natuurlijk is dit nog mar het begin. En een van de eerste uitbreidingen die ik zelf meteen aan zou willen brengen is het verwerken van de (MEMO) en (GRAPHIC) velden. Om de een of andere reden kunnen Web Modules niet automatisch de data uit "grote" velden halen (memo, blob, binary, etc). Terwijl zeker voor Memo-velden dit geen enkel probleem zou moeten zijn (de Value property van een TMemoField levert mij exact de inhoud op in een zgn. LongString). Voor plaatjes kan ik me nog iets voorstellen, zeker als je bedenkt dat we binnen een web browser alleen maar .GIF of .JPG plaatjes kunnen afbeelden, en geen (standaard) .BMPs. Ik laat het als "oefening voor de lezer" om uit te zoeken hoe de kolommen die momenteel alleen maar (MEMO) en (GRAPHIC) laten zien aangepast kunnen worden zodat de daadwerkelijke inhoud (memo tekst en plaatje) te zien komt. Daadwerkelijke bezoekers aan mijn "Web Modules" sessie tijdens de Conference-To-The-Max-99 zullen inmiddels al genoeg weten.

(Win)CGI vs. ISAPI/NSAPI
Als we de source code van het Web Module project zelf eens nader bekijken, dan krijgen we de volgende listing te zien:

  program Project1;
  {$APPTYPE GUI}
  uses
    HTTPApp,
    CGIApp,
    Unit1 in 'Unit1.pas' {WebModule1: TWebModule};

  {$R *.RES}

  begin
    Application.Initialize;
    Application.CreateForm(TWebModule1, WebModule1);
    Application.Run;
  end.
Merk op dat dit WinCGI project - net als een standaard CGI project - de CGIApp unit gebruikt in de uses clause, terwijl een ISAPI/NSAPI project juist de ISAPIApp unit gebruikt. De {$APPTYPE GUI} compiler directive vertelt ons tevens dat het hier om een "GUI" (dus niet-Console) toepassing gaat, wat resulteert in een WinCGI toepassing. Als we dit wijzigen in {$APPTYPE CONSOLE} resulteert bovenstaande code in een standaard CGI toepassing. Om er een ISAPI/NSAPI DLL van te maken hoeven we alleen maar de CGIApp unit te vervangen door de ISAPIApp unit, en de eerste regel van "program Project1;" in "library Project1;" (zodat er daedwerkelijk een DLL en geen EXE wordt gegenereerd). Hierna is een "Rebuild" genoeg om de nieuwe toepassing in DLL formaat beschikbaar te hebben.

Delphi 4 DB Web Application Wizard
Laten we nu eens kijken naar de nieuwe zaken die in Delphi 4 zijn toegevoegd. Misschien is daarin het "(MEMO)" probleem wel al opgelost. Delphi 4 heeft nu een tweede Wizard om een Web Module project te starten, de zgn. DB Web Application Wizard. Ook deze is te vinden in de Repository (File | New), en voorlopig alleen nog in de Client/Server versie van Delphi 4.

Net als bij de "normale" Web Module Wizard, vraagt ook deze om een keuze te maken tussen ISAPI/NSAPI, CGI of WinCGI. Vroeger raadde ik altijd aan om met CGI of WinCGI te starten, omdat dat iets makkelijker te debuggen zou zijn (met de oude IntraBob v2.5). Deze keer echter nemen we ISAPI/NSAPI, omdat dat met IntraBob v3.01 zelfs nog makkelijker - vanuit de Delphi IDE zelf - te debuggen is:

Het maakt overigens niet uit of we voor een TTable of een TQuery kiezen; in beide gevallen wordt er van een TDataSetTableProducer gebruik gemaakt die met een TDataSet wil praten (de "parent" class van zowel een TTable en een TQuery). Ik beide gevallen kunnen we in de tweede pagina van de Wizard de Database Alias aangeven en de Tabel kiezen die we willen zien:

Vervolgens kunnen we aangeven welke velden we willen zien. Hierbij kies ik voor alle velden, inclusief de Image (TDBImage) en Question en Answer (beide TDBMemo) velden. Benieuwd hoe die er straks uit komen te zien.

In de voorlaatste pagina van de Database Web Application Wizard kunnen we aangeven of we zichtbare "borders" willen gebruiken in de tabel. Deze informatie zal gebruikt worden door de TDataSetTableProducer (maar merk op dat we al zagen dat we veel meer kunnen instellen in de Columns property editor van deze control (zoals de achtergrondkleur, de dikte van de "borders", alignment, etc).

De laatste pagina van de Database Web Application Wizard bevat een mededeling betreffende het testen van de Web Module toepassing, waar een Web browser en server voor nodig is. Dit is echter niet helemaal waar, zoals we straks zullen zien, want een kopie van IntraBob is genoeg, en biedt minstens zoveel test en debug gemak:

Als we op de Finish-knop drukken krijgen we een nieuw Delphi ISAPI/NSAPI (library) project, dat uit een Web Module bestaat, met daarop een Session, een Query en een DataSetTableProducer. De Web Module bevat één enkele aktie met als PathInfo "ShowTable", en de event code voor deze aktie is tevens de enige code die verder de moeite van het vermelden waard is:

  procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject;
    Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
  begin
    with DataSetTableProducer1 do begin
      Dataset.Open;
      Response.Content := Content;
      DataSet.Close
    end;
  end;
Dit is alles. En nog afgezien van de programmeerstijl (ik hou niet zo van de begin aan het eind van een regel, en zet hem liever op de volgende regel), ben ik toch een beetje teleurgesteld. Is dit nou alles? Ik had toch op z'n minst ook een Master/Detail-relationship willen zien. Of een Wizard die meteen het gebruik van de QueryTableProducer zou demonstreren. Of een Wizard die ook bijbehorende HTML pagina's kan genereren. Maar in ieder geval blijft er nu nog genoeg werk over voor mensen zoals ik, dus ik moet niet teveel klagen.

Testing/Debugging Web Modules
In de laatste pagina van de Wizard werd gesuggereerd dat we een web browser en een web server nodig hebben om Web Module applications te testen en te debuggen. Dat is gelukkig niet zo, want het kan ook eenvoudiger met behulp van IntraBob v3.01 (de laatste versie is te downloaded van mijn website). Het enige wat we moeten doen is de INTRABOB.EXE in dezelfde directory van het web Module project zetten, en een dummy HOME.HTM pagina maken die de aanroep naar de ISAPI DLL bevat. Vervolgens moeten we in de Delphi IDE met Run | Parameters als de "Host Application" INTRABOB.EXE invullen:

Merk overigens op dat Delphi 4 inmiddels ook de mogelijkheid biedt om remote te debuggen (iets waar ik in een toekomstig artikel wat dieper op in zal gaan).
Nadat de Host Application is gezet, kunnen we de ISAPI DLL vanuit de Delphi IDE draaien en ook debuggen. Als test heb ik een breakpoint gezet op de regel "DataSet.Close". Zodra wep F9 - Run drukken krijg we eerst IntraBob te zien, en moeten we daarin aangeven welke ISAPI.DLL en welk PathInfo relevant is (in dit geval PROJECT1.DLL en als PathInfo /ShowTable). Als we vervolgens op de "Submit" button binnen IntraBob drukken komen we terug in de Delphi IDE op de punt van het breakpoint:

Op dit punt kunnen we ook de Code Insights van Delphi 3 en 4 gebruiken om de inhoud van "Reponse.Content" te vertonen. Aangezien dat een vrij lange String is verschijnt heel even de tekst "[Evaluating...]" in beeld, zoals op bovenstaande screenshot te zien is. Door nogmaals op F9 te drukken gaan we weer door, en verschijnt het resultaat in de IntraBob browser:

Helaas valt meteen op dat de inhoud van de Question en Answer velden net als in Delphi 3, ook in Delphi 4 nog steeds als "(MEMO)" wordt afgebeeld, terwijl we natuurlijk de daadwerkelijke inhoud willen zien. Bezoekers van mijn "Delphi 3 C/S Web Modules" sessie tijdens de Conference to the Max in Mei zullen inmiddels weten hoe dit probleempje eenvoudig op te lossen is: met behulp van calculated fields. We moeten hiertoe met de rechtermuisknop op de TTable component klikken, en de fields editor opstarten. Hierin geven we aan dat we twee nieuwe velden willen toevoegen: Q (voor Question) en A (voor Answer). Beide van type String (lengte de maximale fieldsize van 8192), en beide type Calculated:

Vervolgens moeten we nog twee regels code schrijven om deze velden ook daadwerkelijk de juiste waarde te geven. Dat gebeurt in de OnCalcFields event, die we als volgt moeten implementeren:

  procedure TWebModule1.Table1CalcFields(DataSet: TDataSet);
  begin
    Table1A.AsString := Table1Answer.AsString;
    Table1Q.AsString := Table1Question.AsString
  end;
De "AsString" method geeft namelijk zelfs van TDBMemo velden de daadwerkelijke inhoud terug, en ook al lijkt bovenstaande code dus redelijk triviaal, het is precies wat we nodig hebben. Het enige wat we nu nog moeten doen is de TDataSetTableProducer vertellen dat deze geen gebruik moet maken van de Question en Answer velden, maar van de Q en A velden. Dit kunnen we doen met de nieuwe property editor voor het veld Columns van de TDataSetTableProducer:

Opnieuw compileren en draaien van PROJECT1.DLL geeft uiteindelijk het gewenste resultaat (alhoewel we bij het zien van onderstaande screenshot wellicht af kunnen vragen of een tabel wel de meest geslaagde vorm is om deze informatie in weer te geven, maar dat terzijde):

Oorspronkelijk was nu het plan om de "input" te gaan behandelen, met name aan de hand van de TQueryTableProducer component. Echter, Delphi 4 heeft naast de nieuwe DB Web Application Wizard ook een nieuw Web Modules component: de DataSetPageProducer. Een PageProducer (en dus geen TableProducer) voor DataSets inderdaad. En gezien het vorige voorbeeld, lijkt het me zinvoller om eerst dit nieuwe component nader te bekijken, en pas in de volgende paragraaf uitgebreid in te gaan op input, state en dergelijke zaken.

TDataSetPageProducer
De DataSetPageProducer lijkt sprekend op een normale PageProducer, met een extra DataSet property (die we naar Table1 laten wijzen). Het grote(re) verschil zit hem in de inhoud van de HTMLDoc (Strings) of HTMLFile (filenaam) properties, waarin we nu niet zomaar TAGs kunnen gebruiken, maar specifieke veldnamen uit de dataset. Stel dat we alleen de titel willen laten zien, dan is dat de <#Title> tag. En voor de Question en Answer moeten we resp. <#Q> en <#A> gebruiken, zodat een HTML template er bijvoorbeeld als volgt uit kan zien:

Indien we de normale PageProducer (of Delphi 3) hadden gebruikt, dan hadden we in de OnTag event de "Title", "Q" en "A" tags moeten vervangen door de huidige waarde van de Title, Q en A velden van Table1 (merk op dat Q en A nog steeds de calculated fields zijn die we eerder in deze aflevering gedefinieerd hadden). We hoeven dus in dit geval helemaal geen code te schrijven voor de OnTag event handler, tenzij we nog andere TAGs in de HTML page hadden verstopt (zoals een #TIME tag, die we kunnen vervangen door de huidige tijd met de melding "goede morgen" of "goede middag", afhankelijk van de daadwerkelijke tijd).
Alvorens we kunnen kijken of het werkt moeten we overigens eerst nog een nieuwe WebItemAction maken met behulp van de Actions editor, en in de OnAction event de volgende code schrijven:

  procedure TWebModule1.WebModule1WebActionItem2Action(Sender: TObject;
    Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
  begin
    with DataSetPageProducer1 do
    begin
      DataSet.Open;
      Response.Content := Content;
      DataSet.Close
    end
  end;
Dat lijkt inderdaad wel heel erg op de code die de DB Web Application Wizard kon genereren, alleen werkt die kennelijk (nog) niet met de TDataSetPageProducer, maar alleen maar voor de TDataSetTableProducer.
Na compileren ziet de output er in IntraBob als volgt uit:

Precies zoals ik hem hebben wil. Nu alleen nog de mogelijkheid om naar de vorige en de volgende vraag te zoeken, of zelfs iets toevoegen waarmee we de toepassing op slimme wijze kunnen laten zoeken naar bepaalde vragen (bijvoorbeeld op basis van een opgegeven onderwerp of trefwoord). Dat heeft echter allemaal te maken met "input"; gegevens die van de client naar de web server worden verzonden, en komt straks uitgebreid aan de orde.

TQueryTableProducer
De TQueryTableProducer levert output op die sterk lijkt op die van de TDataSetTableProducer. En dat is niet zo gek, want ze zijn beide afgeleid van de TDSTableProducer component. Waar de TDataSetTableProducer echter gekoppeld kan worden aan elke soort TDataSet (zoals een TTable maar ook een TQuery), komt de TQueryTableProducer echter pas goed tot zijn recht met een geparametriseerde TQuery component - een SQL query met een parameter dus. Als voorbeeld kunnen we in de beroemde BIOLIFE tabel gaan zoeken naar vissen met een bepaalde minimum lengte. Deze lengte willen we echter van te voren kunnen opgeven (dus tijdens run-time), en dat leidt tot een SQL query met een parameter voor de lengte. Start een nieuw Web Module project (met File | New - Web Module Application), en plaats een TQuery en een TQueryTableProducer op de Web Module.

Zet de DatabaseName van de Query op DBDEMOS, en schrijf de volgende SQL query in de SQL property. Als we op de elipsis (de drie puntjes) naast de SQL property klikken in de Object Inspector, dan krijgen we een dialoogje waarin we de SQL code kunnen schrijven. Deze dialoog heeft echter ook een "Code Editor" button, waarmee we de SQL query vanuit de Code Editor zelf kunnen schrijven (inclusief syntax highlighting dus).

Let op dat we het code window voor de WebModule1.Query1.SQL wel moeten sluiten om de waarde in de SQL property zelf te updaten. Vervolgens moeten we de Params property editor starten, en de "Length_In" property als DataType ftInteger, en als ParamType ftInput geven. We kunnen hier zelfs al een Value invullen (zoals 17 - inches) die als default waarde voor deze Parameter gebruikt kan worden. We kunnen tot slot controleren of de SQL code en de Parameter definitie geen fouten bevat door de Active property van de Query op True te zetten. Als dit lukt zonder problem, dan is het goed. Laat Active op True staan, en koppel nu de Query component aan de QueryTableProducer component door de Query property van de QueryTableProducer naar Query1 te laten wijzen, en klik hierna op de Columns property editor van de QueryTableProducer. Met de Columns Editor die we nu te zien krijgen kunnen we - tijdens design-time - aangeven welke velden en op welke manier we willen zien als output:

Merk op dat de eerste records al zichtbaar zijn: allen met een Length_In van minder dan 17, dus de default waarde van de Parameter is al gebruikt. Tot zover gaat het allemaal nog zoals vorige keren. Echter, nu willen we graag deze Query aansturen vanuit een webpagina waarbij we zelf de magische waarde (17 in dit voorbeeld) in kunnen vullen. Hiervoor zullen we toch eerst een webpagina moeten maken, waarin we de SDGN ISAPI DLL starten, met als enige input veld de "Length_In" die door de QueryTableProducer automatisch gekoppeld zal worden aan de Query parameter van dezelfde naam (bewaren in HOME.HTM):

  <FORM ACTION="http://www.drbob42.com/cgi-bin/sdgn.dll" METHOD=POST>
  Lengte: <INPUT NAME="Length_In">
  <P>
  <INPUT TYPE=SUBMIT>
  </FORM>
Als laatste moeten we natuurlijk nog een WebActionItem maken die de output van de QueryTableProducer naar de Response stuurt van onze Web Module toepassing. Dit kost slechts één regel code:
  procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject;
    Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
  begin
    Response.Content := QueryTableProducer1.Content
  end;
Vergeet om IntraBob als "Host Application" te zetten in de Run | Parameters dialoog. Als we vervolgens op de Run knop drukken, start IntraBob met de bovenstaande HTML pagina:

Na het invullen van 10 (alle vissen die kleiner zijn dan 10 inches) en het klikken op de Submit knop krijgen we het resultaat van de QueryTableProducer te zien:

Merk op dat we weer - net als in de eerder - de tekst '(MEMO)' te zien krijgen in plaats van de daadwerkelijke inhoud van het Notes veld. We kunnen dit weer oplossen met een Calculated Field (zoals we al eerder deden), maar er is nog een oplossing die iets eenvoudiger te implementeren is: de OnFormatCell event handler. Sluit IntraBob af (dan komen we weer terug in de Delphi 4 IDE), klik op de TQueryTableProducer en ga naar de event tab van de Object Inspector. Dubbelklik nu op de OnFormatCell event handler, en schrijf de volgende regel code:

  procedure TWebModule1.QueryTableProducer1FormatCell(Sender: TObject;
    CellRow, CellColumn: Integer; var BgColor: THTMLBgColor;
    var Align: THTMLAlign; var VAlign: THTMLVAlign; var CustomAttrs,
    CellData: String);
  begin
    if CellData = '(MEMO)' then
      CellData := (Sender AS TQueryTableProducer).
                    Query.Fields[CellColumn].AsString
  end;
Merk op dat bovenstaande code zo generiek als maar mogelijk is. We gebruiken de Sender om bij de juiste TQueryTableProducer te komen, en de CellColumn om bij het juiste veld te komen. In dat geval moeten we wel alle velden uit de TQuery gebruiken (en niet de Columns Editor gebruiken om velden te verwijderen, maar de Fields Editor van de TQuery). Het resultaat ziet eruit als volgt:

Meer Input
Het zal de oplettende lezer niet ontgaan zijn dat de "input" volledig automatisch werd afgehandeld door de TQuertTableProducer component. Gelukkig is het ook mogelijk om zelf bij deze input data te komen, zodat we bijvoorbeeld in de "header" voor de tabel nog even extra kunnen aangeven wat de maximale lengte was van de vis waarnaar we opzoek zijn. Hiervoor kunnen we de volgende code schrijven in de OnAction event:

  procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject;
    Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
  begin
    QueryTableProducer1.Header.Clear;
    QueryTableProducer1.Header.Add(
      '<p>Maximale lengte = ' + Request.ContentFields.Values['Length_In']);
    Response.Content := QueryTableProducer1.Content
  end;
De Request parameter bevat de ContentFields property (van type TStringList) die we middels de Values method kunnen benaderen om gericht de waarde van input data velden op te zoeken (zoals "Length_In"). We moeten trouwens QueryFields gebruiken in plaats van ContentFields als we van het GET protocol gebruik maken in plaats van het POST protocol.

Mocht iemand nog vragen, opmerkingen of suggesties hebben, dan hoor ik die het liefst via . Wie meer wil weten over web server toepassingen met WebBroker of de Delphi 6 uitbreiding WebSnap moet zeker eens overwegen om zich in te schrijven voor een van mijn Delphi Clinics.


This webpage © 1999-2006 by webmaster drs. Robert E. Swart (aka - www.drbob42.com). All Rights Reserved.