Delphi 6 WebSnap (Custom) Adapters |
Adapters
Maar eerst wil ik de centrale rol van de TAdapters binnen WebSnap laten zien.
Een Adapter kun je zien als de lijm tussen de data en de presentatie laag.
De informatie die je wilt presenteren kan hierbij uit een aantal bronnen komen, waaronder database tables (uiteraard met de TDataSetAdapter component).
De presentatie, oftewel de opbouw van de resulterende HTML pagina's gaat met behulp van een TAdapterPageProducer - een speciale PageProducer die met Adapters kan omgaan.
Ten einde de koppeling tussen informatie en presentatie mogelijk te maken bestaat een adapter component uit twee onderdelen: velden en akties.
Als voorbeeld zal ik met behulp van een standaard TAdapter iets bouwen - een creditcard verificatie pagina - die ik later in dit artikel in één standard component TCreditCardAdapter zal inbouwen.
Start nu Delphi 6 Enterprise (of Kylix 2 Enterprise), en begin een nieuwe WebSnap toepassing (via File | New - Other, ga naar de WebSnap tab en kies voor WebSnap Appication).
Kies een web server toepassing die je kunt testen op je machine - afhankelijk van de web server die erop staat.
Bij twijfel kun je altijd een Web App Debugger toepassing maken, die je kunt testen zonder web server.
Kies voor een Page Module, en verander de Page Name in "Home" (of iets dergelijks).
Omdat ik meteen in deze home pagina het gebruik van een Adapter wil laten zien, moeten we even op de "Page Options" knop drukken en in de Application Module Page Options dialoog aangeven dat ik een AdapterPageProducer wil gebruiken in plaats van - default - een normale PageProducer.
De laatste is namelijk gewoon een "oude" WebBroker PageProducer, die niet met Adapters om kan gaan.
Figuur 1 laat het resultaat zien. Vijf componenten, waaronder de AdapterPageProducer. Ga nu naar de WebSnap tab en bekijk de componenten erop. Er zijn al een aantal Adapter componenten aanwezig, namelijk (links) de TAdapter, TPagedAdapter, TDataSetAdapter, TLoginFormAdapter, etc. Daarnaast zie een ApplicationAdapter component in Figuur 1 - een van de zgn. globale adapters, waar je er maar eentje per toepassing van nodig hebt. Een ander voorbeeld is de EndUserAdapter en EndUserSessionAdapter (nodig om in te loggen).
TAdapter
We beginnen met een normale TAdapter component.
Zet die op de Page Module, en klik er met de rechtermuisknop op.
Je kunt nu kiezen voor de Fields Editor (om nieuwe Adapter velden aan te maken) en de Actions Editor (om uiteraard nieuwe Adapter akties aan te maken).
Adapter Fields
De Adapter velden vormen de doorgeefluiken voor de informatie die je wilt presenteren.
Ze kunnen of zelf direkt de gegevens opslaan, of die ergens anders vandaan halen.
Voor de standaard TAdapter zijn standaard zes mogelijke soorten Adapter velden aanwezig: de AdapterBooleanField, het generieke AdapterField (een string of Variant waarde), de AdapterFileField, de AdapterImageField, de AdapterMemoField en tot slot de AdapterMultiValueField.
Ieder veld soort is afgeleid van de TCustomAdapterField die geen gepubliceerde properties of events heeft, maar wel al intern de OnGetValue aanroept om de waarde van het Adapter veld op te halen, de valideren (met OnValidateValue), te wijzigen (OnUpdateValue) en te laten zien (met OnGetDisplayText).
Zie de on-line help voor wat meer informatie over de verschillende adapterveld soorten.
Voor ons voorbeeld wil ik een drietal "normale" Adapter velden gebruiken, dus voeg er drie toe in de Fields Editor.
Geef ze de namen AdaptCreditCardNumber, AdaptExpirationDate en AdaptUserName.
Selecteer nu elk van de drie Adapter velden en gebruik de Object Inspector om de DisplayLabel properties een fijne tekst te geven (die zien we straks nog terug). Je kan ook de Required property op True zetten voor alle velden, omdat ze allemaal verplicht zijn voor de credit card verificatie. De velden hebben een aantal event handlers, waaronder de OnGetValue die we kunnen gebruiken om de waarde van het veld op te halen (bijvoorbeeld uit een vorige bezoek of sessie). Dat zullen we zo zien; eerst een aktie toevoegen.
Adapter Actions
Nu we een drietal Adapter velden hebben aangemaakt wordt het tijd om na te denken over de aktie die we eraan toe kunnen voegen.
Sluit de Adapter Fields Editor en klik weer met de rechtermuisknop op de Adapter component, maar kies nu de Adapter Actions Editor.
Hier kunnen we maar één soort aktie toevoegen: de AdapterAction.
Geef hem de naam UseCreditCard, en geef de DisplayLabel property weer een leuke tekst.
De UseCreditCard Adapter aktie heeft een vijftal event handlers, waaronder de OnExecute (de belangrijkste), maar ook OnGetEnabled, OnGetParams, OnBeforeGetResponse en OnAfterGetResponse.
TAdapterPageProducer
Voordat ik de andere adapters laat zien, lijkt het me eerst zinvol om de samenwerking tussen de adapters en de TAdapterPageProducer te demonstreren.
Omdat ze allebei al op dezelfde Page Module staan, kun je direkt dubbelklikken op de AdapterPageProducer.
Dit resulteert in de Web Page Editor, die sommige mensen wellicht nog kennen uit de Delphi 5 InternetExpress tijd.
Hij werkt inderdaad nog precies hetzelfde, alleen nu met andere componenten (gekoppeld aan WebSnap adapter velden en akties).
Het scherm bestaat uit drie delen.
Linksboven is de boom met web componenten.
Daar meteen rechts van is het window waarin de kinderen van het huidig geselecteerde component in staan.
Nodig, omdat links alleen maar componenten staan die kinderen hebben of kunnen krijgen (dus alle "bladeren" staan alleen maar rechts - is soms even zoeken).
Onderaan staat de preview, zowel de browser preview als de HTML en het server-side script dat gegenereerd wordt.
Met de rechtermuisknop of de Insert knop kun je nieuwe componenten toevoegen als kind van het huidig geselecteerde component in de boom.
Begin met een AdapterForm, en daaronder een AdapterFieldGroup, AdapterCommandGroup en AdapterErrorList.
We krijgen nu meteen een paar waarschuwingen te zien:
Merk op dat de AdapterErrorList is wel zichtbaar in het rechter window, maar niet links (omdat er geen subcomponenten meer onder kunnen komen).
Nu nog optioneel de volgorde veranderen (zodat bijvoorbeeld de AdapterError list bovenaan de lijst staat, en de eventuele foutmeldingen dus bovenaan het scherm komen te staan), en dan kunnen we het geheel gaan testen.
Eerste Test
Bewaar alles (bijvoorbeeld de Page Module in wsWebMod.pas en het project in CGI.dpr), compileer de WebSnap toepassing, en gebruik hem op de geschikte wijze (een CGI executable of ISAPI DLL zal naar de scripts of cgi-bin directory moeten, een Web App Debugger executable kun je direct vanuit de Delphi 6 IDE uitvoeren).
Vergeet bij een CGI of ISAPI toepassing niet om ook de wsWebMod.html mee te nemen.
Je kunt de toepassing als CGI executable in de browser zien als http://localhost/scripts/CGI.exe
Als je de WebSnap toepassing op een machine zet waar Delphi 6 zelf niet ook staat, zul je een tweetal .tlb bestanden uit de Delphi6\bin directory naar die machine moeten meenemen, en aldaar moeten registreren met tregsvr.
Dit kan met:
tregsvr WebBrokerScript.tlb tregsvr stdvcl40.dllJe hebt daarnaast ook de Microsoft Script Engine nodig, maar die staat al op elke machine waar Windows 2000 of Internet Explorer versie 5 of hoger staat. Indien nodig kun je de Microsoft Script Engine ook zelf ophalen van de Microsoft website te http://msdn.microsoft.com/scripting/ |
Als je de drie editvelden invult en dan op "Use Credit Card" drukt gebeurt er verder niks: je krijgt weer hetzelfde scherm met lege velden. Om iets zinvols te doen met de gegevens zullen we de OnExecute event handler van de UseCreditCard Adapter aktie moeten invullen, net als de OnGetValue event handlers van de drie Adapter velden.
OnExcecute
In de OnExecute kunnen we de credit card gegevens testen, maar ook opslaan om in een later scherm te gebruiken.
Dit kan in een speciaal WebSnap sessie object, waarvoor we het TSessionServices component nodig hebben.
Maar omdat die niet werkt in CGI toepassingen (het SessionService object wordt na ieder request weer uit het geheugen verwijderd), sla ik voor dit voorbeeld de gegevens maar even op in een test bestand, en haal ze er in de OnGetValue weer uit.
Puur om te laten zien dat het werkt, meer niet.
Klik weer met de rechtermuisknop op de Adapter component en selecteer de Action Editor.
Kies hierin voor de UseCreditCard action en ga naar de Object Inspector om zijn OnExecute event handler als volgt in te vullen:
const sCreditCard = 'CreditCard'; sCardNumber = 'CardNumber'; sExpirationDate = 'ExpirationDate'; sUserName = 'UserName'; IniFileName = 'WebSnap.ini'; procedure THome.UseCreditCardExecute(Sender: TObject; Params: TStrings); var Value: IActionFieldValue; Logfile: TIniFile; begin LogFile := TIniFile.Create(IniFileName); try Value := AdaptCreditCardNumber.ActionValue; if Value.ValueCount > 0 then Logfile.WriteString(sCreditCard,sCardNumber,Value.Values[0]); Value := AdaptExpirationDate.ActionValue; if Value.ValueCount > 0 then Logfile.WriteString(sCreditCard,sExpirationDate,Value.Values[0]); Value := AdaptUserName.ActionValue; if Value.ValueCount > 0 then Logfile.WriteString(sCreditCard,sUserName,Value.Values[0]); finally LogFile.UpdateFile; LogFile.Free end end;
OnGetValue
In de OnGetValue event handler van AdaptCreditCardNumber, AdaptExpirationDate en AdaptUserName kunnen we de adapter velden van een waarde voorzien (zonder dat zullen ze leeg zijn).
De implementatie van eentje van hen is als volgt (de rest is hetzelfde).
Merk op dat de sCreditCard en sCardNumber strings ook hier weer gebruikt worden - al is het maar om tikfouten te voorkomen.
procedure THome.AdaptCreditCardNumberGetValue(Sender: TObject; var Value: Variant); var Logfile: TIniFile; begin LogFile := TIniFile.Create(IniFileName); try Value := Logfile.ReadString(sCreditCard,sCardNumber,'8888-8888-8888-8888') finally LogFile.Free end end;Het resultaat hiervan is dat je de eerste keer de default waardes ziet (die je opgeeft bij het inlezen van de adapter velden uit de WebSnap.ini file), en daarna steeds de vorige waarden terugziet.
Meer Adapters
Naast de generieke TAdapter, die ik zojuist heb gedemonstreerd, bevat Delphi nog een aantal speciale Adapters.
De TPagedAdapter heeft als extra de PageSize property, die aangeeft hoeveel elementen er per pagina moet worden weergegeven.
Dit is bijvoorbeeld zinvol bij een zoekmachine, waarbij je maar tien resultaten per pagina wilt zien.
Of een catalogus, waarbij je ook maar een beperkt aantal items per pagina wilt zien.
Zodra de PageSize een getal groter dan 0, worden er boven en onder de output van de PagedAdapter drie links aangemaakt: Prev, Next en eentje met de nummers van de pagina's zelf (die kan nogal lang worden als je erg veel resultaten hebt en er maar een beperkt aantal op je pagina wilt laten zien, natuurlijk).
De PageSize property is ook onderdeel van de TDataSetAdapter, dus is het makkelijker om het effekt te zien met deze laatste component.
De TDataSetAdapter wordt gekoppeld aan een DataSet (table of query) en haalt zijn Adapter velden en akties direkt uit deze dataset.
Erg fijn om op die manier snel van een tabel naar een interaktief HTML formulier te gaan.
Tot slot is de TLoginFormAdapter ook een echte speciale adapter die je kunt gebruiken om gebruikers in te laten loggen.
Met UserName, Password en NextPage velden en de ActionLogin action is dit de meest specifieke Adapter tot nu toe.
En nu gaan we een TCreditCardAdapter schrijven, die lijkt op de TLoginFormAdapter, maar veel zal doen wat we hiervoor al hebben gedaan met de normale Adapter component.
TCreditCardAdapter
We beginnen met een TCustomCreditCardAdapter.
Custom, omdat we in eerste instantie nog niet alle properties en events willen publiceren (dan kunnen andere ontwikkelaars er wellicht ook nog wat mee).
De properties zijn nog nog public.
De declaratie van de TCustomCreditCardAdapter bestaat uit een aantal onderdelen.
De drie Adapter Fields zelf zijn geen expliciete onderdelen van de class zelf (want de eindgebruiker kan ze toevoegen in de Fields Editor).
Wel zullen we steeds drie methoden hebben voor het opvragen van de waarde, de validatie, en het eventueel geven van een foutmelding.
Daarnaast zullen we ook een viertal interface methoden moeten implementeren om het toevoegen van adapter velden en akties toe te staan.
Tot slot zal er voor de Adapter Action ook geen expliciete action in de class zijn opgenomen, maar moeten we wel al rekening houden met de event handler die komen gaat:
type TCreditCardUseEvent = procedure(Sender: TObject; CardNumber, ExpirationDate, UserName: Variant) of object;De definitie van de TCustomCreditCardAdapter zelf is als volgt (als voorouder gebruik ik de TDefaultFieldsAdapter omdat die default velden en akties kan toevoegen aan de adapter- iets wat ik inderdaad wil hebben in ons voorbeeld):
type TCustomCreditCardAdapter = class(TDefaultFieldsAdapter) private // The event handler pointers FOnValidateCardNumber: TValidateAdapterFieldEvent; FOnValidateExpirationDate: TValidateAdapterFieldEvent; FOnValidateUserName: TValidateAdapterFieldEvent; FOnCreditCard: TCreditCardUseEvent; private // Get Adapter Field values function GetCardNumber: Variant; function GetExpirationDate: Variant; function GetUserName: Variant; protected // Validate Adapter values procedure ValidateCardNumber; procedure ValidateExpirationDate; procedure ValidateUserName; protected // Raise Adapter Field Errors procedure RaiseBlankCardNumber; procedure RaiseBlankExpirationDate; procedure RaiseBlankUserName; protected // Execute Adapter Action Event procedure ExecuteCreditCard(CardNumber, ExpirationDate, UserName: Variant); protected { IAdapterEditor } function ImplCanAddFieldClass(AParent: TComponent; AClass: TClass): Boolean; override; function ImplCanAddActionClass(AParent: TComponent; AClass: TClass): Boolean; override; { IWebDataFields } procedure ImplGetFieldsList(AList: TStrings); override; { IWebActionsList } procedure ImplGetActionsList(AList: TStrings); override; public property OnValidateCardNumber: TValidateAdapterFieldEvent read FOnValidateCardNumber write FOnValidateCardNumber; property OnValidateExpirationDate: TValidateAdapterFieldEvent read FOnValidateExpirationDate write FOnValidateExpirationDate; property OnValidateUserName: TValidateAdapterFieldEvent read FOnValidateUserName write FOnValidateUserName; property OnCreditCard: TCreditCardUseEvent read FOnCreditCard write FOnCreditCard; property CardNumber: Variant read GetCardNumber; property ExpirationDate: Variant read GetExpirationDate; property UserName: Variant read GetUserName; end;Om maar met de vier interface methoden te beginnen: die bepalen welke Adapter velden en akties toegevoegd kunnen worden in de Fields Editor en Actions Editor van onze CustomCreditCardAdapter. Voor we aan de implementatie daarvan kunnen beginnen, zullen we eerst een definitie moeten geven van de generieke classes voor deze velden en akties. Die zijn als volgt:
type TCreditCardAdapterField = class(TAdapterNamedDisplayField) protected function GetAdapter: TCustomCreditCardAdapter; public property Adapter: TCustomCreditCardAdapter read GetAdapter; end; TCreditCardAdapterAction = class(TCustomAdapterAction) private function GetAdapter: TCustomCreditCardAdapter; protected property Adapter: TCustomCreditCardAdapter read GetAdapter; end;Dit is de meest generieke vorm van een Adapter Field en Action die we kunnen gebruiken in onze CustomCreditCardAdapter. Ze weten allebei bij welke Adapter ze horen (daar moeten we de GetAdapter funktie nog voor implementeren), en zijn de base classes voor de drie specifieke Adapter Fields en de specifieke Adapter Action die nog komen gaan.
function TCustomCreditCardAdapter.ImplCanAddFieldClass(AParent: TComponent; AClass: TClass): Boolean; begin Result := inherited ImplCanAddFieldClass(AParent, AClass) or AClass.InheritsFrom(TCreditCardAdapterField) end; function TCustomCreditCardAdapter.ImplCanAddActionClass( AParent: TComponent; AClass: TClass): Boolean; begin Result := inherited ImplCanAddActionClass(AParent, AClass) or AClass.InheritsFrom(TCreditCardAdapterAction) end;Voor het daadwerkelijke toevoegen moeten we de ImplGetFieldsList van de IWebDataFields en de ImplGetACtionsList van de IWebActionsList interfaces implementeren. Beide methoden voegen de classtypes toe die mogelijk zijn. Maar die zullen we eerst zelf moeten definieren (en afleiden van resp. TCreditCardAdapterField en TCreditCardAdapterAction).
type TCreditCardAdapterCardNumberField = class(TCreditCardAdapterField) protected function ImplGetValue: Variant; override; published property FieldName; property DisplayLabel; property DisplayWidth; end;Voor TCreditCardAdapterExpirationDateField en TCreditCardAdapterUserNameField geldt een gelijke definitie (dus die schrijf ik niet op), en voor de TCreditCardAdapterCreditCardAction afgeleid van TCreditCardAdapterAction geldt de volgende definitie:
type TCreditCardAdapterCreditCardAction = class(TCreditCardAdapterAction) protected procedure ImplExecuteActionRequest(AActionRequest: IActionRequest; AActionResponse: IActionResponse); override; function GetDefaultActionName: string; override; published property DisplayLabel; property OnBeforeExecute; property OnAfterExecute; property OnBeforeGetResponse; property OnAfterGetResponse; end;Nu we deze class definities hebben gemaakt, kunnen we de TCustomCreditCardAdapter methoden afkomstig van de IWebDataFields en IWebACtionsList interfaces implementeren (om een totale lijst van alle mogelijke classes te maken):
procedure TCustomCreditCardAdapter.ImplGetFieldsList(AList: TStrings); begin AList.Clear; AList.AddObject(sCreditCardNumber, TObject(TCreditCardAdapterCardNumberField)); AList.AddObject(sCreditCardExpirationDate, TObject(TCreditCardAdapterExpirationDateField)); AList.AddObject(sCreditCardUserName, TObject(TCreditCardAdapterUserNameField)); end; procedure TCustomCreditCardAdapter.ImplGetActionsList(AList: TStrings); begin AList.Clear; AList.AddObject(sCreditCardAction, TObject(TCreditCardAdapterCreditCardAction)) end;In bovenstaande listing staan de namen van de drie Adapter velden en de Adapter aktie.
CreditCard Adapter Fields
Laten we even verder gaan met de implementatie van TCreditCardAdapterField en de drie afgeleide velden.
Allereerst de GetAdapter method, die is geïmplementeerd als volgt:
function TCreditCardAdapterField.GetAdapter: TCustomCreditCardAdapter;
begin
if (inherited Adapter <> nil) and
(inherited Adapter is TCustomCreditCardAdapter) then
Result := TCustomCreditCardAdapter(inherited Adapter)
else
begin
Result := nil;
Assert(False) // CreditCardAdapter not found
end
end;
De Assert(False) heb ik overgenomen van het voorbeeld van de TLoginFormAdapter.
Dit is een manier om aan te geven (als je Assertions gebruikt bij het compileren) dat het veld bij een foute vader Adapter is aangemaakt.
In praktijk zou dit niet eens voor mogen komen.
function TCreditCardAdapterCardNumberField.ImplGetValue: Variant; begin Result := '' end;De implementatie voor de ImplGetValue van TCreditCardAdapterExpirationDateField en TCreditCardAdapterUserNameField is uiteraard gelijkvormig aan die voor de hier gegeven ImplGetValue van TCreditCardAdapterCardNumberField.
CreditCard Adapter Actions
Bij het kijken naar de CreditCard Adapter akties beginnen we ook hier met de GetAdapter method, die hetzelfde eruitziet als die van de CreditCard Adapter Field base class:
function TCreditCardAdapterAction.GetAdapter: TCustomCreditCardAdapter;
begin
if (inherited Adapter <> nil) and
(inherited Adapter is TCustomCreditCardAdapter) then
Result := TCustomCreditCardAdapter(inherited Adapter)
else
begin
Result := nil;
Assert(False) // CreditCardAdapter not found
end
end;
Daarnaast heeft de echte TCreditCardAdapterCreditCardAction class twee methods, namelijk GetDefaultActionName (die de string sCreditCardAction teruggeeft - er is er bovendien maar één), en ImplExecuteActionRequest.
De laatste voert de validatie van de drie Adapter velden uit en roept tevens de OnExecute event handler aan (als de programmeur daar nog een event handler aan heeft hangen).
function TCreditCardAdapterCreditCardAction.GetDefaultActionName: string;
begin
Result := sCreditCardAction
end;
procedure TCreditCardAdapterCreditCardAction.ImplExecuteActionRequest(
AActionRequest: IActionRequest; AActionResponse: IActionResponse);
begin
if Adapter <> nil then
begin
with Adapter do // TCustomCreditCardAdapter
begin
try
UpdateRecords;
ValidateCardNumber;
ValidateExpirationDate;
ValidateUserName;
ExecuteCreditCard(CardNumber,ExpirationDate,UserName)
except
on E: Exception do
Errors.AddError(E)
end
end
end
end;
Zoals je ziet wordt een mogelijke exception (die door de Validate routines moet worden gegenereerd) afgevangen en aan de Errors list toegevoegd.
Helaas levert dit alleen maar de eerste fout op, en niet alle fouten.
Om meteen alle mogelijke fouten op te leveren - een vraag van iemand tijdens de Conference to the Point van 13 december 2001 - zal ik het try-except blok in vieren moeten splitsen, als volgt:
procedure TCreditCardAdapterCreditCardAction.ImplExecuteActionRequest(
AActionRequest: IActionRequest; AActionResponse: IActionResponse);
begin
if Adapter <> nil then
begin
with Adapter do // TCustomCreditCardAdapter
begin
try
UpdateRecords
except
on E: Exception do
Errors.AddError(E)
end;
try
ValidateCardNumber
except
on E: Exception do
Errors.AddError(E)
end;
try
ValidateExpirationDate
except
on E: Exception do
Errors.AddError(E)
end;
try
ValidateUserName
except
on E: Exception do
Errors.AddError(E)
end;
if Errors.ErrorCount = 0 then
ExecuteCreditCard(CardNumber,ExpirationDate,UserName)
end
end
end;
De ExecuteCreditCard wordt nu alleen uitgevoerd als er geen errors zijn (als Errors.ErrorCount gelijk is aan nul).
De Errors list kan tevens als input dienen voor een AdapterErrorList in een AdapterForm, zoals we eerder zagen.
TCustomCreditCardAdapter
Nu we alle velden en akties gehad hebben, kunnen we terugkeren naar de normale methoden van de TCustomCreditCardAdapter: voor het ophalen, valideren en melden van een fout.
Het ophalen van een waarde, zoals die van de CardNumber adapter, gebeurt door het opzoeken van de waarde van het betreffende veld met FieldValues.ValueOfField.
function TCustomCreditCardAdapter.GetCardNumber: Variant; var FieldValue: IActionFieldValue; FieldValues: IActionFieldValues; begin Result := Unassigned; if Supports(WebContext.AdapterRequest, IActionFieldValues, FieldValues) then begin FieldValue := FieldValues.ValueOfField(sCreditCardNumber); if FieldValue <> nil then Result := FieldValue.Values[0] end end;Voor GetExpirationDate en GetUserName geldt uiteraard een vergelijkbare implementatie.
Validate
Het valideren van de waarde van een Adapter veld gebeurt op twee manieren.
Allereerst wordt gekeken of de event handler (zoals OnValidateCreditCardNumber) is ingevuld door de WebSnap ontwikkelaar - de gebruiker van de TCustomCreditCardAdapter.
Als dit het geval is, dan wordt deze event handler eerst aangeroepen.
Daarbinnen kan de ontwikkelaar de parameter "Handled" op True zetten om aan te geven dat de verificatie afgelopen is - er zal dan geen verdere verificatie meer plaatsvinden.
Als Handled echter op False blijft staan, is het onze beurt, en volgt de code die verder nog in de ValidateCardNumber method staat (in dit geval niet zoveel zinvols, maar je zou je voor kunnen stellen dat je behalve de test voor een leeg nummer, ook test of het een geldig nummer is door daar wat van de bekende tests op uit te voeren).
procedure TCustomCreditCardAdapter.ValidateCardNumber; var Handled: Boolean; begin Handled := False; if Assigned(OnValidateCardNumber) then OnValidateCardNumber(Self, CardNumber, Handled); if not Handled then if VarIsEmpty(CardNumber) or (Trim(string(CardNumber)) = '') then RaiseBlankCardNumber; end;Als er een foutsituatie is opgetreden - de validate slaagt niet - dan kun wordt met de RaiseBlankCardNumber de fout doorgegeven. Deze laatste raised een exception, die in het try-except blok van de Adapter Action wordt afgevangen en toegevoegd aan de Error list (zoals we al eerder zagen).
procedure TCustomCreditCardAdapter.RaiseBlankCardNumber; begin raise EAdapterFieldException.Create(sBlankCardNumber, sCreditCardNumber) end;
Execute CreditCard
Tot slot de methode van de TCustomCreditCard die de CreditCard dan daadwerkelijk gebruikt: de ExecuteCreditCard.
Deze methode wordt aangeroepen door de Adapter Action, en zal eigenlijk alleen maar kijken of de event handler (de OnCreditCard) door de WebSnap ontwikkelaar is ingevuld, en indien aanwezig deze ook uitvoeren, waarbij alle drie de Adapter velden als argumenten worden meegegeven.
Dit is de laatste kans - na de individuele verificatie - die de WebSnap ontwikkelaar heeft om nog een fout te melden.
Maar meestal zal in de OnCreditCard de betaling zelf plaatsvinden, en hoef je alleen nog de terugkoppeling te doen.
procedure TCustomCreditCardAdapter.ExecuteCreditCard(CardNumber, ExpirationDate, UserName: Variant); begin if Assigned(OnCreditCard) then OnCreditCard(Self, CardNumber, ExpirationDate, UserName) end;
TCreditCardAdapter
De TCreditCardAdapter component waar het om draait is eigenlijk vrij simpel.
Hij is afgeleid van de TCustomCreditCardAdapter en publiceert een aantal properties.
De Data property bevat de Adapter velden, de Actions property de Adapter akties.
De rest hebben we al gezien.
type TCreditCardAdapter = class(TCustomCreditCardAdapter) published property Data; property Actions; property OnValidateCardNumber; property OnValidateExpirationDate; property OnValidateUserName; property OnCreditCard; property OnBeforeExecuteAction; property OnAfterExecuteAction; property OnBeforeGetActionResponse; property OnAfterGetActionResponse; end;
Registratie
En last-but-not-least: de registratie van de TCreditCardAdapter met al zijn velden en akties.
Omdat dit niet zomaar componenten zijn, kunnen we ook niet zomaar een Register routine met een aanroep naar RegisterComponents gebruiken.
In plaats daarvan moeten we eerst RegisterWebComponents gebruiken voor alle Adapter velden en akties, en daarna pas de RegisterComponents voor de TCreditCardAdapter op de DrBob42 (of de WebSnap) tab van het Component Palette.
procedure Register;
begin
RegisterWebComponents([TCreditCardAdapterField]);
RegisterWebComponents([TCreditCardAdapterCardNumberField]);
RegisterWebComponents([TCreditCardAdapterExpirationDateField]);
RegisterWebComponents([TCreditCardAdapterUserNameField]);
RegisterWebComponents([TCreditCardAdapterAction]);
RegisterWebComponents([TCreditCardAdapterCreditCardAction]);
//RegisterWebComponents(TCustomCreditCardAdapter);
RegisterComponents('DrBob42',[TCreditCardAdapter]);
end;
Een eigen icon maken laat ik over aan wie daar zin in heeft.
De installatie gaat eenvoudig: doe Component | Install Component en instaleer de unit met de TCreditCardAdapter in de Delphi User Components package (tenzij je er een eigen package voor wilt maken natuurlijk).
Gebruik
Na installatie is de TCreditCardAdapter terug te vinden op het Component Palette van Delphi of Kylix, en kunnen we de component gebruiken op onze Page Module (zie Figuur 7):
Via de rechtermuisknop op de CreditCardAdapter component kunnen we de Fields Editor, en straks ook de Actions Editor selecteren. In beide hebben we de mogelijkheid tot "Add All Fields" (en Add All Actions). Dit levert de voorgedefinieerde velden en akties op, die we in de eerdergeschreven ImplGetFieldsList en ImplGetActionsList hebben aangegeven. Dat was de hoofdreden dat de TCustomCreditCardAdapter is afgeleid van de TDefaultFieldsAdapter. Verder hoeven we niks meer te doen. In tegenstelling tot de normale adapter waar we de drie velden stuk voor stuk moesten aanmaken en nog code moesten schrijven, kunnen we ons hier beperken tot het gebruiken van de voorgebakken funktionaliteit. Je kan natuurlijk wel weer de DisplayLabel properties aanpassen aan je wensen.
Je ziet, het ziet er hetzelfde uit. En als we dit in praktijk testen, dan zul je ook de drie exceptions zien in de errorlist (tussen de button en de fieldgroup), om aan te geven dat je bijvoorbeeld het CardNumber, de ExpirationDate of de UserName leef hebt gelaten.
Conclusie
WebSnap is een nieuwe architectuur voor Delphi, Kylix (en inmiddels ook C++Builder) waarin de Adapter componenten een verbinding zorgen tussen de data van de web server toepassing en de presentatie (vaak in HTML) naar de eindgebruiker.
In dit artikel heb ik laten zien wat adapters zijn, hoe ze werken, en hoe je er zelf eentje kunt bouwen.
Dit is maar het begin natuurlijk, want iedereen kan zelf nieuwe adapters bedenken en bouwen, alhoewel het wel echt nuttig en herbruikbaar moet zijn wil je daadwerkelijk een nieuwe adapter component gaan bouwen (anders is het waarschijnlijk toch sneller om gewoon met een standaard TAdapter te beginnen en zelf de velden en akties toe te voegen).
In dat kader is het aardig om te melden dat ik inmiddels een WebSnap Custom Adapter Wizard heb gemaakt die gebruikt kan worden om zelf snel Custom Adapters te genereren!
Mocht iemand nog vragen, opmerkingen of suggesties hebben, dan hoor ik die het liefst via .