menu
  Home  ==>  papers  ==>  web  ==>  delphi_web_designer   

Delphi Web Designer - Felix John COLIBRI.

  • abstract : a tiny Delphi "RAD Web Designer", which explains how the Delphi IDE can be used to generate .HTML pages using the Palette / Object Inspector / Form metaphor to layout the page content
  • key words : Delphi HTML Web site generator
  • software used : Windows XP Home, Delphi 6
  • hardware used : Pentium 2.800Mhz, 512 M memory, 140 G hard disc
  • scope : Delphi 6, Delphi 7, Delphi 8, Delphi 2005, Delphi 2006, Turbo Delphi for Windows, Delphi 2007
  • level : Delphi Web developer
  • plan :


1 - Delphi Web Designer

We will present a small HTML generator which uses the Delphi IDE to layout the different page elements.

The developer creates pages in the same way that he builds Windows application:

  • by selecting controls from the Palette
  • and droping them on a tForm
The tForm is the template of the future HTML page, and this tForm is analyzed to generate the .HTML page.

This is only a proof-of-concept application, which allows to understand how the .HTML generation part of tools like IntraWeb can work.




2 - The RAD HTML generator

2.1 - An .HTML target page

Let's assume that we want to generate the following simple .HTML page:

image

To produce .HTML pages, we must generate an .HTML file with a content similar to:

 
<HTML>
  <BODY>
    <FORM method="POST" ACTION="http://127.0.0.1/scripts/training.exe"><BR>
      <TABLE width="100%">
        <TR >
          <TD >
            <TABLE width="100%" bgcolor="#FFBBFF">
              <TR >
                <TD width="22%" valign=top>
                  <IMG SRC="pascal_institute_2.jpg">
                </TD>
                <TD >
                  <H3>Delphi Training Courses</H3>
                </TD>
              </TR>
            </TABLE>
          </TD>
        </TR>
        <TR >
          <TD >
            <TABLE width="100%" cellpadding=4>
              <TR >
                <TD width="23%" bgcolor="#FFBBFF" valign=top>
                  <A HREF="delphi_tutorial">Delphi Tutorial</A><BR>
                  <A HREF="dellphi_2006">Delphi 2006</A><BR>
                  <A HREF="interbase_client_server">Interbase C/S</A>
                </TD>
                <TD bgcolor="#FFFFFF">
                  The Pascal Institute organizes each month
                  Delphi Trainings. To get the list of all
                  available dates:<BR>
                   <UL>
                     <LI> Delphi Tutorial
                     <LI> Delphi 2006 for .Net
                     <LI> Interbase Client Server<BR>
                  </UL>
 
                  To get the full documentation, and the
                  calendar of the next sessions, please
                  enter your e-mail and
                  clic the "Submit" button:<BR>
                  <INPUT TYPE="text" NAME="Edit1" VALUE="my.mail@xxx"> <BR>
                  <INPUT TYPE="submit" NAME="Button1" VALUE="submit"> <BR>
                </TD>
              </TR>
            </TABLE>
          </TD>
        </TR>
        <TR >
          <TD >
            <TABLE width="100%" bgcolor="#FFBBFF">
              <TR >
                <TD width="23%" valign=top>
                </TD>
                <TD >
                    <A HREF="index">Felix Colibri Home</A><BR>
                    <A HREF="source_code">Articles</A> with
                    full Source Code<BR>
                </TD>
              </TR>
            </TABLE>
          </TD>
        </TR>
      </TABLE>
    </FORM>
  </BODY>
</HTML>



2.2 - The tControl to .HTML tag mapping

The above file contains 2 kind of elements
  • layout parts, which tell where the the content will show up in the page
  • content part such as text, images, buttons etc
In our .HTML file, we chose to use an .HTML TABLE to partition the display surface. Alternately, we could have used an .HTML DIV and XY pixels.

And for the content part, we chose to handle

  • free text with bullets and links
  • headers (titles)
  • images
  • Buttons and Input boxes


We decided to use the following mappping:
  • the page blocks will be defined by tPanels, as usual in a Delphi tForm
  • free text will be contained in tMemo, and in addition
    • bullets (1 level) will be minus characters, indented at the start of a line
    • links will be written between "<" and ">"
  • headers will be in tLabels
  • images are loaded from tImage
  • Buttons and Input boxes will be represented by tButtons and tEdits


2.3 - The Delphi template tForm

Using those conventions, we came up with the following tForm:

image



2.4 - The HTML generator

We use a descendent of our c_text_generator which places the result in a tStringList.

The generator receives the main tPanel as a parameter, and simply analyzes all tControls by looking into the tPanel.Controls array:

  • a first pass is used to look for the child tPanels, which allow to compute the TABLE cell width
  • the second pass extracts the non-tPanel children and generates .HTML code depending on the tControl type
With more details
  • the CLASS is defined by:

    c_rad_html_generator=
        class(c_html_generator)
          Constructor create_rad_html_generator(p_nameString);
          procedure generate_page(p_c_design_paneltPanel);
        end// c_rad_html_generator

  • the main method generates the <HTML>, <BODY> and <FORM> tags, and calls the tPanel TABLE generation procedure:

    procedure c_rad_html_generator.generate_page(p_c_design_paneltPanel);

      // -- ...ooo...

      begin // generate_page
        with p_c_design_panel do
        begin
          add_line_indent('<HTML>');
          add_line_indent('<BODY>');
          if ControlCount> 0
            then add_line_indent('<FORM method="POST" '
                + ' ACTION="http://127.0.0.1/scripts/training.exe"><BR>');

          generate_table_recursive(p_c_design_panel);

          if ControlCount> 0
            then unindent_add_line('</FORM>');
          unindent_add_line('</BODY>');
          unindent_add_line('</HTML>');
        end// with p_c_design_panel
      end// generate_page

  • the recursive TABLE generation procedure accumulates the tPanel parameters

    procedure generate_table_recursive(p_c_paneltPanel);
      var l_panel_countInteger;
          l_panel_top_arrayarray of Integer;
          l_tPanel_arrayarray of tPanel;
          l_panel_width_arrayarray of Integer;
          l_total_widthInteger;
          l_colorString;
          l_color_arrayarray of String;

      // -- ...ooo...

      var l_control_indexInteger;
          l_c_control_reftControl;

      begin // generate_table_recursive
        with p_c_panel do
        begin
          l_panel_count:= 0;
          l_total_width:= 0;

          for l_control_index:= 0 to ControlCount- 1 do
          begin
            l_c_control_ref:= Controls[l_control_index];

            if l_c_control_ref.ClassName'TPanel'
              then begin
                  Inc(l_panel_count);

                  SetLength(l_panel_top_arrayl_panel_count);
                  l_panel_top_array[l_panel_count- 1]:= l_c_control_ref.Top;

                  SetLength(l_panel_width_arrayl_panel_count);
                  l_panel_width_array[l_panel_count- 1]:= l_c_control_ref.Width;
                  Inc(l_total_widthl_c_control_ref.Width);

                  SetLength(l_tPanel_arrayl_panel_count);
                  l_tPanel_array[l_panel_count- 1]:= l_c_control_ref as tPanel;

                  SetLength(l_color_arrayl_panel_count);
                  l_color_array[l_panel_count- 1]:=
                      f_color((l_c_control_ref as tPanel).Color);
                end
          end// for l_control_index

          // -- if there is no panel, this is a "content surface"
          if l_panel_count= 0
            then generate_content
            else begin
                l_color:= f_color(Color);

                display(l_color);
                generate_the_table;
              end;
        end// with p_c_panel
      end// generate_table_recursive

  • the TABLE generation uses those tPanel informations to output the <TABLE>, <TR> and <TD> .HTML tags, with their appropriate attributes (mainly the width percentage when there are several tPanels with the same Top value):

    procedure generate_the_table;
      var l_panel_indexInteger;
      begin
        if l_panel_count= 1
          then begin
              add_table_start('border=2 cellpadding=3'l_color);
              add_row_start('');
              add_cell_start('');
              generate_table_recursive(l_tPanel_array[0]);
              add_cell_end;
              add_row_end;
            end
          else
            if l_panel_top_array[0]= l_panel_top_array[1]
              then begin
                  // -- horizontal
                  add_table_start('border=2 cellpadding=3 width="100%"'l_color);
                  add_row_start('');
                  for l_panel_index:= 0 to l_panel_count- 1 do
                  begin
                    if l_panel_indexl_panel_count- 1
                      then add_cell_start('width="'
                          + IntToStr(Round((100* l_panel_width_array[l_panel_index])
                                / l_total_width))+ '%"'
                          + l_color_array[l_panel_index]+ ' valign=top')
                      else add_cell_start('');
                    generate_table_recursive(l_tPanel_array[l_panel_index]);
                    add_cell_end;
                  end// for l_panel_index
                  add_row_end;
                end // horizontal
              else begin
                  // -- vertical
                  add_table_start('border=2 cellpadding=3'l_color);
                  for l_panel_index:= 0 to l_panel_count- 1 do
                  begin
                    add_row_start('');
                    add_cell_start('');
                    generate_table_recursive(l_tPanel_array[l_panel_index]);
                    add_cell_end;
                    add_row_end;
                  end// for l_panel_index
                end// vertical
        add_table_end;
      end// generate_table

  • the content is created by testing the controls:

    procedure generate_content;

      procedure generate_header(p_c_labeltLabel);
        begin
          with p_c_label do
            if Font.Height< -11
              then add_line('<H3>'Caption'</H3>')
              else add_line(Caption);
        end// generate_header

      procedure generate_memo(p_memo_textString);
        begin
          // -- ... ooo...
        end// generate_memo

      procedure generate_image(p_c_imagetImage);
        var l_file_nameString;
            l_indexInteger;
        begin
          with p_c_image do
          begin
            l_file_name:= Name;
            l_index:= Length(l_file_name);
            while (l_index> 1) and (l_file_name[l_index]<> '_'do
              Dec(l_index);
            l_file_name[l_index]:= '.';

            add_line('<IMG SRC="'f_exe_pathl_file_name'">');
          end;
        end// generate_image

      procedure generate_input_box(p_c_edittEdit);
        begin
          with p_c_edit do
            add_line('<INPUT TYPE="text" NAME="'Name'" VALUE="'
                + Text'"> <BR>');
        end// generate_input_box

      procedure generate_submit_button(p_c_buttontButton);
        begin
          with p_c_button do
            add_line('<INPUT TYPE="submit" NAME="'Name'" VALUE="'
                + Caption'"> <BR>');
        end// generate_submit_button

      var l_control_indexInteger;
          l_c_control_reftControl;

      begin // generate_content
        with p_c_panel do
          for l_control_index:= 0 to ControlCount- 1 do
          begin
            l_c_control_ref:= Controls[l_control_index];
            if l_c_control_ref.ClassName'TMemo'
              then generate_memo(tMemo(l_c_control_ref).Textelse
            if l_c_control_ref.ClassName'TLabel'
              then generate_header(tLabel(l_c_control_ref)) else
            if l_c_control_ref.ClassName'TImage'
              then generate_image(tImage(l_c_control_ref)) else
            if l_c_control_ref.ClassName'TEdit'
              then generate_input_box(tEdit(l_c_control_ref)) else
            if l_c_control_ref.ClassName'TButton'
              then generate_submit_button(tButton(l_c_control_ref)) else
          end// for l_control_index
      end// generate_content

    with the slightly more elaborate text generator:

    procedure generate_memo(p_memo_textString);
      var l_indexl_lengthInteger;
          l_resultl_anchorString;

          l_has_bulletBoolean;
          l_at_start_of_paragraphBoolean;
      begin
        l_result:= '';
        l_anchor:= '';
        l_length:= Length(p_memo_text);

        l_has_bullet:= False;
        l_at_start_of_paragraph:= True;

        for l_index:= 1 to l_length do
        begin
          case p_memo_text[l_indexof
             '<' : begin
                     if (l_indexl_lengthand (p_memo_text[l_index+ 1]<> '>')
                       then l_anchor:= '<A HREF="'
                       else l_anchor:= '</A';
                   end;
             '>' : begin
                     if l_anchor[2]= 'A'
                       then l_anchor:= l_anchor'">'
                       else l_anchor:= l_anchor'>';
                     l_result:= l_resultl_anchor;
                     l_anchor:= '';
                   end;
            '|' : begin
                    add_line(l_result'<BR>');
                    l_result:= '';
                    if l_has_bullet
                        and (l_indexl_lengthand (p_memo_text[l_index+ 1]<> ' ')
                      then begin
                          l_result:= l_result'</UL>'k_new_line;
                          l_has_bullet:= False;
                        end;
                    l_at_start_of_paragraph:= True;
                  end;
            '-' : begin
                    if l_at_start_of_paragraph
                      then begin
                          if not l_has_bullet
                            then begin
                                l_result:= l_result'<UL>'k_new_line;
                                l_has_bullet:= True;
                              end;
                          l_result:= l_result'<LI>';
                        end
                      else l_result:= l_resultp_memo_text[l_index];
                  end;
            chr(13), chr(10) :
                begin
                  if l_has_bullet
                    then l_at_start_of_paragraph:= True;
                end// l_start_of_line:= '';

            else
              if l_anchor<> ''
                then l_anchor:= l_anchorp_memo_text[l_index]
                else begin
                    l_result:= l_resultp_memo_text[l_index];
                    if p_memo_text[l_index]<> ' '
                      then l_at_start_of_paragraph:= False;
                  end;
          end// case l_index
        end// for l_index

        if l_result<> ''
          then add_line(l_result);
      end// generate_memo




2.5 - The Result

After generating the .HTML, we load it back into a tWebBrowser component, and this is the result:

image




3 - Building Web Sites with Delphi

3.1 - Improving the designer

The presented project is just a token designer. Many, many improvements would be required to make it somehow usable. To name a few:
  • the tMemo is not well suited for entering .HTML text, because of the way Returns are handled: the Lines property editor has a fixed size, and when we close this editor, the line breaks are set by the tMemo width. So we hacked our way around this by using "|" as the "<BR>" .HTML marker
    In fact we should write some kind of .HTML editor, with all the color, mode (bold, italic, size) features
  • the use of tPanels to set the page proportions is also quite akward. We should use a tStringGrid kind of editor, which would the user resize the rows and colums sizes
  • if we take the component route, with property editors, we could also place the .HTML generation into a component editor, and load the result in the tWebBrowser at design time
In addition:
  • the experiment also demonstrates that building web pages requires an elementary knowledge of .HMTL: what the basic tags are, how the browser formats the lines etc
  • an .HTML designer requires
    • either a complex framework with lots of dedicated components
    • or many conventions with a matching transformer
    For instance, to display bold characters you can have a special "bold" speedbutton which will be used by the text editor to add the bold text, or some special maker which will surround some text pieces which will be transformed as "<B>" and "</B>" tags by the transformer
  • We used the tWebBrowser component. Another route would have been to launch Internet Explorer, or event let the developer click on the .HTML file which will in turn start Internet Explorer.
  • the use of the Delphi IDE to layout text is also at the heart of many reporting tools, like Quick Report: the developer drops some specialized components on the tForm, and an analyzer produces the result using the components layout


3.2 - The different Products

There are many other way to build Web sites with Delphi. To name a few:
  • non-RAD approaches:
    • CGI or ISAPI helper libraries (hRef or similar)
    • WebBroker and DataSnap, which are part of Delphi
  • RAD building tools
    • Intraweb from AtoZed
    • Internet Express Web (IEW) from Developper Express
    • on the Microsoft front, there are .ASP and .ASP .NET


Here are a couple of links with comparisons of the IntraWeb, Developer Express and Asp.Net approaches, with additional references:

3.3 - Our Site Building Framework

Note that we use a different approach to create our pages. Our site mainly contains static text pages with a couple of images. So a text editor is more appropriate than a "component layouter".

Therefore:

  • we simply write pages with our custom Ed editor. This is a simple text editor like Notepad, and writing the text is lightning fast. And in addition to the content that you see, each page also contains
    • markers to incorporate .PNG images
    • simple typographic tags to delimit .HTML links or headers, add bullets or other typographic features
  • the .ED file is placed in the appropriate folder. The disc organization exactly mirrors the hierarchical site path organization
  • we then use another Delphi application, SiteEditor to transform each .ED editor file into an .HTML file.
    The navigation links are built according to the disk pathes
    The generator also adds the purple outer region which contains:
    • the top location link (where you are in the page tree)
    • the left-side indented menu
    • the bottom addresses


By the way, the color surrounding our pages is not "pink", as some of you wrote, but "purple". Purple is the color of the cardinals (catholic bishops), and Blaise PASCAL

image

the French philosopher was associated with the Jansenist catholic movement. But PASCAL was also a brilliant mathematician, and this is why the Zurich scientist and Swiss Federal Institute of Technology Professor Niklaus WIRTH

image

named one of his languages "Pascal".

While we are at it, I can't resist to tell this other story: in 1980, I subscribed to the "Call Apple" magazine. One day, some reader enquired where this "Pascal" name came from. There was no Wikipedia in those times. So, to oblige, another learned reader nicely explained: "Pascal was a Swiss Monk" ...




4 - Download the Sources

Here are the source code files:

The .ZIP file(s) contain:

  • the main program (.DPR, .DOF, .RES), the main form (.PAS, .DFM), and any other auxiliary form
  • any .TXT for parameters, samples, test data
  • all units (.PAS) for units
Those .ZIP
  • are self-contained: you will not need any other product (unless expressly mentioned).
  • for Delphi 6 projects, can be used from any folder (the pathes are RELATIVE)
  • will not modify your PC in any way beyond the path where you placed the .ZIP (no registry changes, no path creation etc).
To use the .ZIP:
  • create or select any folder of your choice
  • unzip the downloaded file
  • using Delphi, compile and execute
To remove the .ZIP simply delete the folder.

The Pascal code uses the Alsacian notation, which prefixes identifier by program area: K_onstant, T_ype, G_lobal, L_ocal, P_arametre, F_unction, C_lasse etc. This notation is presented in the Alsacian Notation paper.



As usual:

  • please tell us at fcolibri@felix-colibri.com if you found some errors, mistakes, bugs, broken links or had some problem downloading the file. Resulting corrections will be helpful for other readers
  • we welcome any comment, criticism, enhancement, other sources or reference suggestion. Just send an e-mail to fcolibri@felix-colibri.com.
  • or more simply, enter your (anonymous or with your e-mail if you want an answer) comments below and clic the "send" button
    Name :
    E-mail :
    Comments * :
     

  • and if you liked this article, talk about this site to your fellow developpers, add a link to your links page ou mention our articles in your blog or newsgroup posts when relevant. That's the way we operate: the more traffic and Google references we get, the more articles we will write.



5 - The author

Felix John COLIBRI works at the Pascal Institute. Starting with Pascal in 1979, he then became involved with Object Oriented Programming, Delphi, Sql, Tcp/Ip, Html, UML. Currently, he is mainly active in the area of custom software development (new projects, maintenance, audits, BDE migration, Delphi Xe_n migrations, refactoring), Delphi Consulting and Delph training. His web site features tutorials, technical papers about programming with full downloadable source code, and the description and calendar of forthcoming Delphi, FireBird, Tcp/IP, Web Services, OOP  /  UML, Design Patterns, Unit Testing training sessions.
Created: nov-04. Last updated: jul-15 - 98 articles, 131 .ZIP sources, 1012 figures
Copyright © Felix J. Colibri   http://www.felix-colibri.com 2004 - 2015. All rigths reserved
Back:    Home  Papers  Training  Delphi developments  Links  Download
the Pascal Institute

Felix J COLIBRI

+ Home
  + articles_with_sources
    + database
    + web_internet_sockets
      – tcp_ip_sniffer
      – socket_programming
      – socket_architecture
      – simple_web_server
      – simple_cgi_web_server
      – cgi_database_browser
      – whois
      – web_downloader
      – web_spider
      – rss_reader
      – news_message_tree
      – indy_news_reader
      – delphi_web_designer
      – intraweb_architecture
      – ajax_tutorial
      – bayesian_spam_filter
      + asp_net
    + oop_components
    + uml_design_patterns
    + debug_and_test
    + graphic
    + controls
    + colibri_utilities
    + colibri_helpers
    + delphi
    + firemonkey
    + compilers
  + delphi_training
  + delphi_developments
  + sweet_home
  – download_zip_sources
  + links
Contacts
Site Map
– search :

RSS feed  
Blog