Plasma GitLab Archive
Projects Blog Knowledge


[UP]
Introduction
 Architecture
 Dialogs and pages
 An Example
 More Examples
   
An Example

The task: Add two numbers

In this chapter I present a very simple web application, and explain it. The task is to let the user enter two numbers, and compute the result once the button is pressed.

You can find the complete solution for this example in the source distribution of WDialog. It consists of three files: index.cgi, index.ml, and index.ui. This example uses Objective Caml as scripting language (compiled ad-hoc); of course, this is not the optimal way to run a web application, but it is the best to learn WDialog as you can change the script, and it has an immediate effect without needing to recompile the program.

The file index.cgi is the part that launches the Objective Caml interpreter, and is of no special interest. The file index.ml is the application written in Objective Caml, and index.ui is the UI definition file in XML syntax.

The structure of the solution

This example consists of only one dialog object. This dialog object contains the three numbers (first_number, second_number, result), and because this triple is the only data unit we have to deal with there is no other dialog object. This is a rule of thumb: For every part of the user interaction model that is centred around one record of data you need a separate dialog object.

As already mentioned, the dialog object contains three numbers. This can be written as (index.ui):


<?xml version="1.0"?>

<!DOCTYPE ui:application
          PUBLIC "-//NPC//DTD WDIALOG 2.1//EN" "">

<ui:application start-dialog="add-numbers">

  <ui:dialog name="add-numbers" start-page="enter-numbers">

    <ui:variable name="first_number"/>
    <ui:variable name="second_number"/>
    <ui:variable name="result"/>

    <ui:page name="enter-numbers"> ... </ui:page>
    <ui:page name="display-result"> ... </ui:page>
    <ui:page name="display-error"> ... </ui:page>

  </ui:dialog>
</ui:application>

Furthermore, we have three pages, i.e. three ways to present these variables to the user:

  • enter-numbers: The user can enter the numbers to add, no result is displayed

  • display-result: The two entered numbers are displayed together with the sum.

  • display-error: If the user does not enter a syntactically correct number, an error will be displayed.

The start page is enter-numbers, i.e. this page will be displayed when the application is visited for the first time.

The ways the three variables are referenced are quite different. In the first page, input boxes prompt the user to enter something. WDialog allows it to bind input boxes to variables. The effect is that the boxes are prefilled with the current value of the variable when the page is displayed (sent to the web browser), and that any modifications of the contents of the boxes are passed back to the variables.

The page display-result simply includes the current values of the variables in the displayed HTML text.

The last page display-error is only about the contents of the variables without displaying them.

There is another aspect about the pages. The user can change from one page to another page, but of course only on predefined paths. The page transitions can be described in the UI definition file, and they can be programmed by hand. The page enter-numbers contains a button "Compute Result" normally directing the user to the second page, display-result. However, if the syntax of the numbers is wrong, the next page will exceptionally be display-error. As the first page transition is the expected one, it is recommended to describe it in the UI definition file ("goto" attribute, see below), and program the second possible transition manually.

There are further transitions from display-result and display-error back to the start page.

In the user interface, transitions are usually represented as buttons or links. WDialog allows it to give these elements names, and when they are pressed or clicked, events are triggered that are identified by these names.

So there are two levels: First, buttons trigger events, and second, events are processed and cause page transitions.

The details of the pages

Here is the first page, enter-numbers:


<ui:page name="enter-numbers">
  <html>
    <head>
      <title>The Ultimative Adder</title>
    </head>
    <body>
      <ui:form>
        <h1>The Ultimative Adder</h1>

        <p>Please enter the two numbers you want to add:</p>
        <p>
          <ui:text variable="first_number"/> +
          <ui:text variable="second_number"/> =
          <ui:button name="add" label="Compute Result" goto="display-result"/>
        </p>
      </ui:form>
    </body>
  </html>
</ui:page>

The special WDialog text boxes that are linked with the variables are created by <ui:text variable="name"/>. They are transformed to ordinary HTML text boxes (INPUT TYPE="TEXT") that are filled with the current values of the variables, and if the user changes the contents of the boxes they will transferred back to the variables.

The special WDialog buttons are created by <ui:button
.../>
. The button add triggers the event Button("add") when pressed, and the default action is to go to the page display-result (by using the goto attribute). Note that the action can be overridden by the web application.

The page display-result looks as follows:


<ui:page name="display-result">
  <html>
    <head>
      <title>The Ultimative Adder</title>
    </head>
    <body>
      <ui:form>
        <h1>The Ultimative Adder</h1>

        <p>The result is:</p>

        <p>
          <ui:dynamic variable="first_number"/> +
          <ui:dynamic variable="second_number"/> =
          <ui:dynamic variable="result"/>
        </p>

        <p>
          <ui:button name="back" label="Go back" goto="enter-numbers"/>
        </p>
      </ui:form>
    </body>
  </html>
</ui:page>

On this page, the contents of the variables are dynamically inserted into the generated text. The special element <ui:dynamic .../> causes the contents of the variable to be encoded correctly (by substituting meta characters like < by their corresponding entities, here &lt;), and printed instead of the element.

Finally, the error page is:


<ui:page name="display-error">
  <html>
    <head>
      <title>The Ultimative Adder</title>
    </head>
    <body>
      <ui:form>
        <h1>The Ultimative Adder</h1>

        <p>Sorry, one of your numbers is not a number. Please go
        back and correct your input.</p>

        <p>
          <ui:button name="back" label="Go back" goto="enter-numbers"/>
        </p>
      </ui:form>
    </body>
  </html>
</ui:page>

An important detail of all three pages is that the special WDialog interactors are placed within the special ui:form element. This is required. ui:form is transformed to an HTML FORM element containing a number of hidden fields that help WDialog managing the interactions.

Note that you can still use the HTML form element directly, but WDialog does not provide any support for this.

The application

The other part of the web application is the CGI program that actually adds the two numbers and sets the result variable. Because it is relatively short, we include here the whole index.ml file:


[01] open Wd_dialog
[02] open Wd_run_cgi
[03] open Wd_types
[04]
[05]
[06] class add_numbers universe name env =
[07] object (self)
[08]   inherit dialog universe name env
[09]
[10]  method prepare_page() =
[11]    (* This method is empty in this example *)
[12]    ()
[13]
[14]  method handle() =
[15]    (* Check which event has happened: *)
[16]    match self # event with
[17]      Button("add") ->
[18]        (* Get the numbers and convert them from string to int. Catch
[19]         * errors.
[20]         *)
[21]        let n_1, n_2 =
[22]          ( try
[23]              let s_1 = self # string_variable "first_number" in
[24]              let s_2 = self # string_variable "second_number" in
[25]              (int_of_string s_1, int_of_string s_2)
[26]            with
[27]              error ->
[28]                (* On error: Jump to the error page *)
[29]                raise(Change_page "display-error")
[30]          )
[31]        in
[32]        (* Add the numbers, and store the result into the variable *)
[33]        let r = n_1 + n_2 in
[34]        self # set_variable "result" (String_value (string_of_int r));
[35]
[36]    | _ ->
[37]        (* Do nothing if the event is not recognized *)
[38]        ()
[39] end
[40] ;;
[41]
[42]
[43] run
[44]   ~charset:`Enc_utf8
[45]   ~reg:(fun universe ->
[46]          (* Bind the XML dialog "add-numbers" to the class "add_numbers": *)
[47]          universe # register "add-numbers" (new add_numbers)
[48]        )
[49]   ()
[50] ;;

I have added line numbers to help readers who are not familiar with the language Objective Caml.

Lines 1-3 open the relevant modules of the WDialog library.

Lines 6-40 define the class add_numbers that inherits from the WDialog class dialog. By inheriting from this base class the class gets all the properties that are required to act as dialog. We will go into detail later.

The lines 43-50 are the main program. It calls the WDialog function run doing all the necessary steps. There are two configuration options: ~charset sets the character set that is internally used by WDialog and that will also be used to represent the generated HTML pages. The option ~reg configures the registry containing the bindings of dialogs to classes. Here we simply bind the ui:dialog called add-numbers to the Objective Caml class called add_numbers.

The instance of the option ~reg must be a function that gets the so-called universe as input. The universe consists of all defined dialogs and the corresponding classes, and acts as a factory for new dialog objects. Let u be the universe. To create a new object for the dialog "add-numbers", you can call u # create env "add-numbers" (the symbol # is the method invocation operator). Here, env is the environment, a record of data that are different for every invocation of the CGI; there is normally an environment at hand when you want to create an object. - The function passed to ~reg initializes the universe by telling WDialog which dialogs correspond to which classes (this needs not to be a one-to-one correspondence).

Now back to the definition of the class. The lines 6, 7, 8, and 39 form the outer "braces" defining the class:

class add_numbers universe name env =
object (self)
  inherit dialog universe name env
  ...
end
The symbols universe, name, and env are only passed to the super class dialog, we can ignore them now. The symbol self is the symbolic name of the current object (also called this in other languages; in Caml you are free to choose your preferred name).

As you can see, the class has two methods. prepare_page is called just before the dialog object outputs the next page (the preprocessing phase). It is normally used to set dialog variables that are only needed for visualization, but do not have any effect on the actions. For instance, if you wanted to display the current date at the top of the page, you could set another dialog variable date to the current date in this method. For this example, however, prepare_page is not needed, so we leave it empty.

The other method, handle, is called after the user has reacted on the page (for instance, pressed a button), and it is always called for the last object seen by the user. This method contains the post processing actions of a page.

Before handle is called, the WDialog processor has reconstructed the last dialog object. This is worth to be mentioned, because handle is called in the CGI activation following the activation that invoked prepare_page and that actually sent the page to the client. So it is usually called in a different Unix process. However, WDialog has managed it that the dialog object of the last activation is reanimated so it is accessible again (by object serialization).

The necessity of object serialization is another reason why we are using the special dialog variables that can be declared with the XML expression <ui:variable>. The values of these variables survive from one CGI activation to the next because they are part of the serialized objects; if we just declared ordinary Caml instance variables their values would be lost.

The body of handle first checks the last event. This is done using the pattern matching operator

match ... with pat1 -> expr1 | pat2 -> expr2 | ... 
The expression self#event returns a symbolic term describing the last event. Here, we are only interested in the case when the last event is Button("add"), i.e. the user has pressed the button labeled "Compute Result". In all other cases (the pattern _) we do nothing. These other cases include the events that the user pressed the "Go back" buttons. It is not necessary to do something when these buttons are hit because the goto attribute already sets an action, namely to go to the requested page.

From lines 21 to 31 we extract the current values from the dialog variable first_number and second_number. This is done in two steps:

let s_1 = self # string_variable "first_number" in
This line looks up the named dialog variable and binds their string contents to the symbol s_1. The next line performs the same for the other dialog variable, and defines s_2. The method string_variable is one of the methods inherited from the super class dialog. The strings are converted to integers (by using the function int_of_string). Of course, this may fail because the user did not type in a number. In this case, the Caml runtime system throws an exception which is caught by the try ... with... block. Normally, when the user entered a correct number, the comma in line 25 forms a pair of the two integers, and this pair is bound to the pair pattern in line 21. The result is that n_1 contains the first number as integer, and that n_2 contains the second number as integer.

Line 33 adds both numbers and calls the sum r. Finally, line 34 sets the dialog variable result to the string value that corresponds to the integer r. Here, the method set_variable is again one of the inherited methods.

Note that there is no statement requesting a certain page as the next page to display. We do not need that because the goto attribute of the button set the name of the next page, this attribute is still in effect.

Line 29 is only executed when the user did not enter valid numbers. In this case, an exception is thrown by the Caml runtime system, and it is caught in lines 26/27. What happens is that another, new exception is raised causing that the rest of the method definition is bypassed. Furthermore, this exception Change_page
"display-error"
overrides the effect of the goto attribute by requesting a different page. The effect is that the next displayed page is display-error, and not display-result.

See the example live

Under this URL the example is running.

Some observations

  • After you entered two numbers into the boxes, pressed the "Compute Result" button, and then went back, you see the two numbers again. This is because we do not change the contents of first_number and second_number by our program, so they keep their values. That means that these variables are not just CGI parameters, they have some "magic" causing that they are automatically passed from one page to the next and back to the previous one.

  • This works even if you enter invalid numbers. You will see the error message, but when you go back, the invalid values appear again.

  • You may ask from where the variables get their initial values. The initial values are implicitly defined by the <ui:variable> declarations, because a string variable defaults to the empty string. You can specify another initial value, for example

    <ui:variable name="first_number">
      <ui:string-value>42</ui:string-value>
    </ui:variable>
    
    declares that 42 is the intial value for first_number.
How to get this example running

  • Of course, you must have installed WDialog properly (see the INSTALL file coming with the distribution)

  • Now go into the directory examples/adder. You will find the three mentioned files here. Run

    ./index.cgi
    from the command line. A message appears explaining that this is a CGI program, and you will be prompted for parameters. Just enter . and press Enter:

    ice:/home/gerd/npc/uiobjects/examples/adder > ./index.cgi
    This is a CGI program. You can now input arguments, every argument on a new
    line in the format name=value. The request method is fixed to GET, and cannot
    be changed in this mode. Consider using the command-line for more options.
    > .
    (Continuing the program)
    Content-type: text/html; charset=UTF-8
    Cache-control: no-cache
    Pragma: no-cache
    Expires: Fri, 08 Feb 2002 01:01:37 +0000
    
    <html >
            <head >
              <title >The Ultimative Adder</title>
            </head>
            <body >
            ...
            </body>
          </html>
          ...
    
    The script outputs the start page. If you get it, everything is installed right.
  • Now you have to configure your web server (do you have one?) to run CGIs. This depends on your product (sorry).

  • Finally, you can enter the right URL into your browser, and the example will appear. Again, the "right URL" depends on your web server configuration.

  • For maximum speed, it is better to compile the program. Objective Caml has two compilers, one generating byte code, and the other generating assembly language code. Byte code has only medium speed, but it is platform-independent. Of course, the assembly language code is much faster. The two compilers have to be invoked as follows:

    ocamlfind ocamlc -o byte.cgi -package wdialog -linkpkg index.ml
    
    ocamlfind ocamlopt -o fast.cgi -package wdialog -linkpkg index.ml
    
    The UI definition must be named after the CGI, so you better create the symlinks:

    ln -s index.ui byte.ui
    
    ln -s index.ui fast.ui
    
    You do not need to recompile the program if you only change the UI definition.
This web site is published by Informatikbüro Gerd Stolpmann
Powered by Caml