3.5 Form Functions



Form provides additional built-in functions that you can call in your user edit routine. These functions allow access to the data fields on the screen and also allow the user edit routine to determine which form is currently on the screen. Use them any time the user edit routine is called to read or write data in fields on the screen. These functions are described in detail at the end of this chapter.

To expand on the second example, let us require that the user edit routine check the state field and the corresponding zip code field. For this, we need a table that has each state and the valid zip codes within that state. This can be done by creating a table with the lowest and highest zip codes valid for that state. Since the contents of one field depend upon another, these two fields should be checked when the record is to be stored rather than when each field is being entered to prevent spurious error messages when one field is entered before the other. If the zip code field was entered before the state field, there would be no way to check whether the zip code is correct or not. Here is a user edit routine that performs these checks:

#include <stdio.h>

#include <cbase/form.h>

long atol ();

char *

user_edit (edit_type, edit_name, old_value, new_value, exit_char)

int edit_type;

char *edit_name;

char *old_value;

char *new_value;

int exit_char;

{

char state_name[4];

long zip;

int i;

static struct {

char *abbrev;

long lowest_zip;

long highest_zip;

}

states[] = {

"AK", 99500, 99599,

"AL", 35000, 36999,

/* ... */

"WA", 98000, 99499,

"WY", 82000, 83199,

NULL, 0, 0

};

/* check for valid state and zip code fields */

if (edit_type == U_PREPARE) {

strcpy (state_name, sread ("state"));

zip = atol (sread ("zip"));

for (i = 0; states[i].abbrev != NULL; i++) {

if (strcmp (states[i].abbrev, state_name) == 0) {

if (zip >= states[i].lowest_zip &&

zip <= states[i].highest_zip)

/* both state and zip are valid */

return (NULL);

else {

snextfld ("zip");

return ("Zip doesn't match state");

}

}

}

/* no state match found */

snextfld ("state");

return ("Illegal state abbreviation");

}

else

return (NULL);

}

main (argc, argv)

int argc;

char *argv[];

{

form (argc, argv);

exit (0);

}

This user edit routine uses the sread function to fetch the contents of the state and zip fields. Note that each time you call the sread function, it overwrites the value returned from the previous call, so you must save what sread returns each time. The sread function does not actually read anything from the screen nor does it change the position of the cursor on the screen. The contents of the referenced data field are converted to a character string identical to the one displayed on the screen, and a pointer to that string is returned by sread.

The snextfld function positions the cursor when an error is discovered. The cursor is positioned to the field that contains the error. This makes it easy for the operator to identify and correct the field with the error. The cursor is not moved when the snextfld function is called, but instead this function sets the next field for accepting data. Form displays the error message and moves the cursor to the field named in the snextfld function call and waits for the operator to enter data.

You do not need to change the formfile to use this user edit routine, since it does not need to be called to edit individual fields. The command to compile the edit routine would be the same as the examples above:

cl -c -AL -Ic:\cbase\include stateform.c

cl /F 5000 /Fe..\bin\stateform stateform.obj /link /NOE c:\cbase\lib\libcbase.lib

and the command to use this form would be:

stateform -qfuad sub

In the next example, the user edit routine keeps track of how many subscribers there are for each magazine. This example requires that the user edit routine update a balance field in a second file, a common operation in on-line systems.

The mag file used in previous examples has a field named subscribers in which the user edit routine maintains a count of the number of subscribers for a magazine. As new magazines are added or deleted for a subscriber, the user edit routine updates the count.

First, a brief discussion of what the edit routine must do. The user edit routine updates the mag file, so it must open and close that file. Each time a new record is stored in the subscriptions file, it must increment the subscribers field in the magazine file. When the operator deletes a record, the user edit routine decrements the subscribers field. When the operator updates a record, the user edit routine must decrement the subscribers count for the old magazine and increment the count for the new magazine. It decrements the old magazine's count when the update is started and increments the new magazine's count when the updated record is stored. Once an update is begun, the updated record must be stored with the STORE key, so the user edit routine may be written in this way. This is so that the user edit routine can change balances at the beginning of an update. The user edit routine also uses features of the C/Base file system and is itself a good demonstration of its user

#include <stdio.h>

#include <cbase/dirio.h>

#include <cbase/form.h>

static DFILE *mag_file;

/* dblist for the magazines file */

static char *mag_list[] = {

"magazine",

"subscribers",

0 };

/* and its corresponding structure */

static struct {

char mag[16];

int subscribers;

}

mag;

char*bump_mag();

char *

user_edit (edit_type, edit_name, old_value, new_value, exit_char) int edit_type;

char *edit_name;

char *old_value;

char *new_value;

int exit_char;

{

if (edit_type == U_INITIALIZE) {

mag_file = dlopen ("mag", "u");

if (mag_file == NULL)

return ("Cannot open 'mag' file");

if (dblist (mag_list, mag_file) < 0)

return ("Cannot set dblist for 'mag' file");

return (NULL);

}

else if (edit_type == U_EXIT) {

dclose (mag_file);

return (NULL);

}

else if (edit_type == U_DELETE || edit_type == U_UPDATE) {

return (bump_mag (-1));

}

else if (edit_type == U_STORED) {

return (bump_mag (1));

}

else

return (NULL);

}

/* change subscriber count in magazine file */

/* if successful, return NULL pointer, */

/* otherwise, return pointer to error message */

char *

bump_mag (delta)

int delta;

{

long recno;

strncpy (mag.mag, sread ("magazine"), sizeof (mag.mag));

if (dflock (mag_file) == 0)

return ("Magazine file busy");

recno = dfindk (&mag, mag_file);

if (recno < 0) {

dfunlock (mag_file);

snextfld ("magazine");

return ("No such magazine");

}

mag.subscribers += delta;

dupdate (&mag, mag_file);

dfunlock (mag_file);

return (NULL);

}

main (argc, argv)

int argc;

char *argv[];

{

form (argc, argv);

exit (0);

}

If this program is stored in a text file named sform.c, then the command:

cl -c -AL -Ic:\cbase\include sform.c

cl /F 5000 /Fe..\bin\sform sform.obj /link /NOE c:\cbase\lib\libcbase.lib

creates a new form program named sform. The command to use this new form program would be:

sform -qfuad script

When the user edit routine is called for initialization, it opens the mag file which contains the field to be updated. The user edit routine uses the dblist function so that if there are any changes made to the RMSfile, no changes need to be made to this program. The field list given to dblist contains only those fields which are needed by this program.

When a record is deleted or when a record update is started, the subscriber count for the current magazine is decremented. When a record is stored, either from an update or from an add, the subscriber count for the magazine is incremented. If a record is updated, but the magazine field is not changed, there is no net change to the subscribers field for that magazine. The user edit routine decrements the count at the beginning of the update, then increments the subscriber count for the magazine when the updated record is stored. This general method correctly processes any combination of adding, updating and deleting records.

The subscriber count is actually updated using the bump_mag function. This function uses file locking, so this routine can be safely used in a multi-user environment. Note that it is very careful about always unlocking the file before it returns. A user edit routine must not leave any file locked when it returns to form.

The user edit routine opens and uses the mag file, and form also opens and uses the mag file for doing the magazine field validation and lookups. There is no conflict here, since form is only reading records in the mag file, and thus does not perform file locking. Form performs record or file locking on the RMSfile when it adds, updates or deletes records in the RMSfile. It is possible for the user edit routine to perform file operations on the RMSfile that form uses as long as the user edit routine also performs record or file locking and does not leave the RMSfile locked between user_edit calls.

This user edit routine need only be modified slightly to be used with the multiple forms file like demo~subscrpt. The user edit routine should perform the U_DELETE, U_UPDATE and U_STORED code if it is operating on the second form, and the change to the user edit routine reflects this. A revised user edit routine is:

#include <stdio.h>

#include <cbase/dirio.h>

#include <cbase/form.h>

static DFILE *mag_file;

/* dblist for the magazines file */

static char *mag_list[] = {

"magazine",

"subscribers",

0 };

/* and its corresponding structure */

static struct {

char mag[16];

int subscribers;

}

mag;

char*bump_mag();

char *

user_edit (edit_type, edit_name, old_value, new_value, exit_char)

int edit_type;

char *edit_name;

char *old_value;

char *new_value;

int exit_char;

{

if (edit_type == U_INITIALIZE) {

mag_file = dlopen ("mag", "u");

if (mag_file == NULL)

return ("Cannot open 'mag' file");

if (dblist (mag_list, mag_file) < 0)

return ("No dblist for 'mag' file");

return (NULL);

}

else if (edit_type == U_EXIT) {

dclose (mag_file);

return (NULL);

}

else if (strcmp (sformname(), "subscription"))

return (NULL); /* not subscription form */

else if (edit_type == U_DELETE || edit_type == U_UPDATE) {

return (bump_mag (-1));

}

else if (edit_type == U_STORED) {

return (bump_mag (1));

}

else

return (NULL);

}

/* change subscriber count in magazine file */

/* if successful, return NULL pointer, */

/* otherwise, return pointer to error message */

char *

bump_mag (delta)

int delta;

{

long recno;

strncpy (mag.mag, sread ("magazine"), sizeof (mag.mag));

if (dflock (mag_file) == 0)

return ("Magazine file busy");

recno = dfindk (&mag, mag_file);

if (recno < 0) {

dfunlock (mag_file);

snextfld ("magazine");

return ("No such magazine");

}

mag.subscribers += delta;

dupdate (&mag, mag_file);

dfunlock (mag_file);

return (NULL);

}

main (argc, argv)

int argc;

char *argv[];

{

form (argc, argv);

exit (0);

}

If this edit routine is stored in a file named sform.c, then the command:

cl -c -AL -Ic:\cbase\include sform.c

cl /F 5000 /Fe..\bin\sform sform.obj /link /NOE c:\cbase\lib\libcbase.lib

creates a new form program named sform that performs the additional editing and file updating. The new form program can be executed with:

sform -qfuad script

This example is used in the demonstration system supplied with C/Base. The source program for sform is stored in \cbase\demo\src\sform.c, and the program is used whenever data entry is done with the subscription screen.