Rapid Application Development toolkit for building Administrative Web Applications

Radicore for PHP - Tutorial (Part 5)

By Tony Marston

18th November 2006

Introduction
Create transactions for the X_PERSON table
   1. Modify Data Dictionary
   2. Generate LIST1 Transaction
   3. Modify the screen structure files
   4. Modify screen labels, titles and button text
   5. Convert STAR_SIGN into a dropdown list
   6. Convert PERS_TYPE_ID into a dropdown list
   7. Convert FAVOURITE_FOOD into a multi-dropdown list
   8. Convert NODE_ID into a popup
   9. Convert PICTURE into a file picker
   10. Custom code for date validation
   11. Create data for X_PERSON
Create transactions for the X_PERSON_ADDR table
   1. Modify Data Dictionary
   2. Generate LIST2 Transaction
   3. Add to navigation bar of parent
   4. Modify the screen structure files
   5. Modify screen labels, titles and button text
   6. Define the business rules
   7. Modify LANGUAGE_TEXT.INC file for the error messages
   8. Modify X_PERSON_ADDR class for the business rules
   9. Modify X_PERSON class for the business rules
Create transactions for the X_PERS_OPT_XREF table
   1. Modify Data Dictionary
   2. Generate LINK1 Transaction
   3. Add to navigation bar of parent
   4. Modify the screen structure file
   5. Run the new Transaction
   6. Modify screen labels, titles and button text

Introduction

In Part 1 of this tutorial I described the initialisation procedure for a new application which is to be run under the Radicore framework, and the mechanism for generating new transactions.

In Part 2 I worked through a live example which created a basic forms family for maintaining the X_OPTION table in the test database.

In Part 3 I worked through the creation of the basic tasks for dealing with the X_PERS_TYPE, X_TREE_TYPE, X_TREE_LEVEL and X_TREE_NODE tables.

In Part 4 I worked through additional transactions for viewing and maintaining the relationships between nodes in a tree structure, and for resequencing the levels.

In this part I shall deal with the X_PERSON and X_PERSON_ADDR tables, plus the X_PERS_OPT_XREF table which is the intersection table in a many-to-many relationship with the X_OPTION table.


Create transactions for the X_PERSON table

Here are the steps necessary to build the components to maintain the X_PERSON table within the test database.

1. Modify Data Dictionary for X_PERSON table

The first step is to modify the data dictionary for this table using the values shown below:

Table Column Setting
x_person person_id UPPERCASE
pers_type_id UPPERCASE
nat_ins_no UPPERCASE
initials UPPERCASE
email_addr subtype=EMAIL
value2 BLANK WHEN ZERO
last_addr_no NOEDIT
end_date INFINITY IS NULL
created_date NOEDIT, AUTO-INSERT, NOSEARCH
created_user NOEDIT, AUTO-INSERT, NOSEARCH
revised_date NOEDIT, AUTO-UPDATE, NOSEARCH
revised_user NOEDIT, AUTO-UPDATE, NOSEARCH

For a description of what these settings mean please refer to Update Column.

After these changes have been made they must be made available to the application by running the Export to PHP function.

2. Generate LIST1 Transaction

We wish to access this transaction directly from a menu, not a navigation bar within another transaction, so we need to create a transaction of type LIST1. Using the Generate Transactions procedure select the X_PERSON table, the LIST1 pattern, then press the SUBMIT button to bring up the screen shown in Figure 1:

Figure 1 - create LIST1 transaction for the X_PERSON table

radicore-tutorial5-001 (8K)

When the SUBMIT button is pressed the transaction tst_x_person(list1) and its 5 children (add1, delete1, enquire1, update1, search) will be added to the MNU_TASK table, and the children will be added to the navigation bar of tst_x_person(list1). Additionally, tst_x_person(list1) will be added to the subsystem's menu as created in the Build Directory stage.

The following component scripts will also be created:

  1. x_person(list1).php
    <?php
    $table_id = 'x_person';                      // table name
    $screen   = 'x_person.list1.screen.inc';     // file identifying screen structure
    require 'std.list1.inc';                     // activate page controller
    ?>
    
  2. x_person(add1).php
    <?php
    $table_id = 'x_person';                      // table id
    $screen   = 'x_person.detail.screen.inc';    // file identifying screen structure
    require 'std.add1.inc';                      // activate page controller
    ?>
    
  3. x_person(del1).php
    <?php
    $table_id = 'x_person';                      // table id
    $screen   = 'x_person.detail.screen.inc';    // file identifying screen structure
    require 'std.delete1.inc';                   // activate page controller
    ?>
    
  4. x_person(enq1).php
    <?php
    $table_id = 'x_person';                      // table id
    $screen   = 'x_person.detail.screen.inc';    // file identifying screen structure
    require 'std.enquire1.inc';                  // activate page controller
    ?>
    
  5. x_person(search).php
    <?php
    $table_id = 'x_person';                      // table id
    $screen   = 'x_person.detail.screen.inc';    // file identifying screen structure
    require 'std.search1.inc';                   // activate page controller
    ?>
    
  6. x_person(upd1).php
    <?php
    $table_id = 'x_person';                      // table id
    $screen   = 'x_person.detail.screen.inc';    // file identifying screen structure
    require 'std.update1.inc';                   // activate page controller
    ?>
    

The following screen structure scripts will also be created:

  1. x_person.list1.screen.inc
    <?php
    $structure['xsl_file'] = 'std.list1.xsl';
    
    $structure['tables']['main'] = 'x_person';
    
    // identify the column specs - may use 'width' or 'class'
    $structure['main']['columns'][] = array('width' => 5);
    $structure['main']['columns'][] = array('width' => '4.76%');
    $structure['main']['columns'][] = array('width' => '4.76%');
    $structure['main']['columns'][] = array('width' => '4.76%');
    $structure['main']['columns'][] = array('width' => '4.76%');
    $structure['main']['columns'][] = array('width' => '4.76%');
    $structure['main']['columns'][] = array('width' => '4.76%');
    $structure['main']['columns'][] = array('width' => '4.76%');
    $structure['main']['columns'][] = array('width' => '4.76%');
    $structure['main']['columns'][] = array('width' => '4.76%');
    $structure['main']['columns'][] = array('width' => '4.76%');
    $structure['main']['columns'][] = array('width' => '4.76%');
    $structure['main']['columns'][] = array('width' => '4.76%');
    $structure['main']['columns'][] = array('width' => '4.76%');
    $structure['main']['columns'][] = array('width' => '4.76%');
    $structure['main']['columns'][] = array('width' => '4.76%');
    $structure['main']['columns'][] = array('width' => '4.76%');
    $structure['main']['columns'][] = array('width' => '4.76%');
    $structure['main']['columns'][] = array('width' => '4.76%');
    $structure['main']['columns'][] = array('width' => '4.76%');
    $structure['main']['columns'][] = array('width' => '4.76%');
    $structure['main']['columns'][] = array('width' => '4.76%');
    
    // identify the field names and their screen labels
    $structure['main']['fields'][] = array('selectbox' => 'Select');
    $structure['main']['fields'][] = array('person_id' => 'Person Id');
    $structure['main']['fields'][] = array('pers_type_id' => 'Pers Type Id');
    $structure['main']['fields'][] = array('node_id' => 'Node Id');
    $structure['main']['fields'][] = array('nat_ins_no' => 'Nat Ins No');
    $structure['main']['fields'][] = array('first_name' => 'First Name');
    $structure['main']['fields'][] = array('last_name' => 'Last Name');
    $structure['main']['fields'][] = array('initials' => 'Initials');
    $structure['main']['fields'][] = array('star_sign' => 'Star Sign');
    $structure['main']['fields'][] = array('email_addr' => 'Email Addr');
    $structure['main']['fields'][] = array('value1' => 'Value1');
    $structure['main']['fields'][] = array('value2' => 'Value2');
    $structure['main']['fields'][] = array('last_addr_no' => 'Last Addr No');
    $structure['main']['fields'][] = array('start_date' => 'Start Date');
    $structure['main']['fields'][] = array('end_date' => 'End Date');
    $structure['main']['fields'][] = array('picture' => 'Picture');
    $structure['main']['fields'][] = array('favourite_food' => 'Favourite Food');
    $structure['main']['fields'][] = array('fckeditor_test' => 'Fckeditor Test');
    $structure['main']['fields'][] = array('created_date' => 'Created Date');
    $structure['main']['fields'][] = array('created_user' => 'Created User');
    $structure['main']['fields'][] = array('revised_date' => 'Revised Date');
    $structure['main']['fields'][] = array('revised_user' => 'Revised User');
    ?>
    
  2. x_person.detail.screen.inc
    <?php
    $structure['xsl_file'] = 'std.detail1.xsl';
    
    $structure['tables']['main'] = 'x_person';
    
    // identify the column specs - may use 'width' or 'class'
    $structure['main']['columns'][] = array('width' => '25%');
    $structure['main']['columns'][] = array('width' => '*');
    
    // identify the contents of each row in the table
    $structure['main']['fields'][] = array('person_id' => 'Person Id');
    $structure['main']['fields'][] = array('pers_type_id' => 'Pers Type Id');
    $structure['main']['fields'][] = array('node_id' => 'Node Id');
    $structure['main']['fields'][] = array('nat_ins_no' => 'Nat Ins No');
    $structure['main']['fields'][] = array('first_name' => 'First Name');
    $structure['main']['fields'][] = array('last_name' => 'Last Name');
    $structure['main']['fields'][] = array('initials' => 'Initials');
    $structure['main']['fields'][] = array('star_sign' => 'Star Sign');
    $structure['main']['fields'][] = array('email_addr' => 'Email Addr');
    $structure['main']['fields'][] = array('value1' => 'Value1');
    $structure['main']['fields'][] = array('value2' => 'Value2');
    $structure['main']['fields'][] = array('last_addr_no' => 'Last Addr No');
    $structure['main']['fields'][] = array('start_date' => 'Start Date');
    $structure['main']['fields'][] = array('end_date' => 'End Date');
    $structure['main']['fields'][] = array('picture' => 'Picture');
    $structure['main']['fields'][] = array('favourite_food' => 'Favourite Food');
    $structure['main']['fields'][] = array('fckeditor_test' => 'Fckeditor Test');
    $structure['main']['fields'][] = array('created_date' => 'Created Date');
    $structure['main']['fields'][] = array('created_user' => 'Created User');
    $structure['main']['fields'][] = array('revised_date' => 'Revised Date');
    $structure['main']['fields'][] = array('revised_user' => 'Revised User');
    ?>
    

3. Modify the screen structure files

If you run transaction tst_x_person(list1) with its default screen structure you will see that it has too many fields and overflows the page width, so its needs to be customised. If you make the amendments show below the result will be as shown in Figure 2.

<?php
$structure['xsl_file'] = 'std.list1.xsl';

$structure['tables']['main'] = 'x_person';

// identify the column specs - may use 'width' or 'class'
$structure['main']['columns'][] = array('width' => 5);
$structure['main']['columns'][] = array('width' => 70);
$structure['main']['columns'][] = array('width' => 100);
$structure['main']['columns'][] = array('width' => 100);
$structure['main']['columns'][] = array('width' => 100);
$structure['main']['columns'][] = array('width' => '*');

// identify the field names and their screen labels
$structure['main']['fields'][] = array('selectbox' => 'Select');
$structure['main']['fields'][] = array('person_id' => 'Person Id');
$structure['main']['fields'][] = array('first_name' => 'First Name');
$structure['main']['fields'][] = array('last_name' => 'Last Name');
$structure['main']['fields'][] = array('star_sign' => 'Star Sign');
$structure['main']['fields'][] = array('pers_type_desc' => 'Person Type');
?>

Figure 2 - list X_PERSON screen (modified)

radicore-tutorial5-002 (7K)

If you press the 'New' button to run the Add1 transaction you will see the screen shown in Figure 3:

Figure 3 - add X_PERSON screen (original)

radicore-tutorial5-003 (9K)

It is possible to adjust this layout so that instead of a purely vertical arrangement some of the fields are side-by-side on the same line. If you make the amendments show below the result will be as shown in Figure 4.

<?php
$structure['xsl_file'] = 'std.detail1.xsl';

$structure['tables']['main'] = 'x_person';

// identify the column specs - may use 'width' or 'class'
$structure['main']['columns'][] = array('width' => '20%');
$structure['main']['columns'][] = array('width' => '25%');
$structure['main']['columns'][] = array('width' => '15%');
$structure['main']['columns'][] = array('width' => '25%');
$structure['main']['columns'][] = array('width' => '10%');
$structure['main']['columns'][] = array('width' => '10%');

// identify the field names and their screen labels
$structure['main']['fields'][1] = array('person_id' => 'Id', 'colspan' => 5);

$structure['main']['fields'][2][] = array('label' => 'First Name');
$structure['main']['fields'][2][] = array('field' => 'first_name', 'size' => 15);
$structure['main']['fields'][2][] = array('label' => 'Last Name');
$structure['main']['fields'][2][] = array('field' => 'last_name', 'size' => 15);
$structure['main']['fields'][2][] = array('label' => 'Initials');
$structure['main']['fields'][2][] = array('field' => 'initials');

$structure['main']['fields'][4] = array('picture' => 'Picture', 'colspan' => 5);
$structure['main']['fields'][5] = array('nat_ins_no' => 'Nat. Ins. No.', 'colspan' => 5);
$structure['main']['fields'][6] = array('pers_type_id' => 'Person Type', 'colspan' => 5);
$structure['main']['fields'][7] = array('star_sign' => 'Star Sign', 'colspan' => 5);
$structure['main']['fields'][8] = array('node_id' => 'Organisation', 'colspan' => 5);
$structure['main']['fields'][9] = array('email_addr' => 'E-mail', 'colspan' => 5);

$structure['main']['fields'][10][] = array('label' => 'Value 1');
$structure['main']['fields'][10][] = array('field' => 'value1');
$structure['main']['fields'][10][] = array('label' => 'Favourite Food', 'rowspan' => 3);
$structure['main']['fields'][10][] = array('field' => 'favourite_food', 'colspan' => 3, 'rowspan' => 3);

// these next two fields fit to the left of 'favourite_food' and must be displayed, even when empty
$structure['main']['fields'][11][] = array('label' => 'Value 2');
$structure['main']['fields'][11][] = array('field' => 'value2', 'display-empty' => 'y');
$structure['main']['fields'][12][] = array('label' => 'Last Address No');
$structure['main']['fields'][12][] = array('field' => 'last_addr_no', 'display-empty' => 'y');

$structure['main']['fields'][13][] = array('label' => 'Start Date');
$structure['main']['fields'][13][] = array('field' => 'start_date');
$structure['main']['fields'][13][] = array('label' => 'End Date');
$structure['main']['fields'][13][] = array('field' => 'end_date', 'colspan' => 3);

$structure['main']['fields'][] = array('created_date' => 'Created Date', 'colspan' => 5);
$structure['main']['fields'][] = array('created_user' => 'Created By', 'colspan' => 5);
$structure['main']['fields'][] = array('revised_date' => 'Revised Date', 'colspan' => 5);
$structure['main']['fields'][] = array('revised_user' => 'Revised By', 'colspan' => 5);
?>

Figure 4 - add X_PERSON screen (modified)

radicore-tutorial5-004 (8K)

This screen only allows text input for each field, so some alterations are required in order to convert some to use dropdown lists and file pickers.

4. Modify screen labels, titles and button text

Follow the procedure described in Part 2 of this tutorial to replace the default text with customised values.

5. Convert STAR_SIGN into a dropdown list

As the number of possible star signs is strictly limited (there are only 12) it would be better to allow the user to pick from a list instead of typing into a text box. This is what a dropdown list is for - it presents the user with a list of options and allows him to choose one. The first step is to modify the data dictionary for this table using the values shown below:

Table Column Setting Value
x_person star_sign control DROPDOWN LIST
option_list star_sign

For a description of what these settings mean please refer to Update Column.

After completing this change you must run the Export to PHP function in order to update the x_person.dict.inc file, otherwise the application will not know that anything has changed. This will create the following entry in x_person.dict.inc:

    $fieldspec['star_sign'] = array('type' => 'string',
                                    'size' => 3,
                                    'required' => 'y',
                                    'control' => 'dropdown',
                                    'optionlist' => 'star_sign');

The following code is needed in the _cm_getExtraData() method of the x_person.class.inc file in order to obtain the list of values and load it into the object's lookup_data variable that will be exported to the <lookup> element in the XML document. From here it will be loaded into the HTML output during the XSL transformation.

    function _cm_getExtraData ($where, $fieldarray)
    // Perform custom processing for the getExtraData method.
    // $where = a string in SQL 'where' format.
    // $fieldarray = the contents of $where as an array.
    {
        // get values for star_sign and insert into lookup array
        $array = $this->getValRep('star_sign');
        $this->lookup_data['star_sign'] = $array;

        return $fieldarray;

    } // _cm_getExtraData

The following code is needed in the _cm_getValRep() method of the x_person.class.inc file in order to provide the list of values:

    function _cm_getValRep ($item, $where)
    // get Value/Representation list as an associative array.
    {
        $array = array();

        if ($item == 'star_sign') {
            $array = getLanguageArray('star_sign');
            return $array;
        } // if

        return $array;

    } // _cm_getValRep

Note the use of the getLanguageArray() function instead of having hard-coded text. This requires an entry in the text/<language>/language_array.inc file as follows:

$array['star_sign'] = array('ARI' => 'Aries',
                            'AQU' => 'Aquarius',
                            'CAN' => 'Cancer',
                            'CAP' => 'Capricorn',
                            'GEM' => 'Gemini',
                            'LEO' => 'Leo',
                            'LIB' => 'Libra',
                            'PIS' => 'Pisces',
                            'SAG' => 'Sagittarius',
                            'SCO' => 'Scorpio',
                            'TAU' => 'Taurus',
                            'VIR' => 'Virgo');

If your application is required to support more than one language all you need do is to translate that text and place it in the language_array.inc file in the relevant <language> subdirectory.

When this data is exported to the XML document it will look like the following:

<x_person>
  .....
  <star_sign size="3" required="y" control="dropdown" optionlist="star_sign" />
  ..... 
</x_person>
<lookup>
  <star_sign>
    <option id="" /> 
    <option id="ARI">Aries</option> 
    <option id="AQU">Aquarius</option> 
    <option id="CAN">Cancer</option> 
    <option id="CAP">Capricorn</option> 
    <option id="GEM">Gemini</option> 
    <option id="LEO">Leo</option> 
    <option id="LIB">Libra</option> 
    <option id="PIS">Pisces</option> 
    <option id="SAG">Sagittarius</option> 
    <option id="SCO">Scorpio</option> 
    <option id="TAU">Taurus</option> 
    <option id="VIR">Virgo</option> 
  </star_sign>
</lookup>

When this data is written to the HTML output it will look like the following:

    <tr>
      <td class="label">
        <span class="required">* </span>Star Sign</td>
      <td>
        <select class="dropdown" name="star_sign">
          <option value="" selected="selected"> </option>
          <option value="ARI">Aries</option>
          <option value="AQU">Aquarius</option>
          <option value="CAN">Cancer</option>
          <option value="CAP">Capricorn</option>
          <option value="GEM">Gemini</option>
          <option value="LEO">Leo</option>
          <option value="LIB">Libra</option>
          <option value="PIS">Pisces</option>
          <option value="SAG">Sagittarius</option>
          <option value="SCO">Scorpio</option>
          <option value="TAU">Taurus</option>
          <option value="VIR">Virgo</option>
        </select>
      </td>
    </tr>

6. Convert PERS_TYPE_ID into a dropdown list

This is similar to the procedure for STAR_SIGN, but with some sight differences. The first step is to modify the data dictionary for this table using the values shown below:

Table Column Setting Value
x_person pers_type_id control DROPDOWN LIST
option_list pers_type_id

For a description of what these settings mean please refer to Update Column.

After completing this change you must run the Export to PHP function in order to update the x_person.dict.inc file, otherwise the application will not know that anything has changed. This will create the following entry in x_person.dict.inc:

$fieldspec['pers_type_id'] = array('type' => 'string',
                                   'size' => 6,
                                   'required' => 'y',
                                   'control' => 'dropdown',
                                   'optionlist' => 'pers_type_id');

The contents of this list does not come from a static source but from the contents of another database table, so we must access that database table in order to obtain the current contents. The following code is needed in the _cm_getExtraData() method of the x_person.class.inc file in order to obtain the list of values and load it into the object's lookup_data variable.

    function _cm_getExtraData ($where, $fieldarray)
    // Perform custom processing for the getExtraData method.
    // $where = a string in SQL 'where' format.
    // $fieldarray = the contents of $where as an array.
    {
        // get values for star_sign and insert into lookup array
        $array = $this->getValRep('star_sign');
        $this->lookup_data['star_sign'] = $array;

        // get contents of foreign table PERS_TYPE and add to lookup array
        $dbobject =& singleton::getInstance('x_pers_type');
        $array = $dbobject->getValRep('pers_type_id');
        $this->lookup_data['pers_type_id'] = $array;

        return $fieldarray;

    } // _cm_getExtraData

The following code is needed in the _cm_getValRep() method of the x_pers_type.class.inc file in order to provide the list of values:

    function _cm_getValRep ($item=null, $where=null)
    // get Value/Representation list as an associative array.
    {
        $array = array();

        if ($item == 'pers_type_id') {
            // get data from the database
            $this->sql_select     = 'pers_type_id, pers_type_desc';
            $this->sql_orderby    = 'pers_type_id';
            $this->sql_ordery_seq = 'asc';
            $data = $this->getData($where);

            // convert each row into 'id=desc' in the output array
            foreach ($data as $row => $rowdata) {
                $rowvalues = array_values($rowdata);
                $array[$rowvalues[0]] = $rowvalues[1];
            } // foreach

            return $array;
        } // if

        return $array;

    } // _cm_getValRep

7. Convert FAVOURITE_FOOD into a multi-dropdown list

Unlike an ordinary dropdown list which can only allow a single selection, this type of control will allow multiple selections to be made. In order for this to work the database schema must allow multiple selections to be stored. In MySQL this can be done as follows:

  `favourite_food` set('1','2','3','4','5','6','7','8','9','10') default NULL,

In PostgreSQL this can be done by using the array type as follows:

  favourite_food varchar(2)[],

Note that the MySQL version defines all the possible values that may be selected whereas the PostgreSQL version simply defines an array of varchar(2) elements without explicitly stating what the allowable values are or placing a limit on their number.

The first step is to modify the data dictionary for this table using the values shown below:

Table Column Setting Value
x_person favourite_food control MULTI-DROPDOWN
option_list favourite_food

For a description of what these settings mean please refer to Update Column.

After completing this change you must run the Export to PHP function in order to update the x_person.dict.inc file, otherwise the application will not know that anything has changed. This will create the following entry in x_person.dict.inc:

$fieldspec['favourite_food'] = array('type' => 'set',
                                     'values' => array('1', '2', '3', '4', '5', '6', '7', '8', '9', '10'),
                                     'control' => 'multidrop',
                                     'optionlist' => 'favourite_food');

The following code is needed in the _cm_getExtraData() method of the x_person.class.inc file in order to obtain the list of values:

    function _cm_getExtraData ($where, $fieldarray)
    // Perform custom processing for the getExtraData method.
    // $where = a string in SQL 'where' format.
    // $fieldarray = the contents of $where as an array.
    {
        // get values for star_sign and insert into lookup array
        $array = $this->getValRep('star_sign');
        $this->lookup_data['star_sign'] = $array;

        // get values for favourite_food and insert into lookup array
        $array = $this->getValRep('favourite_food');
        $this->lookup_data['favourite_food'] = $array;

        // get contents of foreign table PERS_TYPE and add to lookup array
        $dbobject =& singleton::getInstance('x_pers_type');
        $array = $dbobject->getValRep('pers_type_id');
        $this->lookup_data['pers_type_id'] = $array;

        return $fieldarray;

    } // _cm_getExtraData

The following code is needed in the _cm_getValRep() method of the x_pers_type.class.inc file in order to provide the list of values:

    function _cm_getValRep ($item='', $where)
    // get Value/Representation list as an associative array.
    {
        $array = array();

        if ($item == 'star_sign') {
            $array = getLanguageArray('star_sign');
            return $array;
        } // if

        if ($item == 'favourite_food') {
            $array = getLanguageArray('favourite_food');
            return $array;
        } // if

        return $array;

    } // _cm_getValRep

As the list of entries is static it requires an entry in the text/<language>/language_array.inc file as follows:

$array['favourite_food'] = array('1' => 'Eggs',
                                 '2' => 'Bacon',
                                 '3' => 'Chips',
                                 '4' => 'Beans',
                                 '5' => 'Sausages',
                                 '6' => 'Mushrooms',
                                 '7' => 'Tomatoes',
                                 '8' => 'Hash Browns',
                                 '9' => 'Toast',
                                 '10' => 'Fried Bread');

8. Convert NODE_ID into a popup

If the list of options in a dropdown list is too large then this type of control could be used instead as it activates another form. This allows the user to browse through the database table which contains the options, set selection criteria, and even to create new entries to add to the list.

The first step is to create the POPUP form to be used, which will be based on the List Tree Structure screen created previously. Using the Generate Transactions procedure select the X_TREE_NODE table, the POPUP4 pattern, then press the SUBMIT button to bring up the screen shown in Figure 5:

Figure 5 - create POPUP transaction for the X_TREE_NODE table

radicore-tutorial5-005 (9K)

When you press the SUBMIT button this will create the following:

It would be a good idea to amend the contents of x_tree_node.popup4.screen.inc so that it resembles the previously customised x_tree_node.tree2.screen.inc file.


The second step is to modify the data dictionary for this table using the values shown below:

Table Column Setting Value
x_person node_id control POPUP
Task Id tst_x_tree_node(popup4)
Foreign Field node_desc

For a description of what these settings mean please refer to Update Column.

After completing this change you must run the Export to PHP function in order to update the x_person.dict.inc file, otherwise the application will not know that anything has changed. This will create the following entry in x_person.dict.inc:

$fieldspec['node_id'] = array('type' => 'integer',
                              'size' => 4,
                              'minvalue' => 0,
                              'maxvalue' => 65535,
                              'required' => 'y',
                              'default' => '0',
                              'control' => 'popup',
                              'task_id' => 'tst_x_tree_node(popup4)',
                              'foreign_field' => 'node_desc');

This will need an entry from the X_TREE_TYPE table to be pre-selected before it can function, so the following code needs to be inserted into the _cm_popupCall() method of the x_person.class.inc file.

   function _cm_popupCall ($popupname, $where, $fieldarray, &$settings)
    // if a popup button has been pressed the contents of $where amy need to
    // be altered before the popup screen is called.
    // NOTE: $settings is passed BY REFERENCE as it may be altered.
    {
        if ($popupname == 'tst_x_tree_node(popup4)') {
            // structure must be of type 'ORG'
            $where = "tree_type_id='ORG'";
        } // if

        // allow only one entry to be selected (the default)
        $settings['select_one'] = TRUE;

        return $where;

    } // _cm_popupCall

9. Convert PICTURE into a file picker

This is similar to a popup, but instead of picking an entry from the database it allows the user to pick a file from the file system, and it is the filename which is then stored in the database.

The first step is to create the FILE PICKER form to be used. Using the Generate Transactions procedure select the X_PERSON table, the FILEPICKER pattern, then press the SUBMIT button to bring up the screen shown in Figure 6:

Figure 6 - create FILE PICKER transaction

radicore-tutorial5-006 (7K)

When you press the SUBMIT button this will create the following:


The following component script will be created:

<?php
$table_id = 'x_person';                       // table id
$screen   = 'x_person.filepicker.screen.inc'; // file identifying screen structure
require 'std.filepicker1.inc';                // activate page controller
?>

The following code is needed in the _cm_initialiseFilePicker() method of file x_person.class.inc in order to identify the subdirectory which contains the files to be displayed, as well as the types of files to be displayed. Any file with a type which is not in this list will be excluded.

    function _cm_initialiseFilePicker ()
    // perform any initialisation before displaying the File Picker screen.
    {
        // identify the subdirectory which contains the files
        $this->picker_subdir    = 'pictures';

        // identify the file types that may be picked
        $this->picker_filetypes = array('bmp', 'jpg', 'png', 'gif');

        return;

    } // _cm_initialiseFilePicker

You will need to ensure that this subdirectory exists and contains files that can be picked.


You can set the size of the thumbnail image to be displayed by modifying the screen structure script, as shown below.

<?php
$structure['xsl_file'] = 'std.filepicker.list1.xsl';

$structure['tables']['main'] = 'file';

$structure['main']['columns'][] = array('width' => '40%');
$structure['main']['columns'][] = array('width' => '10%');

$structure['main']['fields'][] = array('file' => 'File Name');
$structure['main']['fields'][] = array('image' => 'Image', 
                                       'imagewidth' => 75,
                                       'imageheight' => 95);

?>

Note that the data will be displayed in two column groups, not one, which is why the width of each group totals to 50%.


The next step is to modify the data dictionary for this table using the values shown below:

Table Column Setting Value
x_person picture control FILE PICKER
Subtype IMAGE
Image width 75
Image Height 95
Task Id tst_x_person(filepicker)

For a description of what these settings mean please refer to Update Column.

After completing this change you must run the Export to PHP function in order to update the x_person.dict.inc file, otherwise the application will not know that anything has changed. This will create the following entry in x_person.dict.inc:

$fieldspec['picture'] = array('type' => 'string',
                              'size' => 40,
                              'subtype' => 'image',
                              'imagewidth' => 75,
                              'imageheight' => 95,
                              'control' => 'filepicker',
                              'task_id' => 'tst_x_person(filepicker)');

As a result of these modifications the screen changes from what is shown in Figure 4 to what is shown in Figure 7:

Figure 7 - add X_PERSON screen (modified)

radicore-tutorial5-007 (10K)

10. Custom code for date validation

Although the standard code within the framework will ensure that the start_date and end_date fields both contain valid dates, some extra validation is required to check that start_date is earlier than end_date. This can be achieved by inserting the following code into the _cm_commonValidation() method of the x_person.class.inc file.

    function _cm_commonValidation ($fieldarray, $orignaldata)
    // perform validation that is common to INSERT and UPDATE.
    {
        if ($fieldarray['start_date'] > $fieldarray['end_date']) {
            // 'Start Date cannot be later than End Date';
            $this->errors['start_date'] = getLanguageText('e0001');
            // 'End Date cannot be earlier than Start Date';
            $this->errors['end_date']   = getLanguageText('e0002');
        } // if

        return $fieldarray;

    } // _cm_commonValidation

Please note the following:

Note also the use of the getLanguageText() function instead of having hard-coded text. This requires entries to be added to the text/<language>/language_text.inc file as follows:

$array['e0001'] = "Start Date cannot be later than End Date";
$array['e0002'] = "End Date cannot be earlier than Start Date";

11. Create data for X_PERSON

You may now proceed to enter a selection of test data, perhaps using a PDF export from the xample database as a reference.


Create transactions for the X_PERSON_ADDR table

Here are the steps necessary to build the components to maintain the X_PERSON_ADDR table within the test database.

1. Modify Data Dictionary for X_PERS_ADDR table

The first step is to modify the data dictionary for this table using the values shown below:

Table Column Setting
x_pers_addr person_id UPPERCASE
town UPPERCASE
end_date INFINITY IS NULL
created_date NOEDIT, AUTO-INSERT, NOSEARCH
created_user NOEDIT, AUTO-INSERT, NOSEARCH
revised_date NOEDIT, AUTO-UPDATE, NOSEARCH
revised_user NOEDIT, AUTO-UPDATE, NOSEARCH

For a description of what these settings mean please refer to Update Column.

It would also be useful to specify custom validation for this table using the values shown below:

Table Column Custom Validation
x_pers_addr telephone_no sample.validation.class.inc/sample_validation_class/telephone_no
fax_no sample.validation.class.inc/sample_validation_class/fax_no
postcode sample.validation.class.inc/sample_validation_class/postcode

This is explained in RADICORE for PHP - Extending the Validation class.

After these changes have been made they must be made available to the application by running the Export to PHP function.

2. Generate LIST2 Transaction

This transaction is going to be accessed from a navigation button within the List X_PERSON transaction, not directly from a menu button, therefore we need to use the LIST2 pattern. Using the Generate Transactions procedure select the X_PERSON_ADDR table, the LIST2 pattern, then press the SUBMIT button to bring up the screen shown in Figure 8:

Figure 8 - create LIST2 transaction for the X_PERSON_ADDR table

radicore-tutorial5-008 (11K)

When the SUBMIT button is pressed the transaction tst_x_person_addr(list2) and its 5 children (add2, delete1, enquire1, update1, search) will be added to the MNU_TASK table.

The following component scripts will also be created:

  1. x_person_addr(list2).php
    <?php
    $outer_table = 'x_person';                          // name of outer (parent) table
    $inner_table = 'x_person_addr';                     // name of inner (child) table
    $screen      = 'x_person_addr.list2.screen.inc';    // file identifying screen structure
    require 'std.list2.inc';                            // activate page controller
    ?>
    
  2. x_person_addr(add2).php
    <?php
    $table_id = 'x_person_addr';                      // table name
    $screen   = 'x_person_addr.detail.screen.inc';    // file identifying screen structure
    require 'std.add2.inc';                           // activate page controller
    ?>
    
  3. x_person_addr(del1).php
    <?php
    $table_id = 'x_person_addr';                      // table name
    $screen   = 'x_person_addr.detail.screen.inc';    // file identifying screen structure
    require 'std.del1.inc';                           // activate page controller
    ?>
    
  4. x_person_addr(enq1).php
    <?php
    $table_id = 'x_person_addr';                      // table name
    $screen   = 'x_person_addr.detail.screen.inc';    // file identifying screen structure
    require 'std.enq1.inc';                           // activate page controller
    ?>
    
  5. x_person_addr(search).php
    <?php
    $table_id = 'x_person_addr';                      // table name
    $screen   = 'x_person_addr.detail.screen.inc';    // file identifying screen structure
    require 'std.search.inc';                         // activate page controller
    ?>
    
  6. x_person_addr(upd1).php
    <?php
    $table_id = 'x_person_addr';                      // table name
    $screen   = 'x_person_addr.detail.screen.inc';    // file identifying screen structure
    require 'std.upd1.inc';                           // activate page controller
    ?>
    

The following screen structure scripts will also be created:

  1. x_person_addr.list2.screen.inc
    <?php
    $structure['xsl_file'] = 'std.list2.xsl';
    
    $structure['tables']['outer'] = 'x_person';
    
    // identify the column specs - may use 'width' or 'class'
    $structure['outer']['columns'][] = array('width' => '25%');
    $structure['outer']['columns'][] = array('width' => '*');
    
    // identify the field names and their screen labels
    $structure['outer']['fields'][] = array('person_id' => 'Person Id');
    $structure['outer']['fields'][] = array('pers_type_id' => 'Pers Type Id');
    $structure['outer']['fields'][] = array('node_id' => 'Node Id');
    $structure['outer']['fields'][] = array('nat_ins_no' => 'Nat Ins No');
    $structure['outer']['fields'][] = array('first_name' => 'First Name');
    $structure['outer']['fields'][] = array('last_name' => 'Last Name');
    $structure['outer']['fields'][] = array('initials' => 'Initials');
    $structure['outer']['fields'][] = array('star_sign' => 'Star Sign');
    $structure['outer']['fields'][] = array('email_addr' => 'Email Addr');
    $structure['outer']['fields'][] = array('value1' => 'Value1');
    $structure['outer']['fields'][] = array('value2' => 'Value2');
    $structure['outer']['fields'][] = array('last_addr_no' => 'Last Addr No');
    $structure['outer']['fields'][] = array('start_date' => 'Start Date');
    $structure['outer']['fields'][] = array('end_date' => 'End Date');
    $structure['outer']['fields'][] = array('picture' => 'Picture');
    $structure['outer']['fields'][] = array('favourite_food' => 'Favourite Food');
    $structure['outer']['fields'][] = array('fckeditor_test' => 'Fckeditor Test');
    $structure['outer']['fields'][] = array('created_date' => 'Created Date');
    $structure['outer']['fields'][] = array('created_user' => 'Created User');
    $structure['outer']['fields'][] = array('revised_date' => 'Revised Date');
    $structure['outer']['fields'][] = array('revised_user' => 'Revised User');
    
    $structure['tables']['inner'] = 'x_person_addr';
    
    // identify the column specs - may use 'width' or 'class'
    $structure['inner']['columns'][] = array('width' => 5);
    $structure['inner']['columns'][] = array('width' => '6.25%');
    $structure['inner']['columns'][] = array('width' => '6.25%');
    $structure['inner']['columns'][] = array('width' => '6.25%');
    $structure['inner']['columns'][] = array('width' => '6.25%');
    $structure['inner']['columns'][] = array('width' => '6.25%');
    $structure['inner']['columns'][] = array('width' => '6.25%');
    $structure['inner']['columns'][] = array('width' => '6.25%');
    $structure['inner']['columns'][] = array('width' => '6.25%');
    $structure['inner']['columns'][] = array('width' => '6.25%');
    $structure['inner']['columns'][] = array('width' => '6.25%');
    $structure['inner']['columns'][] = array('width' => '6.25%');
    $structure['inner']['columns'][] = array('width' => '6.25%');
    $structure['inner']['columns'][] = array('width' => '6.25%');
    $structure['inner']['columns'][] = array('width' => '6.25%');
    $structure['inner']['columns'][] = array('width' => '6.25%');
    $structure['inner']['columns'][] = array('width' => '6.25%');
    
    // identify the field names and their screen labels
    $structure['inner']['fields'][] = array('selectbox' => 'Select');
    $structure['inner']['fields'][] = array('person_id' => 'Person Id');
    $structure['inner']['fields'][] = array('address_no' => 'Address No');
    $structure['inner']['fields'][] = array('telephone_no' => 'Telephone No');
    $structure['inner']['fields'][] = array('fax_no' => 'Fax No');
    $structure['inner']['fields'][] = array('addr_line_1' => 'Addr Line 1');
    $structure['inner']['fields'][] = array('addr_line_2' => 'Addr Line 2');
    $structure['inner']['fields'][] = array('addr_line_3' => 'Addr Line 3');
    $structure['inner']['fields'][] = array('town' => 'Town');
    $structure['inner']['fields'][] = array('county' => 'County');
    $structure['inner']['fields'][] = array('postcode' => 'Postcode');
    $structure['inner']['fields'][] = array('start_date' => 'Start Date');
    $structure['inner']['fields'][] = array('end_date' => 'End Date');
    $structure['inner']['fields'][] = array('created_date' => 'Created Date');
    $structure['inner']['fields'][] = array('created_user' => 'Created User');
    $structure['inner']['fields'][] = array('revised_date' => 'Revised Date');
    $structure['inner']['fields'][] = array('revised_user' => 'Revised User');
    ?>
    
  2. x_person_addr.detail.screen.inc
    <?php
    $structure['xsl_file'] = 'std.detail1.xsl';
    
    $structure['tables']['main'] = 'x_person_addr';
    
    // identify the column specs - may use 'width' or 'class'
    $structure['main']['columns'][] = array('width' => '25%');
    $structure['main']['columns'][] = array('width' => '*');
    
    // identify the contents of each row in the table
    $structure['main']['fields'][] = array('person_id' => 'Person Id');
    $structure['main']['fields'][] = array('address_no' => 'Address No');
    $structure['main']['fields'][] = array('telephone_no' => 'Telephone No');
    $structure['main']['fields'][] = array('fax_no' => 'Fax No');
    $structure['main']['fields'][] = array('addr_line_1' => 'Addr Line 1');
    $structure['main']['fields'][] = array('addr_line_2' => 'Addr Line 2');
    $structure['main']['fields'][] = array('addr_line_3' => 'Addr Line 3');
    $structure['main']['fields'][] = array('town' => 'Town');
    $structure['main']['fields'][] = array('county' => 'County');
    $structure['main']['fields'][] = array('postcode' => 'Postcode');
    $structure['main']['fields'][] = array('start_date' => 'Start Date');
    $structure['main']['fields'][] = array('end_date' => 'End Date');
    $structure['main']['fields'][] = array('created_date' => 'Created Date');
    $structure['main']['fields'][] = array('created_user' => 'Created User');
    $structure['main']['fields'][] = array('revised_date' => 'Revised Date');
    $structure['main']['fields'][] = array('revised_user' => 'Revised User');
    ?>
    

3. Add to navigation bar of parent

This part is not done automatically, so it has to be done manually. By following this procedure you must locate the task tst_x_person(list1) and add tst_x_person_addr(list2) to its list of navigation buttons.

4. Modify the screen structure files

If you run transaction tst_x_person_addr(list2) with its default screen structure you will see that it has too many fields and overflows the page width, so its needs to be customised. If you make the amendments show below the result will be as shown in Figure 9.

<?php
$structure['xsl_file'] = 'std.list2.xsl';

$structure['tables']['outer'] = 'x_person';

$structure['outer']['columns'][] = array('width' => '25%');
$structure['outer']['columns'][] = array('width' => '5%');
$structure['outer']['columns'][] = array('width' => '*');

$structure['outer']['fields'][1][] = array('label' => 'Person');
$structure['outer']['fields'][1][] = array('field' => 'first_name');
$structure['outer']['fields'][1][] = array('field' => 'last_name');

$structure['tables']['inner'] = 'x_person_addr';

$structure['inner']['columns'][] = array('width' => 5);
$structure['inner']['columns'][] = array('width' => 20);
$structure['inner']['columns'][] = array('width' => '*');
$structure['inner']['columns'][] = array('width' => 150);
$structure['inner']['columns'][] = array('width' => 100);
$structure['inner']['columns'][] = array('width' => 100);

$structure['inner']['fields'][] = array('selectbox' => 'Select');
$structure['inner']['fields'][] = array('address_no' => '#');
$structure['inner']['fields'][] = array('town' => 'Town');
$structure['inner']['fields'][] = array('county' => 'County');
$structure['inner']['fields'][] = array('postcode' => 'Postcode');
$structure['inner']['fields'][] = array('start_date' => 'Start Date');
?>

Figure 9 - list X_PERSON_ADDR screen (modified)

radicore-tutorial5-009 (7K)

If you modify the default screen structure for the detail screen you will see the result shown in Figure 10.

<?php
$structure['xsl_file'] = 'std.detail1.xsl';

$structure['tables']['main'] = 'x_person_addr';

$structure['main']['columns'][] = array('width' => '25%');
$structure['main']['columns'][] = array('width' => '*');

$structure['main']['fields'][] = array('person_name' => 'Name');
$structure['main']['fields'][] = array('address_no' => 'Address No');
$structure['main']['fields'][] = array('addr_line_1' => 'Addr Line 1');
$structure['main']['fields'][] = array('addr_line_2' => 'Addr Line 2');
$structure['main']['fields'][] = array('addr_line_3' => 'Addr Line 3');
$structure['main']['fields'][] = array('town' => 'Town');
$structure['main']['fields'][] = array('county' => 'County');
$structure['main']['fields'][] = array('postcode' => 'Postcode');
$structure['main']['fields'][] = array('telephone_no' => 'Telephone No');
$structure['main']['fields'][] = array('fax_no' => 'Fax No');
$structure['main']['fields'][] = array('start_date' => 'Start Date');
$structure['main']['fields'][] = array('end_date' => 'End Date');
$structure['main']['fields'][] = array('created_date' => 'Created Date');
$structure['main']['fields'][] = array('created_user' => 'Created User');
$structure['main']['fields'][] = array('revised_date' => 'Revised Date');
$structure['main']['fields'][] = array('revised_user' => 'Revised User');
?>

Figure 10 - add X_PERSON_ADDR screen (modified)

radicore-tutorial5-010 (7K)

You should notice that this refers to a field called person_name which does not actually exist in the database schema, so where does it come from? If you look in file x_person_addr.dict.inc you will see the following entry:

$this->parent_relations[] = array('parent' => 'x_person',
                                  'parent_field' => 'CONCAT(first_name, \' \', last_name) AS person_name',
                                  'fields' => array('person_id' => 'person_id'));

This is possible due to the relationship details which exist in the Data Dictionary as explained here. This information is used by the framework to retrieve the specified field(s) from the parent record(s) and add them into the data array for the current record.

5. Modify screen labels, titles and button text

Follow the procedure described in Part 2 of this tutorial to replace the default text with customised values.

6. Define the business rules

The business rules for person addresses is quite involved and therefore will require some custom code to be added to both the X_PERSON_ADDR and X_PERSON classes. These rules are as follows:

  1. The primary key for each X_PERSON_ADDR record is a combination of the following:

    This means that the first address for each person is number 1, the second address is number 2, and so on, in strict sequence.

    The value for address_no is obtained by reading the last_addr_no field on the parent X_PERSON record and incrementing it by one. This means the following:

    This simple arrangement means that any primary key can be constructed without having to search for the address with the earliest/latest or previous/next start date.

  2. Each X_PERSON record has a start_date and an end_date, with the rule being that end_date cannot be earlier than start_date.
  3. Each X_PERSON_ADDR record also has a start_date and an end_date, with the rule being that end_date cannot be earlier than start_date.
  4. In addition the start_date of the first address must be the same as the X_PERSON start date, and the end_date of the last address must be the same as the X_PERSON end date. This ensures that the time period covered on the X_PERSON record is exactly the same as the time period on the associated X_PERSON_ADDR records.
  5. The dates on all address records for a person must be contiguous, i.e. they must flow in a continuous and unbroken sequence. No overlaps are allowed, and no missing dates are allowed.
  6. If the start_date on the X_PERSON record is changed it will also change the start_date on the first address record. This date cannot be changed to a value which is greater than the end_date of either the X_PERSON record or the first X_PERSON_ADDR record.
  7. If the end_date on the X_PERSON record is changed it will also change the end_date on the last address record. This date cannot be changed to a value which is earlier than the start_date of either the X_PERSON record or the last X_PERSON_ADDR record.
  8. When inserting address records the following rules apply:
    1. The value for address_no will be obtained by incrementing the current value of last_addr_no on the X_PERSON record.
    2. If this is the first address then start_date will automatically be set to start_date on the X_PERSON record and no changes will be allowed.
    3. If this is not the first address then an initial value for start_date will be displayed as the start_date of the previous address plus 1 day. This may be changed to a later date, but cannot be earlier.
    4. When the record is added to the database and there is a previous address then the end_date of the previous address will be updated to the start_date of the current address minus 1 day.
    5. The end_date will be set to end_date on the X_PERSON record and no changes will be allowed.
  9. When updating address records the following rules apply:
    1. It is not possible to change the start_date of the first address record. This can only be done via the Update Person screen.
    2. If there is a previous address then the start_date of the current address must be later than the start_date of the previous address.
    3. When the record is written to the database and there is a previous address then the end_date of the previous address will be updated to the start_date of the current address minus 1 day.
    4. It is not possible to change the end_date of the last address record. This can only be done via the Update Person screen.
    5. If there is a next address then the end_date of the current address must be earlier than the end_date of the next address.
    6. When the record is written to the database and there is a next address then the start_date of the next address will be updated to the end_date of the current address plus 1 day.
  10. When deleting address records the following rules apply:
    1. In order to ensure that address sequence numbers start at 1 and continue in an unbroken sequence it is not possible to delete any address record for a person other than the last address.
    2. When the last address is deleted and a previous address exists then the end_date of the previous address must be set to the end_date of the record being deleted.
    3. When the last address is deleted the value in last_addr_no on the X_PERSON record must be set to the address_no of the deleted record minus 1.

7. Modify LANGUAGE_TEXT.INC file for the error messages

The following error messages should first be placed in the text/en/language_text.inc file so that they can be accessed by the getLanguageText() method.

$array['e0001'] = "Start Date cannot be later than End Date";
$array['e0002'] = "End Date cannot be earlier than Start Date";
$array['e0003'] = "This is the lowest level - no children allowed";
$array['e0004'] = "Cannot be empty";
$array['e0005'] = "Value too large (%1\$s)";
$array['e0006'] = "Must be an integer";
$array['e0007'] = "Must be greater than 0";
$array['e0008'] = "Must not be greater than %1\$s";
$array['e0009'] = "Value has already been used";

$array['e0010'] = "Could not locate first PERS_ADDR record";
$array['e0011'] = "Start Date cannot be later than End Date of first address";
$array['e0012'] = "Could not locate last PERS_ADDR record";
$array['e0013'] = "End Date cannot be earlier than Start Date of last address";
$array['e0014'] = "Could not locate next PERS_ADDR record";
$array['e0015'] = "End Date must be earlier than End Date of next entry";
$array['e0016'] = "Could not locate previous PERS_ADDR record";
$array['e0017'] = "Start Date must be later than Start Date of previous entry";
$array['e0018'] = "Cannot change start date of first address.";
$array['e0019'] = "Cannot change end date of last address.";

$array['e0020'] = "";
$array['e0021'] = "";
$array['e0022'] = "";
$array['e0023'] = "Could not locate PERSON record.";
$array['e0024'] = "You have not selected an address for deletion.";
$array['e0025'] = "Only the last address can be deleted.";

// these entries are used by 'sample.validation.class.inc'
$array['sample001'] = "Invalid format for a postcode.";
$array['sample002'] = "Invalid format for a telephone number.";
$array['sample003'] = "Invalid format for a fax number.";

Translations in other languages can be placed in the relevant language subdirectories.

8. Modify X_PERSON_ADDR class for the business rules

The following code goes into the _cm_getInitialData() method:

    function _cm_getInitialData ($fieldarray)
    // Perform custom processing for the getInitialData method.
    // $fieldarray contains data from the initial $where clause.
    {
        $dbobject =& singleton::getInstance('x_person');

        $dbobject->sql_select = 'person_id,last_addr_no,start_date,end_date';
        $where = array2where($fieldarray, $dbobject->getPkeyNames());
        $person_data = $dbobject->getData_raw($where);
        if ($dbobject->numrows <> 1) {
            // 'Could not locate PERSON record'
            $this->errors[] = getLanguageText('e0023');
            return $fieldarray;
        } // if

        $person_data = $person_data[0];  // use data from first row only

        // this field is generated, not input by the user
        $this->fieldspec['address_no']['noedit'] = 'y';

        // get last_addr_no and increment it for the new address
        $address_no = $person_data['last_addr_no'] + 1;    <-- Rule 8a
        $fieldarray['address_no'] = $address_no;

        // end_date must be same as person.end_date (and not editable)
        $fieldarray['end_date'] = $person_data['end_date'];    <-- Rule 8e
        $this->fieldspec['end_date']['noedit'] = 'y';

        if ($fieldarray['address_no'] == 1) {
            // for 1st address start_date must be same as person.start_date (and not editable)
            $fieldarray['start_date'] = $person_data['start_date'];    <-- Rule 8b
            $this->fieldspec['start_date']['noedit'] = 'y';
        } else {
            if (empty($fieldarray['start_date'])) {
                // for subsequent addresses the start date must be later than
                // the start date of the previous address
                $this->sql_select = 'start_date,end_date';    <-- Rule 8c
                $where_array['person_id']  = $fieldarray['person_id'];
                $where_array['address_no'] = $address_no -1;
                $where = array2where($where_array);
                $prev_addr_data = $this->getData_raw($where);
                if ($this->numrows <> 1) {
                    // 'Could not locate previous PERS_ADDR record'
                    $this->errors[] =