Example: Weekly Time Report

In this example we will create a form that allows entry of a simple weekly time report, where one reports how many hours were spent on each of one's projects on a daily basis. This is what it might look like:

Defining the Form Structure

First, we define block report with 2 text properties and 4 buttons. This block will only contain one record - namely the time report we are editing. This is the simplest way of defining a block - all properties will be displayed using the default representation (display in the terminology of B-Forms).

We also begin defining some of the layout information, by creating two layouts and adding a special placeholder to the report block. This placeholder will display block record using layout $tl within layout $bl.

// Define the form structure
$form = new Form("denied.html", TRUE);

// Prepare layouts
$bl = & new BaseLayout();
$tl = & new TableLayout();

// Single row block "report": properties
$block = & new Block("report");
$block -> add_property(new TextProperty("name", "Employee Name",
                                        "", TRUE, 64));
$block -> add_property(new DateProperty("start_date", "Week Start Date",
                                        "", TRUE, TRUE));
// report: a layout placeholder for the records
$block -> add_property(new LayoutElement(),
                       new InlineBlock(&$bl, "record", &$tl));
// report: the actions
$block -> add_property(new ButtonProperty("add", "Add Record"));
$block -> add_property(new ButtonProperty("save", "Save", TRUE));
$block -> add_property(new ButtonProperty("submit", "Submit"));
$block -> add_property(new ButtonProperty("cancel", "Cancel"));
$form -> add_block($block);

The second block is trickier: it will contain several rows, we will generate its columns for days of the week in a loop, and we will not use the default displays:

//Multi-row block "record" for holding project records
$days = array( 0=>"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun");
$block = & new Block("record", TRUE);
$block -> add_property(new TextProperty("project_name", "Project Name",
                                        "", TRUE, 64),
                       new TextBox(30));
$block -> add_property(new ButtonProperty("delete", "Delete"));
for ($i=0; $i<7; $i++) {
    $block -> add_property(new NumericProperty("hours_$i", $days[$i],
                                               "", FALSE, 2, 1));
$form -> add_block($block);

Defining the Form Behaviour

First we will define the triggers, that are supposed to load the data. It could be done in one form-level trigger form_on_open() or it can be done with two triggers, one for each block, like it is done below. The triggers will execute in the order the blocks were added to the form.

Note that while fields on the first block are addressed as $form->report->name, fields on the multi-row block record are addressed as arrays: $form->record->project_name[$j].

// This trigger loads the time report itself into the $form->report
// block. A real implementation should read it from the database,
// but we will just hardcode values for simplicity.

function report_on_open() {
   global $form;

      // RS_OLD means that the block will contain
      // an existing record, rather than a new one.

   $form->report->name = "John Smith";
   $form->report->start_date = "2004-09-06";


// This trigger loads the project records that are part of the
// time report loaded in the report_on_open() trigger.

function record_on_open() {
   global $form;

   // For the purpose of this example, we will generate
   // dummy records. In reality, this code should retreive
   // the actual values for the time report, that is
   // contained in $form->report block.

   for ($j=0; $j<6; $j++) {
      $form->record->project_name[$j] = "Project ".($j+1);
      $form->record->id[$j] = $j+1;
      // And we will skip the hours for now

And second, we define the actions that should be taken when the user presses one of the buttons. Note, how easy it is to add a new record or to delete one!

Of course, when you save data you access the form data in the same way we initially assigned it in the ON_OPEN triggers.

// This trigger is called by pressing the "Add Record" button.
// It simply adds a new record to block $form->record and returns.
// Returning from a trigger causes the form to be redisplayed.

function report_add_on_action($rownum = -1) {
   global $form;


// This trigger is called by pressing the "Delete" button next to the
// project field. It marks the record, where the button was pressed for
// deletion. The actual deletion only happens when the form is saved.
// Returning from a trigger causes the form to be redisplayed, but the
// marked record will not be displayed.

function record_delete_on_action($rownum) {
   global $form;


// Cancelling the form is also simple (like it should be) -
// just redirect the browser and exit.

function report_cancel_on_action($rownum = -1) {

   header("Location: index.php");

// Here we just call the standard $form->save() function that
// will manage the validation and saving process. If it returns
// TRUE, then the save operation has been successful, and we can
// safely redirect the browser.

function report_save_on_action($rownum = -1) {
   global $form;

   // Save the data with status DRAFT

   if ($form->save()) {
      header("Location: /examples/");

// In this trigger we set a global variable $submitted to TRUE
// to signify that when the report will be saved it should be
// saved in status submitted. (We could have had a hidden field
// with status and set the status directly to that field instead).
// In any case we mark the report block as changed and start
// the save sequence.

function report_submit_on_action($rownum = -1) {
   global $form, $submitted;

   // Save the data with status SUBMITTED
   $submitted = TRUE;

   // Ensure that the saving sequence treats the report record
   // as changed

   if ($form->save()) {
      header("Location: /examples/");

And the last task at defining the form behaviour is to define the saving triggers for each block. Each block can have three saving triggers: ON_INSERT, ON_UPDATE and ON_DELETE. One of these triggers will be called for each record on the block if it requires saving (i.e. was changed or deleted). Since there is no report-level delete button, we do not need to define report_on_delete() trigger.

In this example we just print out the operation that is being performed.

// Saving triggers

function report_on_update($rownum) {
   global $form, $submitted;
   echo "updating report, submitted=$submitted<br>";

function report_on_insert($rownum) {
   global $form;
   echo "inserting report, submitted=$submitted<br>";

function record_on_insert($rownum) {
   global $form;
   echo "inserting record $rownum, project=".

function record_on_delete($rownum) {
   global $form;
   echo "deleting record $rownum, id=".

function record_on_update($rownum) {
   global $form;
   echo "updating record $rownum, project="
        ", id=".$form->record->id[$rownum]."<br>";

Displaying the form

Without automatic layouts this part of the script would definitely be the messiest - because generating a layout for the form requires a lot of HTML tags in the right places. But in this example we use standard layouts (added in version 1.1), and the form generation process becomes very easy. If not for extensive comments, you would have just several lines of code.

Note, that the first HTML tag is sent to the output only after the call to $form->process() method. Otherwise your redirects from triggers will not work!

// The key method of the library: it does most of the work!


// Generate the HTML code for the form
// Use the layout.css file provided with the library to achieve
// the needed look of the form

<title>Weekly Time Report</title>
<link rel="stylesheet" media="screen, projection"
      type="text/css" href="screen.css" />
<link rel="stylesheet" media="screen, projection"
      type="text/css" href="layout.css" />
<h1>Weekly Time Report</h1>


// This following line will display the error message, if
// an error was found during form validation. This can only
// happen after the form is submitted.

if ($error) echo "<h2>$error</h2>\n";

// Another important call - it will open the form tag and do
// some other preparations.


// And finally, generate the form with just one call!


// A very important call: $form->end_form().
// It generates all the hidden fields needed for the form to work
// and the electronic signature.


echo "</body></html>\n";

And this is it - the form is ready (except, of course, the actual data loading and saving code). The screenshot was taken from the execution of this script (aided by an appropriate stylesheet).