Difference between revisions of "TutWebAppServer"

From Overbyte
Jump to navigation Jump to search
(Lesson 4 : Building tables)
Line 97: Line 97:
 
==== Summary ====
 
==== Summary ====
 
In this lesson you learned how to write a template for a dynamic page, link the dynamic page URL to a Delphi class, write a Delphi class implementing the processing required to get data and feed it to the template.
 
In this lesson you learned how to write a template for a dynamic page, link the dynamic page URL to a Delphi class, write a Delphi class implementing the processing required to get data and feed it to the template.
<br><br>
+
<br><br><br>
 
=== Lesson 3 : Simple data entry and processing ===
 
=== Lesson 3 : Simple data entry and processing ===
 
During this lesson, you will learn how to present a data entry form to the user,  process entered data and produce a result page. For simplicity, we will use a simple form to enter two numbers, an operator and provide the result in another simple page. Later, in another lesson, we will use AJAX to update the current page with the computation result.
 
During this lesson, you will learn how to present a data entry form to the user,  process entered data and produce a result page. For simplicity, we will use a simple form to enter two numbers, an operator and provide the result in another simple page. Later, in another lesson, we will use AJAX to update the current page with the computation result.
Line 178: Line 178:
 
<tt>
 
<tt>
 
::    <#Operator>(<#Number1>, <#Number2>) = <#Result>
 
::    <#Operator>(<#Number1>, <#Number2>) = <#Result>
::    <br><br>
 
 
::    <a href="SimpleCalculator.html">Continue</a>
 
::    <a href="SimpleCalculator.html">Continue</a>
 
</tt>
 
</tt>
Line 194: Line 193:
 
:::    ExtractURLEncodedValue(Params, 'Operator', Op);
 
:::    ExtractURLEncodedValue(Params, 'Operator', Op);
 
:::    '''if''' SameText(Op, 'Add') '''then'''
 
:::    '''if''' SameText(Op, 'Add') '''then'''
:::        R := StrToInt(N1) + StrToInt(N2)
+
::::        R := StrToInt(N1) + StrToInt(N2)
 
:::    '''else if''' SameText(Op, 'Multiply') '''then'''
 
:::    '''else if''' SameText(Op, 'Multiply') '''then'''
:::        R := StrToInt(N1) * StrToInt(N2)
+
::::        R := StrToInt(N1) * StrToInt(N2)
 
:::    '''else'''
 
:::    '''else'''
:::        R := 0;
+
::::        R := 0;
 
:::    AnswerPage(&rsquo;&rsquo;, NO_CACHE, 'SimpleCalculatorResult.html', nil,
 
:::    AnswerPage(&rsquo;&rsquo;, NO_CACHE, 'SimpleCalculatorResult.html', nil,
::::              ['Number1', N1, 'Number2', N2, 'Operator', Op, 'Result', R]);
+
::::::              ['Number1', N1, 'Number2', N2, 'Operator', Op, 'Result', R]);
 
:::    Finish;
 
:::    Finish;
 
::'''end''';
 
::'''end''';
 
</tt>
 
</tt>
 +
You can see that we used NO_CACHE constant in the call to AnswerPage. This constant instruct the client browser to not store the page into his cache. This is very important since it is a dynamic page. If the client navigator store the page in his cache, then the user would see an old version showing old results.
 
</li>
 
</li>
 
<li>Finally, we have to map our class to an URL. This is done by calling AddGetHandler from TTutWebAppServerMainForm.FormShow.  
 
<li>Finally, we have to map our class to an URL. This is done by calling AddGetHandler from TTutWebAppServerMainForm.FormShow.  
Line 211: Line 211:
 
<li>Hit F9 to compile and run the application. Point your browser to http://127.0.0.1:20080/SimpleCalculator.html. You can enter values and submit. You should get the expected result page.</li>
 
<li>Hit F9 to compile and run the application. Point your browser to http://127.0.0.1:20080/SimpleCalculator.html. You can enter values and submit. You should get the expected result page.</li>
 
</ol><br>
 
</ol><br>
 +
 +
==== Summary ====
 +
In this lesson you learned how to get back data from a HTML form, do some processing with it and produce a result page. You also learned how to instruct the client navigator to not store an answer page to his cache.
 +
<br><br><br>
 +
 +
=== Lesson 4 : Building tables ===
 +
During this lesson, you will learn how to build a table with a variable number of rows containing dynamic data. In a typical application, a table is used to show the result of a database query (for example a list of items matching a given search criteria) or to build a list of items to fill a HTML combobox or anything else repetitive.
 +
 +
In this lesson, to avoid database stuff we have not yet learned, we will do something simple and yet useful: We will create a password generator. The user will be presented a form where he can enter the number of passwords he wants. The program will generated those passwords and present them in a nice HTML table.
 +
 +
Since we already learned how to handle HTML forms to enter data, I will quickly pass over those steps to come directly tho this lesson's objective: generate a table.
 +
 +
==== Steps ====
 +
<ol>
 +
<li> Create the entry form to enter the number of passwords the user want to get. Save the form to c:\icstutorial\templates\GenerateTable.html. Create the code to handle the form and to get the value back. Save the code into a new unit named OverbyteIcsTutWebAppServerGenerateTable.pas. Name the two classes TUrlHandlerGenerateTable and TUrlHandlerDoGenerateTable. Map the two classes to the URL GenerateTable.html and DoGenerateTable.html. For detailed steps, please see lesson 2.
 +
<br>
 +
The HTML form include the following code:
 +
<tt>
 +
:::    <form action="DoGenerateTable.html"
 +
:::::          method="GET"
 +
:::::          enctype="application/x-www-form-urlencoded">
 +
::::      Enter the number of passwords you want
 +
::::      <input type="TEXT" name="PasswordCount" size="3">
 +
::::      <input type="SUBMIT" value="Generate">
 +
:::    </form>
 +
</tt>
 +
<br>
 +
The unfinished new Delphi unit looks like:
 +
<tt>
 +
:::'''unit''' OverbyteIcsTutWebAppServerGenerateTable;
 +
:::<br>
 +
:::'''interface'''
 +
:::<br>
 +
:::'''uses'''
 +
::::    SysUtils, OverbyteIcsHttpAppServer, OverbyteIcsHttpSrv;
 +
:::<br>
 +
:::'''type'''
 +
::::    TUrlHandlerGenerateTable = '''class'''(TUrlHandler)
 +
::::    '''public'''
 +
:::::        '''procedure''' Execute; '''override;'''
 +
::::    '''end;'''
 +
:::<br>
 +
::::    TUrlHandlerDoGenerateTable = '''class'''(TUrlHandler)
 +
::::    '''public'''
 +
:::::        '''procedure''' Execute; '''override;'''
 +
::::    '''end;'''
 +
:::<br>
 +
:::'''implementation'''
 +
:::<br>
 +
:::'''procedure''' TUrlHandlerGenerateTable.Execute;
 +
:::'''begin'''
 +
::::    AnswerPage(&rsquo;&rsquo;, NO_CACHE, 'GenerateTable.html', nil, []);
 +
::::    Finish;
 +
:::'''end;'''
 +
:::<br>
 +
:::'''procedure''' TUrlHandlerDoGenerateTable.Execute;
 +
:::'''var'''
 +
::::    PasswordCount : '''String;'''
 +
:::'''begin'''
 +
::::    ExtractURLEncodedValue(Params, 'PasswordCount',  PasswordCount);
 +
::::    // Here comes the processing and the AnswerPage
 +
::::    Finish;
 +
:::'''end;'''
 +
:::<br>
 +
:::'''end.'''
 +
</tt>
 +
<br>
 +
The two lines to be inserted in TTutWebAppServerMainForm.FormShow to map the URL are:
 +
<tt>
 +
::::    HttpAppSrv1.AddGetHandler('/GenerateTable.html', TUrlHandlerGenerateTable);
 +
::::    HttpAppSrv1.AddGetHandler('/DoGenerateTable.html', TUrlHandlerDoGenerateTable);
 +
</tt>
 +
</li>
 +
<li> Write the code to generate the requested number of passwords. Here we have two options: either generate all passwords before generating the actual table, or generate the passwords on the fly while generating each table row. Since the first option gives a code easier to understand, I will use it. The passwords will be generated in a TStringList which will be used later when generating each table row.
 +
<br>
 +
Passwords will be generated using Delphi random generator. For simplicity, we will generate passwords containing uppercase and lowercase letters plus all ten digits. Passwords will have 12 characters.
 +
<br>
 +
The code looks like:
 +
<tt>
 +
:::'''procedure''' TUrlHandlerDoGenerateTable.GeneratePasswords(PasswordCount: Integer);
 +
:::'''var'''
 +
::::    Password : '''String;'''
 +
::::    I        : '''Integer;'''
 +
:::'''const'''
 +
::::    PasswordLen = 12;
 +
::::    PasswordChars : '''String''' = 'abcdefghijklmnopqrstuvwxyz' +
 +
::::::::                            'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
 +
::::::::                            '0123456789';
 +
:::'''begin'''
 +
::::    RandSeed  := GetTickCount;
 +
::::    FPasswords := TStringList.Create;
 +
::::    SetLength(Password, PasswordLen);
 +
::::    '''while''' PasswordCount > 0 '''do begin'''
 +
:::::        '''for''' I := 1 '''to''' PasswordLen '''do'''
 +
::::::            Password[I] := PasswordChars[1 + Random(Length(PasswordChars))];
 +
:::::        FPasswords.Add(Password);
 +
:::::        Dec(PasswordCount);
 +
::::    '''end;'''
 +
:::'''end;'''
 +
</tt>
 +
</li>
 +
<li> Now that we have a function to generate the passwords, we may use it into the simple procedure TUrlHandlerDoGenerateTable we wrote before:
 +
<tt>
 +
:::'''procedure''' TUrlHandlerDoGenerateTable.Execute;
 +
:::'''var'''
 +
::::    PasswordCount : '''String;'''
 +
:::'''begin'''
 +
::::    ExtractURLEncodedValue(Params, 'PasswordCount',  PasswordCount);
 +
::::    GeneratePasswords(StrToInt(PasswordCount));
 +
::::    OnGetRowData := GetRowData;
 +
::::    AnswerPage(&rsquo;&rsquo;, NO_CACHE, 'GeneratePasswordResult.html', nil, []);
 +
::::    Finish;
 +
:::'''end;'''
 +
</tt>
 +
You can see here a new comer: OnGetRowData. This is a kind of event which will be called by AnswerPage whenever a table is found in the template file. A table is marked in a template file using the special tag <#table_rows>. The next step show how to use that tag.</li>
 +
<li> Create a new HTML template file for presenting the password list and name it GeneratePasswordResult.html. We will build a real HTML table here. There is nothing special about the HTML table except we only create one row and surround it with the special tags <#table_rows> and <#/table_rows>. When AnswerPage will find the first tag, it starts repeating the bloc until the second tag. In each iteration it call the OnGetRowData event handler to get the data and know if there are more rows.
 +
 +
Here is how the HTML source code looks like in the body of the HTML file:
 +
<tt>
 +
:::    &lt;table>
 +
::::      &lt;tr>&lt;td>N°&lt;/td>&lt;td>Password&lt;/td>&lt;/tr>
 +
::::      &lt;#table_rows PasswordTable>
 +
:::::        &lt;tr>&lt;td>&lt;#Row>&lt;/td>&lt;td>&lt;#Password>&lt;/td>&lt;/tr>
 +
::::      &lt;#/table_rows>
 +
:::    &lt;/table>
 +
</tt>
 +
You see a parameter within <#table_rows>. It is used in OnGetRowData so that Delphi code knows which table is currently built because a single HTML template can have multiple tables and tables may be nested.
 +
</li>
 +
<li> Write the OnGetRowData event handler with the following code:
 +
<tt>
 +
:::'''procedure''' TUrlHandlerDoGenerateTable.GetRowData(Sender: TObject;
 +
::::    '''const''' TableName: '''String;''' Row: Integer; TagData: TStringIndex;
 +
::::    '''var''' More: Boolean; UserData: TObject);
 +
:::'''begin'''
 +
::::    More := Assigned(FPasswords) and (Row <= FPasswords.Count);
 +
::::    '''if''' More '''then begin'''
 +
:::::        TagData.Add('Row',      IntToStr(Row));
 +
:::::        TagData.Add('Password', FPasswords[Row - 1]);
 +
::::    '''end;'''
 +
:::'''end;'''
 +
</tt>
 +
OnGetRowData event handler is called again and again by AnswerPage when a table_rows tag is found. Actually it is called once per data row. AnswerPage stops calling it when the var argument named "more" is set to FALSE. The main purpose of the handler is to call TagData.Add to specify values for the special tags within the table rows. Here we have defined two of them in our template file: "Row" and "Password". The event handler has also to set the var argument More according to the fact that we have more records. Here we set More according to the row index given by the argument "Row" compared to the password stringlist count.
 +
</li>
 +
<li> One more step is needed to free the stringlist holding all password. We do that in the destructor:
 +
<tt>
 +
:::'''destructor''' TUrlHandlerDoGenerateTable.Destroy;
 +
:::'''begin'''
 +
::::    FreeAndNil(FPasswords);
 +
::::    '''inherited;'''
 +
:::'''end;'''
 +
</tt>
 +
</li>
 +
<li>You can now hit F9 to compile and run the application. Then point your browser to http://127.0.0.1:20080/GenerateTable.html, enter a value and click the button. You you see the table built and filled with the number of passwords you requested.
 +
</ol>
 +
<br><br>
 +
 +
==== Summary ====
 +
In this lesson you learned how to created a template file having a table and to write code to feed the table rows with dynamic data. You know how to use <#table_rows> tag and how to write a OnGetRowData handler to fetch data for your table.

Revision as of 16:08, 30 April 2009

Description

This tutorial is about writing a dynamic web application using THttpAppSrv component you can find in ICS version 7.

A dynamic web application is one which not only deliver static pages and files, but also dynamic web page.

Dynamic Pages

A dynamic web page is build on a template html file having special tags such as "<#MyTagName>" which are replaced at runtime by content provided by the application. Usually the content is fetched from a database but can comes from any source. The only limit is the developper imagination !

The component has also special tags to handle tables. Not only html tables, but anything which need to be repeated such as a list content. Using those tags, the developer describe one table row in the template and at runtime, as many rows as needed are created. The developper has full control on the data in each row and of course the number of generated rows.


The tutorial

The tutorial is divided in lessons and each lessons in steps. You must do the lesson in the correct order since most lessons depends on the previous one. In other words, lessons are simply miles stones toward the final

The steps belows correspond to Delphi 2009. You'll have to adapt somewhat the steps according to the IDE you use. The tutorial is OK with all Delphi versions supported by ICS-V7. There could also be some minor differences according to the settings you use for your ide. I'm using undocked layout with floating VCL designer. I'm using a french localized version so the menu items and button captions I give are translated from french to english and could be slightly different than the original english one.

Obvioulsy, you must have ICS-V7 installed within the IDE. When you install ICS, you usually add <ics installdir>\Delphi\VC32 to the compiler search path. If don't do that, you must add that path to every project.

Lesson 1 : Hello World

During this lesson, you will learn how to write the simplest website. The website you'll build has only one static page displaying "Hello World".

Steps

  1. Create a new VCL forms application.
    Here in the demo we use a classic GUI application. For a real application, it is likely that you'll create a service application.
  2. Rename the form "TutWebAppServerMainForm"
  3. On the form just created, drop a THttpAppSrv component.
  4. Save the files you have just created. Menu / File / Save all. Use OverbyteIcsTutWebAppServerMain.pas instead of unit1.pas and OverbyteIcsTutWebAppServer.dproj instead of project1.dproj.
  5. Hit F9 to compile and run the projet. You should get no error and a blank form. If you have errors, then it is likely you have ICS incorrectly installed. Terminate the application.
  6. Select the THttpAppSrv you just dropped on a form. Hit F11 to show the oject inspector and set a few property values:
    Name HttpAppSrv1
    Port 20080
    DocDir c:\icstutorial\wwwroot
    TemplateDir c:\icstutorial\templates
    DefaultDoc index.html
  7. Create the document directory "c:\icstutorial\wwwroot" and the templates directory "c:\icstutorial\templates".
  8. Create our static html page: Menu / File / New / Others / Web document / HTML Page. Just enter the body text "Hello World" and save the file as "c:\icstutorial\wwwroot\index.html"
  9. Add a FormShow event handler to TutWebAppServerMainForm with the code:
    HttpAppSrv1.AddGetAllowedPath('/', afBeginBy);
    HttpAppSrv1.Start;
  10. Hit CTRL-SHIFT-S to save all.
  11. Hit F9 to compile and run the application. You still get an empty form, but actually you already have a web server running ! Depending on your OS and security settings, you may get a message asking you to block or allow the application. Be sure to allow it.
  12. Now enter the url: "http://127.0.0.1:20080" into your favorite browser. You should get your marvelous "Hello World" static page.


Summary

In this lesson you learned how to build the basic webserver to serve static page. Beside the static pages you need, there are only two Delphi code lines to write.

Lesson 2 : Hello Today

Let's add a dynamic webpage to our website. To make it simple, we will just display the current server date and time.

Steps

  1. Create the template for the dynamic web page: Menu / File / New / Others / web document / HTML Page.
  2. Write the text: "Server time is"
  3. Switch to the HTML code editor (ALT-PgUP or click "code" tab at bottom of the editor) to enter the special tag "<#DateTime>" where you want to have the date and time shown, that is just after "Server time is". You'll see that Delphi HTML editor add an ending tag we don't need. Just delete "</#DateTime>" Delphi has inserted.
  4. Save the template as "c:\icstutorial\templates\HelloToday.html"
  5. Now we must associate the processing required to build our dynamic webpage. This is done by creating a new class deriving from TUrlHandler. Menu / File / New / Other / Delphi Project / Delphi Files / Unit. Save this new unit as "OverbyteIcsTutWebAppServerHelloToday.pas". Enter this code into the unit:
    uses
    SysUtils, OverbyteIcsHttpAppServer;

    type
    TUrlHandlerHelloToday = class(TUrlHandler)
    public
    procedure Execute; override;
    end;

    implementation

    procedure TUrlHandlerHelloToday.Execute;
    begin
    AnswerPage(’’, ’’, 'HelloToday.html', nil, ['DateTime', DateTimeToStr(Now)]);
    Finish;
    end;
  6. Switch back to OverbyteIcsTutWebAppServerMain and add "OverbyteIcsTutWebAppServerHelloToday" to the uses clause, add the following line in front of the FormShow event handler:
    HttpAppSrv1.AddGetHandler('/HelloToday.html', TUrlHandlerHelloToday);
  7. Hit CTRL-SHIFT-S and F9 to save all, compile and run your application.
  8. Enter this URL into your browser: http://127.0.0.1:20080/HelloToday.html. You should see the web page as the template your designed with the tag "<#DateTime>" replaced by the date and time. Hit the refresh button several times to see the dynamic page in action: the time is the real time.


Summary

In this lesson you learned how to write a template for a dynamic page, link the dynamic page URL to a Delphi class, write a Delphi class implementing the processing required to get data and feed it to the template.


Lesson 3 : Simple data entry and processing

During this lesson, you will learn how to present a data entry form to the user, process entered data and produce a result page. For simplicity, we will use a simple form to enter two numbers, an operator and provide the result in another simple page. Later, in another lesson, we will use AJAX to update the current page with the computation result.

Steps

  1. Create a template HTML file for the calculator. Let's name that file SimpleCalculator.html and save it in the template folder we have located at "c:\icstutorial\templates". Menu / File / New / Others / Web documents / HTML page. Add the following HTML code as body:
    <form action="DoSimpleCalculator.html"
    method="get"
    enctype="application/x-www-form-urlencoded">
    Number 1 <input name="Number1">
    Operator <select name="Operator">
    <option>Add</option>
    <option>Multiply></option>
    </select>
    Number 2 <input name="Number2" type="TEXT">
    <input name="Submit" type="SUBMIT" value="Compute">
    </form>
  2. Create a new unit to hold the calculator code. Menu / File / New / Delphi Unit. Save the unit as "OverbyteIcsTutWebAppServerSimpleCalculator.pas".
  3. Create the class to deliver the calculator's form. Actually we could have done a static page as well. Since we made a template, we need to write a class and map the class to the URL. The class and his implementation are quite straigthforward:
    uses
    SysUtils, OverbyteIcsHttpAppServer, OverbyteIcsHttpSrv;
    type
    TUrlHandlerSimpleCaculator = class(TUrlHandler)
    public
    procedure Execute; override;
    end;
    implementation
    procedure TUrlHandlerSimpleCaculator.Execute;
    begin
    AnswerPage(’’, ’’, NO_CACHE, 'SimpleCalculator.html', nil, []);
    Finish;
    end;
  4. Mapping the class to the URL is done by calling AddGetHandler from TTutWebAppServerMainForm.FormShow. In order to reference the class, we must also add OverbyteIcsTutWebAppServerSimpleCalculator to the uses clause in OverbyteIcsTutWebAppServerMain.pas.
    HttpAppSrv1.AddGetHandler('/SimpleCalculator.html', TUrlHandlerSimpleCaculator);
  5. At this stage, you can save all (CTRL-SHIFT-S), compile and run (F9) the application. Then point your browser to http://127.0.0.1:20080/SimpleCalculator.html. You should see the HTML form. If you click the Compute button, you get a 404 error since we have not implemented the form action yet. Let's do it. Stop the program and execute the next steps.
  6. Create the class to implement the calculator operation. In the HTML form above, we named the action "DoSimpleCalculator.html", so we will name the class "TUrlHandlerDoSimpleCalculator". We write the class in OverbyteIcsTutWebAppServerSimpleCalculator.pas unit. The declaration looks like:
    TUrlHandlerDoSimpleCalculator = class(TUrlHandler)
    public
    procedure Execute; override;
    end;
  7. Now write the code for the execute procedure. In this code we have to get the values entered by the user thru the HTML form we have designed. There are 3 fields we named "Number1", "Number2" and "Operator". The form definition specify method "get" and encoding "application/x-www-form-urlencoded". Those values directly impact how we received the values in Delphi. The HTTP server component unit has a function "ExtractURLEncodedValue" designed to get such value. It takes 3 arguments. The first is the parameters sent by the browser, the second is the name of the field and the third is a reference to the string variable to hold the result. The function return a boolean telling if the named parameter is found or not.
    All in all we have to define 3 variable and call the method 3 times to get their values. Up to here, the code looks like:
    procedure TUrlHandlerDoSimpleCalculator.Execute;
    var
    N1 : String;
    N2 : String;
    Op : String;
    begin
    ExtractURLEncodedValue(Params, 'Number1', N1);
    ExtractURLEncodedValue(Params, 'Number2', N2);
    ExtractURLEncodedValue(Params, 'Operator', Op);
    end;
  8. Now we have to do the computation. Be aware the every parameter is returned as a string. We have to do convertion to be able to compute the result. To hold the result, add an integer variable and name it "R". Add the following lines to the Execute procedure:
    if SameText(Op, 'Add') then
    R := StrToInt(N1) + StrToInt(N2)
    else if SameText(Op, 'Multiply') then
    R := StrToInt(N1) * StrToInt(N2)
    else
    R := 0;
    For simplicity, we have not written any validation nor any exception handling. We are just demonstrating dynamic web design, not Delphi programmming best practices.
  9. To display the result, we have to prepare a HTML template and used it to send the answer page to the client's browser. Menu / File / New / Other / Web Document / HTML page. Name the file "SimpleCalculatorResult.html" and save it in the templates directory. As HTML body, write this HTML code:
    <#Operator>(<#Number1>, <#Number2>) = <#Result>
    <a href="SimpleCalculator.html">Continue</a>
    This HTML code make use of 4 special tags named "Number1", "Number2", "Operator" and "Result". Those tags will be replaced by the actual values we provide by code when calling AnswerPage. The final code for the Execute procedure looks like this:
    procedure TUrlHandlerDoSimpleCalculator.Execute;
    var
    N1 : String;
    N2 : String;
    Op : String;
    R : Integer;
    begin
    ExtractURLEncodedValue(Params, 'Number1', N1);
    ExtractURLEncodedValue(Params, 'Number2', N2);
    ExtractURLEncodedValue(Params, 'Operator', Op);
    if SameText(Op, 'Add') then
    R := StrToInt(N1) + StrToInt(N2)
    else if SameText(Op, 'Multiply') then
    R := StrToInt(N1) * StrToInt(N2)
    else
    R := 0;
    AnswerPage(’’, NO_CACHE, 'SimpleCalculatorResult.html', nil,
    ['Number1', N1, 'Number2', N2, 'Operator', Op, 'Result', R]);
    Finish;
    end;
    You can see that we used NO_CACHE constant in the call to AnswerPage. This constant instruct the client browser to not store the page into his cache. This is very important since it is a dynamic page. If the client navigator store the page in his cache, then the user would see an old version showing old results.
  10. Finally, we have to map our class to an URL. This is done by calling AddGetHandler from TTutWebAppServerMainForm.FormShow.
    HttpAppSrv1.AddGetHandler('/DoSimpleCalculator.html', TUrlHandlerDoSimpleCalculator);
  11. Hit F9 to compile and run the application. Point your browser to http://127.0.0.1:20080/SimpleCalculator.html. You can enter values and submit. You should get the expected result page.


Summary

In this lesson you learned how to get back data from a HTML form, do some processing with it and produce a result page. You also learned how to instruct the client navigator to not store an answer page to his cache.


Lesson 4 : Building tables

During this lesson, you will learn how to build a table with a variable number of rows containing dynamic data. In a typical application, a table is used to show the result of a database query (for example a list of items matching a given search criteria) or to build a list of items to fill a HTML combobox or anything else repetitive.

In this lesson, to avoid database stuff we have not yet learned, we will do something simple and yet useful: We will create a password generator. The user will be presented a form where he can enter the number of passwords he wants. The program will generated those passwords and present them in a nice HTML table.

Since we already learned how to handle HTML forms to enter data, I will quickly pass over those steps to come directly tho this lesson's objective: generate a table.

Steps

  1. Create the entry form to enter the number of passwords the user want to get. Save the form to c:\icstutorial\templates\GenerateTable.html. Create the code to handle the form and to get the value back. Save the code into a new unit named OverbyteIcsTutWebAppServerGenerateTable.pas. Name the two classes TUrlHandlerGenerateTable and TUrlHandlerDoGenerateTable. Map the two classes to the URL GenerateTable.html and DoGenerateTable.html. For detailed steps, please see lesson 2.
    The HTML form include the following code:
    <form action="DoGenerateTable.html"
    method="GET"
    enctype="application/x-www-form-urlencoded">
    Enter the number of passwords you want
    <input type="TEXT" name="PasswordCount" size="3">
    <input type="SUBMIT" value="Generate">
    </form>

    The unfinished new Delphi unit looks like:
    unit OverbyteIcsTutWebAppServerGenerateTable;

    interface

    uses
    SysUtils, OverbyteIcsHttpAppServer, OverbyteIcsHttpSrv;

    type
    TUrlHandlerGenerateTable = class(TUrlHandler)
    public
    procedure Execute; override;
    end;

    TUrlHandlerDoGenerateTable = class(TUrlHandler)
    public
    procedure Execute; override;
    end;

    implementation

    procedure TUrlHandlerGenerateTable.Execute;
    begin
    AnswerPage(’’, NO_CACHE, 'GenerateTable.html', nil, []);
    Finish;
    end;

    procedure TUrlHandlerDoGenerateTable.Execute;
    var
    PasswordCount : String;
    begin
    ExtractURLEncodedValue(Params, 'PasswordCount', PasswordCount);
    // Here comes the processing and the AnswerPage
    Finish;
    end;

    end.

    The two lines to be inserted in TTutWebAppServerMainForm.FormShow to map the URL are:
    HttpAppSrv1.AddGetHandler('/GenerateTable.html', TUrlHandlerGenerateTable);
    HttpAppSrv1.AddGetHandler('/DoGenerateTable.html', TUrlHandlerDoGenerateTable);
  2. Write the code to generate the requested number of passwords. Here we have two options: either generate all passwords before generating the actual table, or generate the passwords on the fly while generating each table row. Since the first option gives a code easier to understand, I will use it. The passwords will be generated in a TStringList which will be used later when generating each table row.
    Passwords will be generated using Delphi random generator. For simplicity, we will generate passwords containing uppercase and lowercase letters plus all ten digits. Passwords will have 12 characters.
    The code looks like:
    procedure TUrlHandlerDoGenerateTable.GeneratePasswords(PasswordCount: Integer);
    var
    Password : String;
    I : Integer;
    const
    PasswordLen = 12;
    PasswordChars : String = 'abcdefghijklmnopqrstuvwxyz' +
    'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
    '0123456789';
    begin
    RandSeed := GetTickCount;
    FPasswords := TStringList.Create;
    SetLength(Password, PasswordLen);
    while PasswordCount > 0 do begin
    for I := 1 to PasswordLen do
    Password[I] := PasswordChars[1 + Random(Length(PasswordChars))];
    FPasswords.Add(Password);
    Dec(PasswordCount);
    end;
    end;
  3. Now that we have a function to generate the passwords, we may use it into the simple procedure TUrlHandlerDoGenerateTable we wrote before:
    procedure TUrlHandlerDoGenerateTable.Execute;
    var
    PasswordCount : String;
    begin
    ExtractURLEncodedValue(Params, 'PasswordCount', PasswordCount);
    GeneratePasswords(StrToInt(PasswordCount));
    OnGetRowData := GetRowData;
    AnswerPage(’’, NO_CACHE, 'GeneratePasswordResult.html', nil, []);
    Finish;
    end;
    You can see here a new comer: OnGetRowData. This is a kind of event which will be called by AnswerPage whenever a table is found in the template file. A table is marked in a template file using the special tag <#table_rows>. The next step show how to use that tag.
  4. Create a new HTML template file for presenting the password list and name it GeneratePasswordResult.html. We will build a real HTML table here. There is nothing special about the HTML table except we only create one row and surround it with the special tags <#table_rows> and <#/table_rows>. When AnswerPage will find the first tag, it starts repeating the bloc until the second tag. In each iteration it call the OnGetRowData event handler to get the data and know if there are more rows. Here is how the HTML source code looks like in the body of the HTML file:
    <table>
    <tr><td>N°</td><td>Password</td></tr>
    <#table_rows PasswordTable>
    <tr><td><#Row></td><td><#Password></td></tr>
    <#/table_rows>
    </table>
    You see a parameter within <#table_rows>. It is used in OnGetRowData so that Delphi code knows which table is currently built because a single HTML template can have multiple tables and tables may be nested.
  5. Write the OnGetRowData event handler with the following code:
    procedure TUrlHandlerDoGenerateTable.GetRowData(Sender: TObject;
    const TableName: String; Row: Integer; TagData: TStringIndex;
    var More: Boolean; UserData: TObject);
    begin
    More := Assigned(FPasswords) and (Row <= FPasswords.Count);
    if More then begin
    TagData.Add('Row', IntToStr(Row));
    TagData.Add('Password', FPasswords[Row - 1]);
    end;
    end;
    OnGetRowData event handler is called again and again by AnswerPage when a table_rows tag is found. Actually it is called once per data row. AnswerPage stops calling it when the var argument named "more" is set to FALSE. The main purpose of the handler is to call TagData.Add to specify values for the special tags within the table rows. Here we have defined two of them in our template file: "Row" and "Password". The event handler has also to set the var argument More according to the fact that we have more records. Here we set More according to the row index given by the argument "Row" compared to the password stringlist count.
  6. One more step is needed to free the stringlist holding all password. We do that in the destructor:
    destructor TUrlHandlerDoGenerateTable.Destroy;
    begin
    FreeAndNil(FPasswords);
    inherited;
    end;
  7. You can now hit F9 to compile and run the application. Then point your browser to http://127.0.0.1:20080/GenerateTable.html, enter a value and click the button. You you see the table built and filled with the number of passwords you requested.



Summary

In this lesson you learned how to created a template file having a table and to write code to feed the table rows with dynamic data. You know how to use <#table_rows> tag and how to write a OnGetRowData handler to fetch data for your table.