dBASE has methods to easily create data-entry forms but often I would want something more advanced. For example, I might want to allow the user to select a value for a field from a list of search for a value in a popup, or I might want to load data when a field is filled based on the data entered, or I might want the user to be able to press a function key to view some related information such as customer history.
Although some of this is possible using regular dBASE forms, I came up with my own way of creating forms where each field was handled individually.
This allowed me to display information only pertinent to the current field, such as press F1 to look up a value.
It also allowed me to do an action when a field was entered such as show a popup where the user could search or select for a value.
It also allowed me to skip fields that weren't needed based on the value of another field.
To accomplish this, I first need to initialise some settings for the Escape and F10 keys, and then define memory variables to store the forms values temporarily until save.
SET FUNCTION F10 TO CHR(28) SET ESCAPE ON tmp_acc = SPACE(6) tmp_name = SPACE(30) tmp_add = SPACE(30) tmp_city = SPACE(30) tmp_state = SPACE(3) tmp_pcode = SPACE(5)
I also need some memory variables to store the state of the form and the current field.
tmp_dirty = .F. frm_field = 1
We need some procedures to get and say field values. I originally used the same procedure for this and then followed it with either READ or CLEAR GETS depending on whether entering or displaying the form value, but having two procedures adds the option to do different things depending on whether getting or saying, such as showing something when in get mode and then clearing it when finished editing.
PROCEDURE get_field
PARAMETERS fieldno
DO CASE
CASE fieldno = 1
@ 3, 15 GET tmp_acc PICTURE "XXXXXX"
@ 24, 0 SAY "Lookup"
CASE fieldno = 2
@ 4, 15 GET tmp_name PICTURE "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
CASE fieldno = 3
@ 5, 15 GET tmp_add PICTURE "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
CASE fieldno = 4
@ 6, 15 GET tmp_city PICTURE "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
CASE fieldno = 5
@ 7, 15 GET tmp_state PICTURE "XXX"
CASE fieldno = 6
@ 8, 15 GET tmp_pcode PICTURE "XXXXX"
ENDCASE
RETURN
PROCEDURE say_field
PARAMETERS fieldno
DO CASE
CASE fieldno = 1
@ 3, 15 SAY tmp_acc PICTURE "XXXXXX"
@ 24, 0 SAY " "
CASE fieldno = 2
@ 4, 15 SAY tmp_name PICTURE "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
CASE fieldno = 3
@ 5, 15 SAY tmp_add PICTURE "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
CASE fieldno = 4
@ 6, 15 SAY tmp_city PICTURE "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
CASE fieldno = 5
@ 7, 15 SAY tmp_state PICTURE "XXX"
CASE fieldno = 6
@ 8, 15 SAY tmp_pcode PICTURE "XXXXX"
ENDCASE
RETURN
Next step is to display the form and the current values. In this example I call the say procedure for each field, but it would make more sense to do this in a loop if you had more fields than this.
@ 0, 0 CLEAR TO 24, 79 @ 0, 35 SAY "Sample Form" @ 3, 0 SAY "Account:" DO say_field WITH 1 @ 4, 0 SAY "Name:" DO say_field WITH 2 @ 5, 0 SAY "Address:" DO say_field WITH 3 @ 6, 0 SAY "City:" DO say_field WITH 4 @ 7, 0 SAY "State:" DO say_field WITH 5 @ 8, 0 SAY "Postcode:" DO say_field WITH 6 @ 23, 0 SAY "F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 " @ 24, 72 SAY "Save"
Now we need the program loop.
DO WHILE frm_field # 0
*** Pre- field, used to skip conditional fields
DO CASE
*CASE frm_field =
ENDCASE
*** Get field
DO get_field WITH frm_field
*** Read the field
READ
frm_rkey = IIF(READKEY() > 256, READKEY() - 256, READKEY())
frm_fdirty = READKEY() >= 256
tmp_dirty = IIF(frm_fdirty, .T., tmp_dirty)
*** Show the field
DO say_field WITH frm_field
frm_lfield = frm_field && Remember the field just read
*** Handle key used to exit read mode
DO CASE
CASE frm_rkey = 6 && Page up
CASE frm_rkey = 7 && Page down
CASE frm_rkey = 14 && Ctrl+end
*** Go to end of form
frm_field = 6
CASE frm_rkey = 33 && Ctrl_home
*** Go to start of form
frm_field = 1
CASE frm_rkey = 12 && Escape
*** Handle exit without save
IF tmp_dirty = .F.
*** Exit form, no save
RETURN
ENDIF
*** Ask the user first
@ 16, 0 SAY "Are you sure (Y/N)?"
lkey = 0
DO WHILE UPPER(CHR(lkey)) # "Y" .AND. UPPER(CHR(lkey)) # "N" .AND. lkey # 27
lkey = INKEY()
ENDDO
IF UPPER(CHR(lkey)) = "Y"
RETURN
ENDIF
@ 16, 0 CLEAR TO 16, 79
CASE frm_rkey = 36 .AND. LASTKEY() = -9 && F10 key
*** Exit loop to save
frm_field = 0
CASE frm_field = 1 .AND. frm_rkey = 36 .AND. LASTKEY() = 28 && F1 key on account field
*** TODO: Show a list
acc_val = acc_select()
DO CASE
CASE acc_val = 1
tmp_acc = "CS "
frm_fdirty = .T.
CASE acc_val = 2
tmp_acc = "SMITH "
frm_fdirty = .T.
ENDCASE
CASE frm_rkey = 4 && Cursor up / previous field
IF frm_field > 1
frm_field = frm_field - 1
ELSE
@ 0, 0 SAY CHR(7)
ENDIF
CASE frm_rkey = 5 .OR. frm_rkey = 15 .OR. frm_rkey = 16 && Cursor down / enter / next field
IF frm_field < 6
frm_field = frm_field + 1
ELSE
@ 0, 0 SAY CHR(7)
ENDIF
OTHERWISE
*** Unsupported exit key
@ 0, 0 SAY CHR(7)
ENDCASE
*** Handle post-entry of specific fields
DO CASE
CASE frm_lfield = 1 .AND. frm_fdirty = .T.
*** Account field changed
DO CASE
CASE tmp_acc = "CS"
tmp_name = "CASH SALES "
tmp_add = SPACE(30)
tmp_city = SPACE(30)
tmp_state = SPACE(3)
tmp_pcode = SPACE(5)
CASE tmp_acc = "SMITH"
tmp_name = "JOHN SMITH "
tmp_add = "1 SMITH STREET "
tmp_city = "SPRINGWOOD "
tmp_state = "NSW"
tmp_pcode = "2056 "
OTHERWISE
tmp_acc = SPACE(6)
tmp_name = SPACE(30)
tmp_add = SPACE(30)
tmp_city = SPACE(30)
tmp_state = SPACE(3)
tmp_pcode = SPACE(5)
ENDCASE
*** Redisplay customer information
DO say_field WITH 1
DO say_field WITH 2
DO say_field WITH 3
DO say_field WITH 4
DO say_field WITH 5
DO say_field WITH 6
DO say_field WITH 7
ENDCASE
ENDDO
*** If execution gets to here then the user pressed save
RETURN
This first thing in the loop is an optional DO CASE that is useful for skipping conditional fields.
Next we call the get function and wait for user input and then store the key used to exit, and track whether there was a change to the value.
We then call the say function and store the current field as it may change, but we may need to reset, such as if an invalid value was entered.
Next we have a DO CASE to handle key presses. We need to handle moving forward and backward, as well as escape to exit the form, and in this case F10 to save the form. We also need to handle function key presses in specific fields.
Finally we have a DO CASE to handle form input. In this example we look up a customer based on an account number entered. Obviously in an actual application this wouldn't just be hard-coded like in the example, but look up the customer in a database table.
After the main loop, if the loop was EXITed then this means the user pressed save, so we take care of saving the memory variables to the database.
For my example I also need a function to select a customer. Again, this is just a simple example and would normally get data from the database.
FUNCTION acc_select
acc_bar = 0
DEFINE POPUP acc_popup FROM 4, 15 to 8, 35
DEFINE BAR 1 OF acc_popup PROMPT "CS "
DEFINE BAR 2 OF acc_popup PROMPT "SMITH "
ON SELECTION POPUP acc_popup DO acc_sel
ACTIVATE POPUP acc_popup
acc_bar = BAR()
DEACTIVATE POPUP
RELEASE POPUP acc_popup
RETURN acc_bar
PROCEDURE acc_sel
DEACTIVATE POPUP
RETURN
Much of this also works with earlier versions of dBASE, except I couldn't find a way to use any of the function keys except F1.
The forms I created back in the 1990s were quite a bit more complex that this example but used the same techniques. I also created some more complex forms like an invoice form which included a paginated list of invoice items. I will cover these type of forms in a later post.
I originally came up with this type of form on my own back in the early 1990s, but I've recently came across a book on the Internet Archive that uses a similar technique, except the form definition is stored in a database. The book is dBASE IV Power Tools by Malcolm C. Rubel, from 1989.


Comments
There are no comments yet. Be the first to leave a comment!