Shiv Kumar
 Hobbyist Filmmaker / Editor

Best Practice - Using the TPageProducer Component

Not Rated YetNot Rated YetNot Rated YetNot Rated YetNot Rated Yet0votes
January 08, 2008 02:40 PM  Views: 1571   Favorited: 0 Favorite It Comments: 0
Filed Under:  Programming
Tags:  Delphi, ISAPI
 
In my experience with Delphi's WebBroker framework since Delphi 3, I've found the TPageProducer to be the single most useful component (out of the box). I have put this component to many uses in many different applications. Over a period of time, I've come up with a set of rules that define the ways in which I use this component. In this article, I'd like to show you, what I think is the best way to use this component.

Fixing A Minor Flaw

Before we look at the "best practice" use of this component, I'd like to highlight a flaw (in my opinion) in the component. When the component encounters a tag that you don't process (in the OnHTMLTag Event), it "swallows" this tag. This behavior prevents you from being able to chain page producer one after the next (which by the way is a good use of this component). If you're not familiar with the use of the TPageProducer, I suggest reading the tutorial titled - Using Cookies Hidden Fields and the TPageProducer.

To circumvent this issue in the OnHTMLTag event of your PageProducer, you could have code like this:

Circumventing the "flaw" in the TPageProducer
procedure TForm1.PageProducer1HTMLTag(Sender: TObject; Tag: TTag;
  const TagString: String; TagParams: TStrings; var ReplaceText: String);
begin
  if AnsiCompareText(TagString, 'sometag') = 0 then
    ReplaceText := 'Something'
  else
  begin
    if TagParams.Text = '' then
      ReplaceText := Format('<#%s>', [TagString])
    else
      ReplaceText := Format('<#%s %s>', [TagString, TagParams.Text]);
  end;
end;

exclamation.gif Rule No:1 HTML should not be mixed in with code.
In real world Applications, I don't like to have HTML mixed with code in my ISAPI applications. This "rule" has a number of benefits:
  1. Makes the code cleaner and therefore more maintainable/manageable.
  2. Speeds up the application quite a bit. (Due primarily to the intricacies related to string handling in 32 bit Delphi)
  3. Allows the use of HTML designers (designers being either tools or people) to design the "templates", while you the programmer can code the business rules. (yea right if only we were that lucky!)
  4. Allows for changing the page layout of your web sites/applications without the need to re-compile the application.

The TPageProducer has a property called HTMLFile. You should use this property to load up static html templates at run time. If you're having problems loading up files/images at run time due to "path" issues, I suggest reading the article titled - Relative/Virtual Paths Explained.

Defining a Language

I won't go into HTML/JavaScript specific things here, but rather talk about how templates should be designed from the perspective of using a TPageProducer to "process" templates.

exclamation.gif Rule No:2 Define a minimal "tag language" that you use over and over again across applications.
This rule goes a very long way in:
  1. Streamlining your templates
  2. Making your templates more readable/maintainable/configurable
  3. When working in a team where there are HTML designers (people) they start to understand your "language" and can start to apply it in templates they build for you.

So what is this language about? It's simple really. It's tags that you "re-use". Tags that have "attributes" that when set control the "behavior" of the processing. Kind of like an object that has properties and setting different properties change the behavior slightly. The more generic you make your tags, the more reusable they become.

In the process of building web applications, there are quite a few things we seem to repeat. For example:
  1. We use Request.ScriptName wherever we need the "path" of our application to be included in things like hyperlinks, form actions and the like.
  2. The action attribute of forms.
  3. The target attribute of forms.
  4. The src attribute of images.
For such needs I suggest you use tags like: <#scriptname>

All your page producers should "understand" this tag (Or at least one PageProducer in your application should). In the OnHTML event you could have code like the following:

If AnsiCompareText(TagString, 'scriptname') = 0 then
  ReplaceText := Request.Scriptname;

Similarly for the "form" tag <#form action="" target="" method="">

if AnsiCompareText(TagString, 'form') = 0 then
  ReplaceText := Format('<form action="%s" target="%s" method="%s" />', [
    TagParams.Values['action'], TagParams.Values['target'], TagParams.Values['method']]);

This kind of tag gives you the flexibility to define attributes in your template while also giving you the flexibility to change them later. Granted, if you're using templates, you could simply change them there (the hard coded value of a form action say). But believe you me a time will come, when you wished you used tags in the template! For example, you might need to change either the target or the action of the form depending on some condition. Instead of having two similar templates (that need to be synchronized each time there is a change) you can have just one.

exclamation.gif Rule No: 3 - Get used to coding your html in xhtml style. That is all tags and attributes in lowercase, all attribute values within double quotes and all empty tags closed.

Designing Templates

As our first exercise we'll look at some design aspects related to templates for HTML Forms. This form may contain various "controls" to allow for data entry. I propose the following:
  1. All <input> elements will have one and only one tag.
  2. All <select> elements will have one and only one tag.

Of course one could go a step further and say that we need have only one tag for ALL controls. This is the kind of thing I do personally in fact. But I have a component that does this for me (and a lot more). But for this discussion we'll stick to the 2 proposed above.

We won't be talking about the html aspects of the design here (the look and feel). We'll only look at the tags involved. The tags shown below can satisfy our needs for any type of &lg;input> tag we need in our forms:

<#input type="etText" name="foo" value="" maxlength="10" />
<#input type="etPassword" name="foo" value="" maxlength="10" />
<#input type="etHidden" name="foo" value="" maxlength="0" />
<#input type="etCheckBox" name="foo" value="" caption="bar" checked="false" />
<#input type="etRadioButton" name="foo" value="" caption="bar" checked="false" />

It goes without saying that this is only one tag. As a result, we can process these tags using one conditional statement in our code. It's the attributes that make all the difference. The code that processes these tags might look like this:

Sample code to process the proposed Tag Language.
type
  TMsHTMLElementType = (etText, etPassword, etHidden, etComboBox, etLookUpCombo,
    etMemo, etCheckBox, etRadioButton, etButton, etLabel);
...
...
...
...

implementation

{$R *.dfm}

uses TypInfo;

procedure TForm1.PageProducer1HTMLTag(Sender: TObject; Tag: TTag;
  const TagString: String; TagParams: TStrings; var ReplaceText: String);
const
  sHTMLTextInput = '<input type="%s" name="%s" value="%s" maxlength="%s" />';
  sHTMLCheckRadio = '<input type="%s" name="%s" value="%s" maxlength="%s"%s />nbsp;%s';
var
  sChecked: string;
  sRadioOrCheck: string;
begin
  If AnsiCompareText(TagString, 'input') = 0 then
    case TMsHTMLElementType(GetEnumValue(TypeInfo(TMsHTMLElementType), TagParams.Values['type'])) of
      etText    : ReplaceText := Format(sHTMLTextInput, ['text', TagParams.Values['name'],
                    TagParams.Values['value'], TagParams.Values['maxlength']]);
      etPassword: ReplaceText := Format(sHTMLTextInput, ['password', TagParams.Values['name'],
                    TagParams.Values['value'], TagParams.Values['maxlength']]);
      etHidden  : ReplaceText := Format(sHTMLTextInput, ['hidden', TagParams.Values['name'],
                    TagParams.Values['value'], TagParams.Values['maxlength']]);
      etCheckbox,
      etRadioButton:
        begin
          if AnsiCompareText(TagParams.Values['type'], 'etCheckBox') = 0 then
            sRadioOrCheck := 'checkbox'
          else
            sRadioOrCheck := 'radio';
          if AnsiCompareText(TagParams.Values['checked'], 'true') = 0 then
            ReplaceText := Format(sHTMLCheckRadio, [sRadioOrCheck, TagParams.Values['name'],
              TagParams.Values['value'], TagParams.Values['maxlength'], ' checked="checked"', TagParams.Values['caption']])
          else
            ReplaceText := Format(sHTMLCheckRadio, [sRadioOrCheck, TagParams.Values['name'],
              TagParams.Values['value'], TagParams.Values['maxlength'], '', TagParams.Values['caption']])
        end;
    end;

end;


Similarly, you could define a tag that (depending) on an attribute can become either a Combo Box or a List Box. Better yet, if it's a list box, whether it's a multi-select list box or a normal list box. You can then go on to define a "data aware" combo box tag that will allow you to define the stored procedure to use to populate it, the "name" field, then "value" field and so on.

Sooner or later you're going to want to build a specialized descendant component that does all of this for you thus allowing you to reuse your vocabulary. That's what object oriented programming is about. So don't let anyone stop you!

One could go in all sorts of directions with this concept. For instance, you could define a template that has a special tag like so:

<#embedtemplate filename="">

buried deep inside it somewhere. The filename attribute is the name of another template, a template for an html form for instance. That way, the same basic template can be used over and over throughout your system. You could even determine the embedded template to use at run time. Or the template to use is determined by the value of a parameter in the URL or content fields of a posted form. It's up to you really. But where ever you do, make sure the concept is re-usable and is generic enough to be reusable. Give a lot of thought to naming your tags and the attributes they can/should have.

A sample Template that allows for embeding other templates.

<html>
  <head>
    <link rel="stylesheet" type="text/css" href="<#statevar name="StyleSheet">
    <title><#title></title>
    <script>
    <#script>
    </script>
  </head>
<#body>
  <table border="0" cellpadding="0" cellspacing="0" width="100%" align="" valign="">
    <tr>
      <td colspan="2">Page Header goes here</td>
    </tr>
    <tr>
      <td><#embedtemplate id="1" name="LoginForm"></td><td><#childtemplate></td>
    </tr>
    <tr>
      <td colspan="2">Page Footer goes here</td>
    </tr>
  </table>
</body>
</html>

A Template that Shows Database information

This kind of thing is really simple to do. Depending on your template, you should generally be able to "fill in the blanks" using one line of code.

Take a look at the template below:

A simple Template that can show information from a database. (single record)

<table>
  <tr class="tableheaderrow">
    <td class="tableheadercell" align="center" style="font-size: smaller;">Customer ID</td>
    <td class="tableheadercell" align="center" style="font-size: smaller;">Company Name</td>
    <td class="tableheadercell" align="center" style="font-size: smaller;">Address</td>
  </tr>
  <tr class="tablerowrow">
    <td class="tablerowcell" align="center" style="font-size: smaller;"><#fieldvalue fieldname="CUST_ID "></td>
    <td class="tablerowcell" align="center" style="font-size: smaller;"><#fieldvalue fieldname="COMPANY_NAME"></td>
    <td class="tablerowcell" align="center" style="font-size: smaller;"><#fieldvalue fieldname="ADDRESS "></td>
  </tr>
</table>

Given a dataset that holds a record, the code to process this template might look like this:

  if AnsiCompareText(TagString, 'fieldvalue') = 0 then
    ReplaceText := MyDataSet.FieldByName(TagParams.Values['fieldname']).AsString;

The next thing that comes to mind is; "Ok, now how do we do this for a record set that contains a number of records?". This is probably a more common need than the previous one. So let's explore this case a bit. The solution is to break up your templates in "snippets".

  1. A Main "Table" template
  2. A Header Template
  3. A Row Template
  4. A Footer Template

The Main "Table" template essentially, has "place holders" for the other templates. This template might look like this:

The Main "Table" template used for showing Multiple Records from a Dataset

<table border="0" cellpadding="0" cellspacing="0" width="100%" align="" valign="">
  <tr>
    <#tableheadertemplate name="SoAndSoTableHeaderTemplate">
  </tr>
  <#tablerowtemplate name="SoAndSoTableRowTemplate">
</table>

Notice that the "insertion" point where "Rows" are inserted does not have the <tr> tags around it. Unlike the tag for the header.

Using the earlier templates as a starting point, the header template would look like this:

A Table Header Template.

    <td class="tableheadercell" align="center" style="font-size: smaller;">Customer ID</td>
    <td class="tableheadercell" align="center" style="font-size: smaller;">Company Name</td>
    <td class="tableheadercell" align="center" style="font-size: smaller;">Address</td>

The Row Template would look like this:

A Table "Row" Template. Make sure you include the <tr> tags.

  <tr class="tablerowrow">
    <td class="tablerowcell" align="center" style="font-size: smaller;"><#fieldvalue fieldname="CUST_ID "></td>
    <td class="tablerowcell" align="center" style="font-size: smaller;"><#fieldvalue fieldname="COMPANY_NAME"></td>
    <td class="tablerowcell" align="center" style="font-size: smaller;"><#fieldvalue fieldname="ADDRESS "></td>
  </tr>

Notice that the "row" template has the <tr> tags, while the "header" template does not.

I hope this article has shown you how best to utilize the power of the TPageProducer component.

Comments have been Disabled for this post





Leave A Comment

Shiv Kumar
Gainesville, Virginia,
United States
Member Bio Member Skills/Specialization

Bio

close

Specializations

close
Photographer
Landscape
Nature
Portrait
Videographer/Cinematographer
Interview
Landscape
Nature
Portrait
 
Privacy Policy | Terms Of Service | Contact Us | Support | Help/FAQ | News