Commit inicial: fuentes MBS ERP (Genero 6) + .gitignore + docs/SETUP_PC_MBS.md

This commit is contained in:
2026-08-18 20:59:52 -04:00
commit 454973e269
5978 changed files with 2664094 additions and 0 deletions
@@ -0,0 +1,169 @@
# Property of Four Js*
# (c) Copyright Four Js 1995, 2019. All Rights Reserved.
# * Trademark of Four Js Development Tools Europe Ltd
# in the United States and elsewhere
#
# Four Js and its suppliers do not warrant or guarantee that these
# samples are accurate and suitable for your purposes. Their inclusion is
# purely for information purposes only.
# Included in other demos to simplify compilation / link
IMPORT FGL MsgBox
FUNCTION select_city(prefix)
DEFINE prefix STRING
DEFINE filter STRING
DEFINE r SMALLINT
DEFINE name, state STRING
DEFINE cityarr DYNAMIC ARRAY OF RECORD
city_id INTEGER,
city_name VARCHAR(50),
city_state CHAR(2)
END RECORD
OPEN WINDOW w_city WITH FORM "CityList"
DIALOG ATTRIBUTES(UNBUFFERED, FIELD ORDER FORM)
INPUT BY NAME filter
END INPUT
DISPLAY ARRAY cityarr TO sr.*
END DISPLAY
BEFORE DIALOG
IF LENGTH(prefix) < 3 THEN
LET filter = prefix || "*"
ELSE
LET filter = "*"
END IF
CALL select_city_fill(DIALOG,filter,cityarr)
CALL DIALOG.setActionActive("accept", DIALOG.getCurrentRow("sr")>0)
ON ACTION filter
CALL select_city_fill(DIALOG,filter,cityarr)
CALL DIALOG.setActionActive("accept", DIALOG.getCurrentRow("sr")>0)
ON ACTION accept
LET name = cityarr[DIALOG.getCurrentRow("sr")].city_name
LET state = cityarr[DIALOG.getCurrentRow("sr")].city_state
LET r = 0
EXIT DIALOG
ON ACTION cancel
LET r = -1
EXIT DIALOG
END DIALOG
CLOSE WINDOW w_city
RETURN r, name, state
END FUNCTION
FUNCTION select_city_fill(d,filter, cityarr)
DEFINE d ui.Dialog
DEFINE filter STRING
DEFINE cityarr DYNAMIC ARRAY OF RECORD
city_id INTEGER,
city_name VARCHAR(50),
city_state CHAR(2)
END RECORD
DEFINE i INTEGER
LET i = 0
CALL cityarr.clear()
&define ADD_CITY(name, state) \
LET cityarr[i:=i+1].city_id = i \
LET cityarr[i].city_name = name \
LET cityarr[i].city_state = state
ADD_CITY("Austin", "TX")
ADD_CITY("Boston", "MA")
ADD_CITY("Chicago", "IL")
ADD_CITY("Dallas", "TX")
ADD_CITY("Denver", "CO")
ADD_CITY("Detroit", "MI")
ADD_CITY("Houston", "TX")
ADD_CITY("Las Vegas", "NV")
ADD_CITY("Los Angeles", "CA")
ADD_CITY("Miami", "FL")
ADD_CITY("New York", "NY")
ADD_CITY("Philadelphia", "PA")
ADD_CITY("Phoenix", "AZ")
ADD_CITY("Portland", "ME")
ADD_CITY("San Antonio", "TX")
ADD_CITY("San Diego", "CA")
ADD_CITY("San Francisco", "CA")
ADD_CITY("Washington", "DC")
FOR i=1 TO cityarr.getLength()
IF cityarr[i].city_name NOT MATCHES filter THEN
CALL cityarr.deleteElement(i)
LET i = i - 1
END IF
END FOR
CALL d.setCurrentRow("sr",1)
END FUNCTION
FUNCTION combo_fill_states(cb)
DEFINE cb ui.ComboBox
CALL cb.addItem("AK","Alaska")
CALL cb.addItem("AL","Alabama")
CALL cb.addItem("AR","Arkansas")
CALL cb.addItem("AZ","Arizona")
CALL cb.addItem("CA","California")
CALL cb.addItem("CO","Colorado")
CALL cb.addItem("CT","Connecticut")
CALL cb.addItem("DC","D.C.")
CALL cb.addItem("DE","Delaware")
CALL cb.addItem("FL","Florida")
CALL cb.addItem("GA","Georgia")
CALL cb.addItem("HI","Hawaii")
CALL cb.addItem("IA","Iowa")
CALL cb.addItem("ID","Idaho")
CALL cb.addItem("IL","Illinois")
CALL cb.addItem("IN","Indiana")
CALL cb.addItem("KS","Kansas")
CALL cb.addItem("KY","Kentucky")
CALL cb.addItem("LA","Louisiana")
CALL cb.addItem("MA","Massachusetts")
CALL cb.addItem("MD","Maryland")
CALL cb.addItem("ME","Maine")
CALL cb.addItem("MI","Michigan")
CALL cb.addItem("MN","Minnesota")
CALL cb.addItem("MO","Missouri")
CALL cb.addItem("MS","Mississippi")
CALL cb.addItem("MT","Montana")
CALL cb.addItem("NC","North Carolina")
CALL cb.addItem("ND","North Dakota")
CALL cb.addItem("NE","Nebraska")
CALL cb.addItem("NH","New Hampshire")
CALL cb.addItem("NJ","New Jersey")
CALL cb.addItem("NM","New Mexico")
CALL cb.addItem("NV","Nevada")
CALL cb.addItem("NY","New York")
CALL cb.addItem("OH","Ohio")
CALL cb.addItem("OK","Oklahoma")
CALL cb.addItem("OR","Oregon")
CALL cb.addItem("PA","Pennsylvania")
CALL cb.addItem("PR","Puerto Rico")
CALL cb.addItem("RI","Rhode Island")
CALL cb.addItem("SC","South Carolina")
CALL cb.addItem("SD","South Dakota")
CALL cb.addItem("TN","Tennessee")
CALL cb.addItem("TX","Texas")
CALL cb.addItem("UT","Utah")
CALL cb.addItem("VA","Virginia")
CALL cb.addItem("VT","Vermont")
CALL cb.addItem("WA","Washington")
CALL cb.addItem("WI","Wisconsin")
CALL cb.addItem("WV","West Virginia")
CALL cb.addItem("WY","Wyoming")
END FUNCTION
@@ -0,0 +1,43 @@
# Property of Four Js*
# (c) Copyright Four Js 1995, 2019. All Rights Reserved.
# * Trademark of Four Js Development Tools Europe Ltd
# in the United States and elsewhere
#
# Four Js and its suppliers do not warrant or guarantee that these
# samples are accurate and suitable for your purposes. Their inclusion is
# purely for information purposes only.
ACTION DEFAULTS
ACTION filter (ACCELERATOR = CONTROL-ENTER, ACCELERATOR2 = CONTROL-RETURN)
END
LAYOUT ( STYLE = "dialog4" )
GRID
{
Filter: [f ]
<t t1 >
Id City State
[id |name |st ]
[id |name |st ]
[id |name |st ]
[id |name |st ]
[id |name |st ]
[id |name |st ]
< >
}
END
END
ATTRIBUTES
BUTTONEDIT f = FORMONLY.filter TYPE CHAR, ACTION=filter, IMAGE="filter",
COMMENT = "Presse Control-Enter to apply filter";
TABLE t1 : COMMENT = "Double-click to select an item from the list";
EDIT id = FORMONLY.city_id TYPE INTEGER;
EDIT name = FORMONLY.city_name TYPE CHAR;
EDIT st = FORMONLY.city_state TYPE CHAR;
END
INSTRUCTIONS
SCREEN RECORD sr (FORMONLY.city_id, FORMONLY.city_name, FORMONLY.city_state);
END
@@ -0,0 +1,490 @@
# Property of Four Js*
# (c) Copyright Four Js 1995, 2019. All Rights Reserved.
# * Trademark of Four Js Development Tools Europe Ltd
# in the United States and elsewhere
#
# Four Js and its suppliers do not warrant or guarantee that these
# samples are accurate and suitable for your purposes. Their inclusion is
# purely for information purposes only.
IMPORT FGL CityList
IMPORT FGL OrderInput
IMPORT FGL MsgBox
GLOBALS "Globals.4gl"
DEFINE cust_curr INTEGER
DEFINE cust, old_cust custrec
DEFINE cust_state SMALLINT
CONSTANT STATE_NAVIGATE = 0
CONSTANT STATE_MODIFY = 1
CONSTANT STATE_APPEND = 2
CONSTANT otitle = "Customer"
MAIN
DEFINE ordlist_lastrow INTEGER
OPTIONS INPUT WRAP
OPEN FORM f_cust FROM "CustOrders"
DISPLAY FORM f_cust
DIALOG ATTRIBUTES(FIELD ORDER FORM, UNBUFFERED)
INPUT cust.* FROM custrec.* ATTRIBUTES(WITHOUT DEFAULTS)
ON CHANGE custrec.cust_name
IF NOT cust_check_name(DIALOG) THEN NEXT FIELD CURRENT END IF
ON CHANGE custrec.cust_address
IF NOT cust_check_address(DIALOG) THEN NEXT FIELD CURRENT END IF
ON CHANGE custrec.cust_state
INITIALIZE cust.cust_city, cust.cust_zipcode, cust.cust_address TO NULL
END INPUT
DISPLAY ARRAY orders TO ordlist.*
BEFORE ROW
MESSAGE SFMT("Order record %1 / %2", DIALOG.getCurrentRow("ordlist"), DIALOG.getArrayLength("ordlist"))
AFTER ROW
LET ordlist_lastrow = arr_curr()
END DISPLAY
BEFORE DIALOG
CALL cust_loadlist(DIALOG,100)
CALL cust_move_to(DIALOG, 1)
ON ACTION zoom_city
CALL cust_zoom_city(DIALOG)
ON ACTION cust_query
CALL cust_query(DIALOG)
ON ACTION cust_first
CALL cust_move_to(DIALOG, 1)
ON ACTION cust_previous
CALL cust_move_to(DIALOG, cust_curr - 1)
ON ACTION cust_next
CALL cust_move_to(DIALOG, cust_curr + 1)
ON ACTION cust_last
CALL cust_move_to(DIALOG, customers.getLength())
ON ACTION dialogtouched
CALL cust_touched(DIALOG)
ON ACTION cust_append
IF NOT cust_append(DIALOG) THEN CONTINUE DIALOG END IF
NEXT FIELD cust_name
ON ACTION cust_delete -- VALIDATE=NO
CALL cust_delete(DIALOG)
DISPLAY BY NAME cust.*
ON ACTION cust_undo -- VALIDATE=NO
CALL cust_undo(DIALOG)
DISPLAY BY NAME cust.*
ON ACTION cust_save
IF NOT cust_save(DIALOG) THEN CONTINUE DIALOG END IF
ON ACTION ord_append
CALL ord_append(DIALOG)
ON ACTION ord_modify
CALL ord_modify(DIALOG)
ON ACTION ord_delete
CALL ord_delete(DIALOG)
ON ACTION close
IF cust_check_changed(DIALOG) THEN EXIT DIALOG END IF
END DIALOG
END MAIN
FUNCTION cust_zoom_city(d)
DEFINE d ui.Dialog
DEFINE s INTEGER
DEFINE name, state STRING
CALL select_city(d.getFieldBuffer("cust_city")) RETURNING s, name, state
IF s==0 THEN
LET cust.cust_city = name
LET cust.cust_state = state
CALL d.setFieldTouched("cust_city",TRUE)
CALL d.setFieldTouched("cust_state",TRUE)
CALL cust_touched(d)
END IF
END FUNCTION
FUNCTION cust_touched(d)
DEFINE d ui.Dialog
LET cust_state = STATE_MODIFY
CALL cust_setup(d)
END FUNCTION
FUNCTION cust_check_changed(d)
DEFINE d ui.Dialog
DEFINE r INTEGER
IF cust_modifying() THEN
CALL cust_setup(d)
LET r = mbox_ync(otitle,"Do you want to save your changes?","question")
IF r == 1 THEN
IF NOT cust_save(d) THEN LET r = 3 END IF
END IF
IF r == 2 THEN
CALL cust_clean(d)
END IF
RETURN (r == 1 OR r == 2)
END IF
RETURN TRUE
END FUNCTION
FUNCTION cust_modifying()
RETURN cust_state != STATE_NAVIGATE
END FUNCTION
FUNCTION cust_undo(d)
DEFINE d ui.Dialog
CASE cust_state
WHEN STATE_MODIFY
IF NOT mbox_yn(otitle,"Undo customer modification?","question") THEN RETURN END IF
LET cust.* = old_cust.*
CALL cust_clean(d)
WHEN STATE_APPEND
IF NOT mbox_yn(otitle,"Cancel customer creation?","question") THEN RETURN END IF
CALL customers.deleteElement(customers.getLength())
CALL cust_clean(d)
CALL cust_move_to(d, customers.getLength())
END CASE
END FUNCTION
FUNCTION cust_verify_address_format(value)
DEFINE value STRING
DEFINE num STRING, p,x INTEGER
IF value IS NULL THEN RETURN TRUE END IF
LET p = value.getIndexOf(",", 1)
IF p == 0 THEN RETURN FALSE END IF
LET num = value.subString(1, p)
LET x = num
IF x <= 0 THEN RETURN FALSE END IF
IF value.getLength() <= p THEN RETURN FALSE END IF
RETURN TRUE
END FUNCTION
FUNCTION cust_check_name(d)
DEFINE d ui.Dialog
IF LENGTH(cust.cust_name) = 0 THEN
CALL mbox_ok(otitle,"You must specify a customer name.","stop")
RETURN FALSE
END IF
IF cust.cust_name MATCHES "*[@#$%^&]*" THEN
CALL mbox_ok(otitle,"The customer name containts invalid characters.","stop")
RETURN FALSE
END IF
RETURN TRUE
END FUNCTION
FUNCTION cust_check_address(d)
DEFINE d ui.Dialog
IF NOT cust_verify_address_format(cust.cust_address) THEN
CALL mbox_ok(otitle,"Address must hold a number followed by a comma and the street name.","stop")
RETURN FALSE
END IF
RETURN TRUE
END FUNCTION
FUNCTION cust_check_city(d)
DEFINE d ui.Dialog
-- Inter-dependent field validation
IF cust.cust_city IS NULL AND cust.cust_zipcode IS NOT NULL THEN
CALL mbox_ok(otitle,"You must specify a city because zipcode is not null.","stop")
RETURN FALSE
END IF
RETURN TRUE
END FUNCTION
FUNCTION cust_check_data(d)
DEFINE d ui.Dialog
IF d.validate("custrec.*") < 0 THEN
RETURN "*"
END IF
IF NOT cust_check_name(d) THEN RETURN "custrec.cust_name" END IF
IF NOT cust_check_city(d) THEN RETURN "custrec.cust_city" END IF
IF NOT cust_check_address(d) THEN RETURN "custrec.cust_address" END IF
RETURN NULL -- data is valid
END FUNCTION
FUNCTION cust_check_fields(d)
DEFINE d ui.Dialog
DEFINE fn STRING
LET fn = cust_check_data(d)
IF fn IS NULL THEN RETURN TRUE END IF
IF fn != "*" THEN CALL d.nextField(fn) END IF
RETURN FALSE
END FUNCTION
FUNCTION cust_save(d)
DEFINE d ui.Dialog
IF NOT cust_check_fields(d) THEN RETURN FALSE END IF
IF cust_state == STATE_APPEND THEN
-- SQL INSERT ...
END IF
IF cust_state == STATE_MODIFY THEN
-- SQL UPDATE ...
END IF
LET customers[cust_curr].* = cust.*
CALL cust_clean(d)
CALL mbox_ok("Customers", "Changes saved to database", "information")
CALL d.setFieldTouched("custrec.*",FALSE)
CALL cust_setup(d)
RETURN TRUE
END FUNCTION
FUNCTION cust_append(d)
DEFINE d ui.Dialog
IF NOT cust_autosave(d) THEN RETURN FALSE END IF
INITIALIZE cust.* TO NULL
CALL ord_loadlist(d,0,0)
LET cust.cust_id = cust_curr + 101044
LET cust_state = STATE_APPEND
CALL customers.appendElement()
LET cust_curr = customers.getLength()
CALL cust_setup(d)
RETURN TRUE
END FUNCTION
FUNCTION cust_delete(d)
DEFINE d ui.Dialog
IF cust_curr<= 0 THEN RETURN END IF
IF cust_modifying() THEN
CALL cust_setup(d)
IF NOT mbox_yn(otitle,"Cancel current changes?","question") THEN RETURN END IF
CALL cust_clean(d)
ELSE
IF NOT mbox_yn(otitle,"Delete current customer?","question") THEN RETURN END IF
END IF
-- SQL DELETE ...
CALL customers.deleteElement(cust_curr)
IF customers.getLength()==0 THEN
CALL mbox_ok(otitle,"No more customers in list, switching to query mode","stop")
CALL cust_query(d)
RETURN
END IF
IF cust_curr>customers.getLength() THEN
LET cust_curr=customers.getLength()
END IF
CALL cust_move_to(d,cust_curr)
CALL cust_setup(d)
END FUNCTION
FUNCTION cust_query(d)
DEFINE d ui.Dialog
DEFINE sql STRING
IF NOT cust_check_changed(d) THEN RETURN END IF
DISPLAY "Enter customer search criterias" TO cust_info
WHILE TRUE
LET int_flag = FALSE
CONSTRUCT BY NAME sql ON cust_id, cust_name, cust_state,
cust_city, cust_zipcode, cust_address, cust_checked
ATTRIBUTES( CANCEL=FALSE, ACCEPT=FALSE )
ON ACTION close
IF mbox_yn(otitle,"Quit application?","question") THEN EXIT PROGRAM END IF
ON ACTION cust_fetch
ACCEPT CONSTRUCT
END CONSTRUCT
IF NOT int_flag THEN
-- Dummy query, should use database query instead.
CALL cust_loadlist(d,LENGTH(sql))
CALL cust_setup(d)
CALL mbox_ok(otitle,SFMT("Found %1 customer records in database",customers.getLength()),"information")
EXIT WHILE
END IF
END WHILE
END FUNCTION
FUNCTION cust_setup(d)
DEFINE d ui.Dialog
DEFINE has_rows, on_first, on_last, cust_ro INTEGER
LET has_rows = (cust_curr > 0 AND customers.getLength()>0)
LET on_first = (cust_curr == 1)
LET on_last = (cust_curr == customers.getLength())
LET cust_ro = (has_rows AND cust_state == STATE_NAVIGATE)
CALL d.setActionActive("dialogtouched", cust_ro)
CALL d.setActionActive("cust_undo", NOT cust_ro)
CALL d.setActionActive("cust_save", NOT cust_ro)
CALL d.setActionActive("cust_append", TRUE) -- always active
CALL d.setActionActive("cust_delete", has_rows) -- can delete creation
CALL d.setActionActive("cust_query", TRUE) -- always active
CALL d.setActionActive("cust_first", NOT on_first)
CALL d.setActionActive("cust_previous", NOT on_first)
CALL d.setActionActive("cust_next", NOT on_last)
CALL d.setActionActive("cust_last", NOT on_last)
CALL d.setActionActive("ord_append", cust_ro)
CALL d.setActionActive("ord_modify", cust_ro)
CALL d.setActionActive("ord_delete", cust_ro)
CASE cust_state
WHEN STATE_APPEND
DISPLAY "Creating a new customer record (save with ctrl-s)" TO cust_info
WHEN STATE_MODIFY
DISPLAY "Modifying current customer record (save with ctrl-s)" TO cust_info
OTHERWISE
DISPLAY SFMT("Customer record %1 / %2",cust_curr,customers.getLength()) TO cust_info
END CASE
END FUNCTION
FUNCTION cust_clean(d)
DEFINE d ui.Dialog
LET old_cust.* = cust.*
LET cust_state = STATE_NAVIGATE
CALL cust_setup(d)
END FUNCTION
FUNCTION cust_autosave(d)
DEFINE d ui.Dialog
IF cust_modifying() THEN
IF NOT cust_save(d) THEN RETURN FALSE END IF
END IF
RETURN TRUE
END FUNCTION
FUNCTION cust_move_to(d,index)
DEFINE d ui.Dialog
DEFINE index INTEGER
IF NOT cust_autosave(d) THEN RETURN END IF
IF index > customers.getLength() THEN
LET index = customers.getLength()
END IF
IF index < 1 THEN
LET index = 1
END IF
LET cust.* = customers[index].*
LET old_cust.* = cust.*
LET cust_curr = index
MESSAGE SFMT("Customer: %1/%2", cust_curr, customers.getLength())
CALL ord_loadlist(d, customers[index].cust_id, (index mod 5) * 3)
CALL d.setCurrentRow("ordlist",1)
CALL cust_setup(d)
END FUNCTION
-- Dummy function to fill the list of customers (normally, SELECT from database)
FUNCTION cust_loadlist(d,m)
DEFINE d ui.Dialog
DEFINE m,i INTEGER
CALL customers.clear()
FOR i=1 TO m
LET customers[i].cust_id = i
LET customers[i].cust_name = "Name " || i
LET customers[i].cust_state = "CA"
LET customers[i].cust_city = "San Fransisco"
LET customers[i].cust_zipcode = 94102
LET customers[i].cust_address = "52, Marlon Street"
LET customers[i].cust_checked = i mod 2
END FOR
IF customers.getLength()>0 THEN
LET cust_curr = 1
LET cust.* = customers[cust_curr].*
CALL ord_loadlist(d, customers[cust_curr].cust_id, (cust_curr mod 5) * 3)
ELSE
LET cust_curr = 0
INITIALIZE cust.* TO NULL
CALL ord_loadlist(d, 0, 0)
END IF
CALL cust_clean(d)
END FUNCTION
FUNCTION ord_loadlist(d,mid,m)
DEFINE d ui.Dialog
DEFINE mid,m,i INTEGER
CALL orders.clear()
FOR i=1 TO m
CALL orders.appendElement()
LET orders[i].ord_id = mid * 100 + i
LET orders[i].ord_crea = CURRENT YEAR TO SECOND
LET orders[i].ord_amount = ( i mod 5 ) * 133.56
END FOR
END FUNCTION
FUNCTION ord_append(d)
DEFINE d ui.Dialog
IF NOT cust_check_changed(d) THEN RETURN END IF
CALL ord_input('A', cust.cust_id, cust.cust_name, 0)
END FUNCTION
FUNCTION ord_modify(d)
DEFINE d ui.Dialog
IF NOT cust_check_changed(d) THEN RETURN END IF
IF d.getCurrentRow("ordlist")==0 THEN RETURN END IF
CALL ord_input('M', cust.cust_id, cust.cust_name, orders[d.getCurrentRow("ordlist")].ord_id)
END FUNCTION
FUNCTION ord_delete(d)
DEFINE d ui.Dialog
IF NOT cust_check_changed(d) THEN RETURN END IF
IF NOT mbox_yn(otitle,"Delete select order from database?","question") THEN
RETURN
END IF
CALL d.deleteRow("ordlist",d.getCurrentRow("ordlist"))
END FUNCTION
FUNCTION combo_fill_states(cb)
DEFINE cb ui.ComboBox
CALL cb.addItem("AK","Alaska")
CALL cb.addItem("AL","Alabama")
CALL cb.addItem("AR","Arkansas")
CALL cb.addItem("AZ","Arizona")
CALL cb.addItem("CA","California")
CALL cb.addItem("CO","Colorado")
CALL cb.addItem("CT","Connecticut")
CALL cb.addItem("DC","D.C.")
CALL cb.addItem("DE","Delaware")
CALL cb.addItem("FL","Florida")
CALL cb.addItem("GA","Georgia")
CALL cb.addItem("HI","Hawaii")
CALL cb.addItem("IA","Iowa")
CALL cb.addItem("ID","Idaho")
CALL cb.addItem("IL","Illinois")
CALL cb.addItem("IN","Indiana")
CALL cb.addItem("KS","Kansas")
CALL cb.addItem("KY","Kentucky")
CALL cb.addItem("LA","Louisiana")
CALL cb.addItem("MA","Massachusetts")
CALL cb.addItem("MD","Maryland")
CALL cb.addItem("ME","Maine")
CALL cb.addItem("MI","Michigan")
CALL cb.addItem("MN","Minnesota")
CALL cb.addItem("MO","Missouri")
CALL cb.addItem("MS","Mississippi")
CALL cb.addItem("MT","Montana")
CALL cb.addItem("NC","North Carolina")
CALL cb.addItem("ND","North Dakota")
CALL cb.addItem("NE","Nebraska")
CALL cb.addItem("NH","New Hampshire")
CALL cb.addItem("NJ","New Jersey")
CALL cb.addItem("NM","New Mexico")
CALL cb.addItem("NV","Nevada")
CALL cb.addItem("NY","New York")
CALL cb.addItem("OH","Ohio")
CALL cb.addItem("OK","Oklahoma")
CALL cb.addItem("OR","Oregon")
CALL cb.addItem("PA","Pennsylvania")
CALL cb.addItem("PR","Puerto Rico")
CALL cb.addItem("RI","Rhode Island")
CALL cb.addItem("SC","South Carolina")
CALL cb.addItem("SD","South Dakota")
CALL cb.addItem("TN","Tennessee")
CALL cb.addItem("TX","Texas")
CALL cb.addItem("UT","Utah")
CALL cb.addItem("VA","Virginia")
CALL cb.addItem("VT","Vermont")
CALL cb.addItem("WA","Washington")
CALL cb.addItem("WI","Wisconsin")
CALL cb.addItem("WV","West Virginia")
CALL cb.addItem("WY","Wyoming")
END FUNCTION
@@ -0,0 +1,133 @@
# Property of Four Js*
# (c) Copyright Four Js 1995, 2019. All Rights Reserved.
# * Trademark of Four Js Development Tools Europe Ltd
# in the United States and elsewhere
#
# Four Js and its suppliers do not warrant or guarantee that these
# samples are accurate and suitable for your purposes. Their inclusion is
# purely for information purposes only.
ACTION DEFAULTS
ACTION cust_query ( IMAGE="find", COMMENT = "Query customers", ACCELERATOR = control-q )
ACTION cust_append ( IMAGE="new", COMMENT = "Create new customer", ACCELERATOR = control-n )
ACTION cust_undo ( IMAGE="refresh", COMMENT = "Undo last changes", ACCELERATOR = control-u, VALIDATE = NO )
ACTION cust_save ( IMAGE="disk", COMMENT = "Save changes in database", ACCELERATOR = control-s )
ACTION cust_delete ( IMAGE="eraser", COMMENT = "Delete current customer", ACCELERATOR = control-d, VALIDATE = NO )
ACTION cust_fetch ( IMAGE="services", COMMENT = "Search records in database", ACCELERATOR = enter, ACCELERATOR2 = return )
ACTION cust_first ( IMAGE = "gobegin", COMMENT = "Move to first customer in list")
ACTION cust_previous ( IMAGE = "gorev", COMMENT = "Move to previous customer in list")
ACTION cust_next ( IMAGE = "goforw", COMMENT = "Move to next customer in list")
ACTION cust_last ( IMAGE = "goend", COMMENT = "Move to last customer in list")
ACTION cust_print ( IMAGE="printer", COMMENT = "Print customer")
ACTION ord_append ( IMAGE = "new", COMMENT="Add a new order")
ACTION ord_modify ( IMAGE = "pen", COMMENT="Modify selected order")
ACTION ord_delete ( IMAGE = "eraser", COMMENT="Delete selected order")
END
TOPMENU
GROUP cust ( TEXT="Customers" )
COMMAND cust_query ( TEXT="Query" )
COMMAND cust_append ( TEXT="New" )
COMMAND cust_undo ( TEXT="Undo" )
COMMAND cust_save ( TEXT="Save" )
COMMAND cust_print ( TEXT="Print" )
SEPARATOR
COMMAND cust_fetch ( TEXT="Fetch" )
COMMAND cancel
SEPARATOR
COMMAND cust_first ( TEXT="First" )
COMMAND cust_previous ( TEXT="Previous" )
COMMAND cust_next ( TEXT="Next" )
COMMAND cust_last ( TEXT="Last" )
SEPARATOR
COMMAND close ( TEXT="Close" )
END
GROUP ord ( TEXT="Orders" )
COMMAND ord_append ( TEXT="Append" )
COMMAND ord_modify ( TEXT="Modify" )
COMMAND ord_delete ( TEXT="Delete" )
END
GROUP help ( TEXT="Help" )
COMMAND help
SEPARATOR
COMMAND about ( TEXT="About" )
END
END
LAYOUT ( STYLE="main2", TEXT = "Customers and Orders" )
GRID
{
<g g1 >
[f1 ][f2 ]
State: [f3 ] [f4 ]
City: [f5 ]
Zipcode: [f6 ]
Address: [f7 ]
[ ]
-------------------------------------------------------------------
[tr |cq:cx:cf:cp:cn:cl:ca:cd:cu:cs]
< >
<g g2 >
<t t1 >[oad]
[c1 |c2 |c3 ][omo]
[c1 |c2 |c3 ][ode]
[c1 |c2 |c3 ]
[c1 |c2 |c3 ]
[c1 |c2 |c3 ]
[c1 |c2 |c3 ]
[c1 |c2 |c3 ]
[c1 |c2 |c3 ]
[c1 |c2 |c3 ]
[c1 |c2 |c3 ]
< >
< >
}
END
END
ATTRIBUTES
GROUP g1: TEXT = "Customer information";
EDIT f1 = FORMONLY.cust_id TYPE INTEGER, NOT NULL, NOENTRY;
EDIT f2 = FORMONLY.cust_name TYPE VARCHAR, NOT NULL, REQUIRED;
COMBOBOX f3 = FORMONLY.cust_state TYPE VARCHAR, NOT NULL, REQUIRED,
QUERYEDITABLE, DEFAULT="CA", INITIALIZER=combo_fill_states;
CHECKBOX f4 = FORMONLY.cust_checked TYPE CHAR, TEXT="Checked";
BUTTONEDIT f5 = FORMONLY.cust_city TYPE CHAR, NOT NULL, REQUIRED,
ACTION=zoom_city, IMAGE="find";
EDIT f6 = FORMONLY.cust_zipcode TYPE VARCHAR, NOT NULL, REQUIRED,
COMMENT="Possible values: 10000 to 99999",
INCLUDE=(10000 to 99999);
TEXTEDIT f7 = FORMONLY.cust_address TYPE CHAR,
COMMENT="Must hold '<number>, <street name>'";
LABEL tr = FORMONLY.cust_info, STYLE="information";
BUTTON cq: cust_query, TABINDEX=0;
BUTTON cx: cust_fetch, TABINDEX=0;
BUTTON cf: cust_first, TABINDEX=0;
BUTTON cp: cust_previous, TABINDEX=0;
BUTTON cn: cust_next, TABINDEX=0;
BUTTON cl: cust_last, TABINDEX=0;
BUTTON ca: cust_append, TABINDEX=0;
BUTTON cd: cust_delete, TABINDEX=0;
BUTTON cu: cust_undo, TABINDEX=0;
BUTTON cs: cust_save, TABINDEX=0;
GROUP g2: TEXT = "Orders";
TABLE t1: DOUBLECLICK=ord_modify;
EDIT c1 = FORMONLY.ord_id TYPE INTEGER, TITLE = "Id";
EDIT c2 = FORMONLY.ord_crea TYPE DATE, TITLE = "Creation";
EDIT c3 = FORMONLY.ord_amount TYPE MONEY(15,2), TITLE = "Amount";
BUTTON oad: ord_append, TABINDEX=0;
BUTTON omo: ord_modify, TABINDEX=0;
BUTTON ode: ord_delete, TABINDEX=0;
END
INSTRUCTIONS
SCREEN RECORD custrec (FORMONLY.cust_id THROUGH FORMONLY.cust_address);
SCREEN RECORD ordlist (FORMONLY.ord_id THROUGH FORMONLY.ord_amount);
END
@@ -0,0 +1,432 @@
# Property of Four Js*
# (c) Copyright Four Js 1995, 2019. All Rights Reserved.
# * Trademark of Four Js Development Tools Europe Ltd
# in the United States and elsewhere
#
# Four Js and its suppliers do not warrant or guarantee that these
# samples are accurate and suitable for your purposes. Their inclusion is
# purely for information purposes only.
IMPORT FGL CityList
IMPORT FGL OrderInput
IMPORT FGL MsgBox
GLOBALS "Globals.4gl"
DEFINE cust, old_cust custrec
DEFINE cust_curr INT -- current position in the array (scroll cursor)
DEFINE cust_touched INT -- set if something in the customer record was changed
DEFINE cust_new INT -- > 0 if new customer is currently edited
CONSTANT mytitle = "Customer"
CONSTANT S_MOVE = "move"
CONSTANT S_NEW = "new"
CONSTANT S_CLOSE = "close"
CONSTANT S_QUERY = "query"
CONSTANT S_SAVE = "save"
MAIN
DEFINE state STRING -- requested state in the dialog
DEFINE req_pos INT -- required position in the dialog
DEFINE del INT -- delete flag
OPTIONS INPUT WRAP
OPEN FORM f_cust FROM "CustOrders2"
DISPLAY FORM f_cust
CALL cust_loadlist(9)
LET state=S_MOVE
LET cust_curr=1
WHILE state<>S_CLOSE
DISPLAY "WHILE,state=",state,",cust_curr():",cust_curr,",req_pos:",req_pos
IF cust_len() == 0 OR state == S_NEW THEN
LET cust_curr=cust_len()+1
LET cust_new=cust_curr
ELSE
LET cust_new=0
CALL cust_fetch(req_pos)
END IF
LET state=S_MOVE
LET req_pos=cust_curr
LET cust_touched=0
DIALOG ATTRIBUTES(FIELD ORDER FORM, UNBUFFERED)
INPUT cust.* FROM custrec.* ATTRIBUTES(WITHOUT DEFAULTS=NOT cust_new)
AFTER FIELD cust_name
IF cust.cust_name MATCHES "*[@#$%^&]*" THEN
ERROR "The customer name contains invalid characters."
NEXT FIELD cust_name
END IF
AFTER FIELD cust_address
IF NOT cust_check_address(cust.cust_address) THEN
NEXT FIELD cust_address
END IF
AFTER INPUT
IF cust.cust_city IS NULL AND cust.cust_zipcode IS NOT NULL THEN
CALL mbox_ok(mytitle,"You must specify a city because zipcode is not null.","stop")
NEXT FIELD cust_city
END IF
END INPUT
DISPLAY ARRAY orders TO ordlist.*
BEFORE DISPLAY
IF cust_new THEN
CALL mbox_ok(mytitle,"Please save the new customer before adding orders!","stop")
NEXT FIELD cust_name
END IF
BEFORE ROW
MESSAGE SFMT("Order record %1 / %2", DIALOG.getCurrentRow("ordlist"), DIALOG.getArrayLength("ordlist"))
ON ACTION ord_append
CALL ord_append(DIALOG)
ON ACTION ord_modify
CALL ord_modify(DIALOG)
ON ACTION ord_delete
CALL ord_delete(DIALOG)
END DISPLAY
BEFORE DIALOG
CALL cust_setup(DIALOG)
CALL order_of_cust(cust_curr)
IF cust_new THEN
LET old_cust.* = cust.*
END IF
ON ACTION zoom_city
CALL cust_zoom_city(DIALOG)
ON ACTION cust_first
LET req_pos=1
LET state=S_MOVE ; ACCEPT DIALOG
ON ACTION cust_previous
LET req_pos=cust_curr-1
LET state=S_MOVE ; ACCEPT DIALOG
ON ACTION cust_next
LET req_pos=cust_curr+1
LET state=S_MOVE ; ACCEPT DIALOG
ON ACTION cust_last
LET req_pos=cust_len()
LET state=S_MOVE ; ACCEPT DIALOG
ON ACTION cust_append
LET state=S_NEW ; ACCEPT DIALOG
ON ACTION cust_query --we can perform a safe query also only after
LET state=S_QUERY ; ACCEPT DIALOG --the validations have been done
ON ACTION cust_delete
IF cust_len() > 0 THEN
LET del=0
IF cust_new AND NOT cust_touched THEN
LET del=1
ELSE
IF mbox_yn(mytitle,"Delete current customer?","question") THEN
LET del=1
END IF
END IF
IF del THEN
CALL customers.deleteElement(cust_curr)
-- normally a
-- IF cust_id>0 THEN
-- DELETE from customers WHERE cust_id=cust.cust_id
-- END IF
LET state=S_MOVE
EXIT DIALOG
END IF
END IF
ON ACTION dialogtouched
LET cust_touched=1
CALL cust_setup(DIALOG)
ON ACTION cust_undo
LET cust.* = old_cust.*
DISPLAY BY NAME cust.*
CALL DIALOG.setFieldTouched("custrec.*",FALSE)
LET cust_touched=0
CALL cust_setup(DIALOG)
ON ACTION cust_save
LET state=S_SAVE ; ACCEPT DIALOG
ON ACTION cancel
LET state=S_CLOSE
--close is a bit difficult: if we did not touch something,
--we can go out safely, otherwise we need to duplicate things
--we do normally in AFTER DIALOG
IF cust_touched THEN
--we touched something
CASE mbox_ync(mytitle,"Save your changes?","question")
WHEN 1 --yes
ACCEPT DIALOG --go thru the validation and fall finally out
--in AFTER DIALOG
WHEN 2 --no
EXIT DIALOG
WHEN 3 --cancel
CONTINUE DIALOG
END CASE
ELSE
--nothing changed, just go out
EXIT DIALOG
END IF
AFTER DIALOG
IF state=S_CLOSE OR state=S_SAVE THEN
--we already confirmed the save, jump to the save code
GOTO savedb
ELSE
IF old_cust.* <> cust.* THEN
CASE mbox_ync(mytitle,"Save your changes?","question")
WHEN 1 --yes
GOTO savedb
WHEN 3 --cancel and stay in the dialog
LET state=S_MOVE ; CONTINUE DIALOG
END CASE
END IF
END IF
EXIT DIALOG
LABEL savedb: --use the label here to save a local var and some IF's
IF cust_new THEN
DISPLAY "INSERT new customer into db"
LET customers[cust_new].* = cust.*
ELSE
DISPLAY "UPDATE customer in db"
LET customers[cust_curr].* = cust.*
END IF
END DIALOG
--we can perform the query action only if everything was validated before
IF state == S_QUERY THEN
CALL cust_query()
END IF
END WHILE
END MAIN
FUNCTION cust_len()
--we use the 'customers' array as a db replacement here
--normally you could return the real COUNT of the DB statement cursor
RETURN customers.getLength()
END FUNCTION
FUNCTION cust_fetch(index)
DEFINE index INTEGER
IF index > cust_len() THEN
LET index = cust_len()
END IF
IF index < 1 THEN
LET index = 1
END IF
--fetch customer from the DB, index could be used to FETCH ABSOLUTE
LET cust.* = customers[index].*
LET old_cust.* = cust.*
LET cust_curr = index
MESSAGE SFMT("Fetched Customer: %1/%2", cust_curr, cust_len())
END FUNCTION
FUNCTION cust_setup(d)
DEFINE d ui.Dialog
DEFINE has_rows, on_first, on_last, cust_ro INTEGER
LET has_rows = (cust_curr > 0 AND cust_len() > 0 )
LET on_first = (cust_curr == 1)
LET on_last = (cust_curr == cust_len())
LET cust_ro = (has_rows AND NOT cust_touched)
CALL d.setActionActive("dialogtouched", cust_ro)
CALL d.setActionActive("cust_undo", NOT cust_ro)
CALL d.setActionActive("cust_save", NOT cust_ro)
CALL d.setActionActive("cust_append", TRUE) -- always active
CALL d.setActionActive("cust_delete", has_rows) -- can delete creation
CALL d.setActionActive("cust_query", TRUE) -- always active
CALL d.setActionActive("cust_first", NOT on_first)
CALL d.setActionActive("cust_previous", NOT on_first)
CALL d.setActionActive("cust_next", NOT on_last)
CALL d.setActionActive("cust_last", NOT on_last)
IF cust_new THEN
DISPLAY "New customer (save with ctrl-s)" TO cust_info
ELSE
IF cust_touched THEN
DISPLAY "Modifying customer (save with ctrl-s)" TO cust_info
ELSE
DISPLAY SFMT("Customer record %1 / %2",cust_curr,cust_len()) TO cust_info
END IF
END IF
END FUNCTION
FUNCTION cust_query()
DEFINE sql STRING
DISPLAY "Enter customer search criterias" TO cust_info
LET int_flag = FALSE
CONSTRUCT BY NAME sql ON cust_id, cust_name, cust_state,
cust_city, cust_zipcode, cust_address, cust_checked
IF NOT int_flag THEN
-- Dummy query, should use database query instead.
CALL cust_loadlist(LENGTH(sql))
CALL mbox_ok(mytitle,SFMT("Found %1 customer records in database",cust_len()),"information")
END IF
END FUNCTION
FUNCTION cust_zoom_city(d)
DEFINE d ui.Dialog
DEFINE s INTEGER
DEFINE name, state STRING
CALL select_city(d.getFieldBuffer("cust_city")) RETURNING s, name, state
IF s==0 THEN
LET cust.cust_city = name
LET cust.cust_state = state
CALL d.setFieldTouched("cust_city",TRUE)
CALL d.setFieldTouched("cust_state",TRUE)
LET cust_touched = TRUE
CALL cust_setup(d)
END IF
END FUNCTION
FUNCTION cust_verify_address_format(value)
DEFINE value STRING
DEFINE num STRING, p,x INTEGER
IF value IS NULL THEN RETURN TRUE END IF
LET p = value.getIndexOf(",", 1)
IF p == 0 THEN RETURN FALSE END IF
LET num = value.subString(1, p)
LET x = num
IF x <= 0 THEN RETURN FALSE END IF
IF value.getLength() <= p THEN RETURN FALSE END IF
RETURN TRUE
END FUNCTION
FUNCTION cust_check_address(value)
DEFINE value STRING
IF NOT cust_verify_address_format(value) THEN
CALL mbox_ok(mytitle,"Address must hold a number followed by a comma and the street name.","stop")
RETURN FALSE
END IF
RETURN TRUE
END FUNCTION
FUNCTION order_of_cust(index)
DEFINE index INTEGER --index should be normally the cust_id
--MESSAGE SFMT("Customer: %1/%2", cust_curr, cust_len())
IF index >= 1 AND index <= cust_len() THEN
CALL ord_loadlist(customers[index].cust_id, (index mod 5) * 3)
ELSE
CALL orders.clear()
END IF
END FUNCTION
-- Dummy function to fill the list of customers (normally, SELECT from database)
FUNCTION cust_loadlist(m)
DEFINE m,i INTEGER
CALL customers.clear()
FOR i=1 TO m
LET customers[i].cust_id = i
LET customers[i].cust_name = "Name " || i
LET customers[i].cust_state = "CA"
LET customers[i].cust_city = "San Fransisco"
LET customers[i].cust_zipcode = 94102
LET customers[i].cust_address = "52, Marlon Street"
LET customers[i].cust_checked = i mod 2
END FOR
END FUNCTION
FUNCTION ord_loadlist(mid,m)
DEFINE mid,m,i INTEGER
CALL orders.clear()
FOR i=1 TO m
CALL orders.appendElement()
LET orders[i].ord_id = mid * 100 + i
LET orders[i].ord_crea = CURRENT YEAR TO SECOND
LET orders[i].ord_amount = ( i mod 5 ) * 133.56
END FOR
END FUNCTION
FUNCTION ord_append(d)
DEFINE d ui.Dialog
CALL ord_input('A', cust.cust_id, cust.cust_name, 0)
END FUNCTION
FUNCTION ord_modify(d)
DEFINE d ui.Dialog
IF d.getCurrentRow("ordlist")==0 THEN RETURN END IF
CALL ord_input('M', cust.cust_id, cust.cust_name, orders[d.getCurrentRow("ordlist")].ord_id)
END FUNCTION
FUNCTION ord_delete(d)
DEFINE d ui.Dialog
IF NOT mbox_yn(mytitle,"Delete select order from database?","question") THEN
RETURN
END IF
CALL d.deleteRow("ordlist",d.getCurrentRow("ordlist"))
END FUNCTION
FUNCTION combo_fill_states(cb)
DEFINE cb ui.ComboBox
CALL cb.addItem("AK","Alaska")
CALL cb.addItem("AL","Alabama")
CALL cb.addItem("AR","Arkansas")
CALL cb.addItem("AZ","Arizona")
CALL cb.addItem("CA","California")
CALL cb.addItem("CO","Colorado")
CALL cb.addItem("CT","Connecticut")
CALL cb.addItem("DC","D.C.")
CALL cb.addItem("DE","Delaware")
CALL cb.addItem("FL","Florida")
CALL cb.addItem("GA","Georgia")
CALL cb.addItem("HI","Hawaii")
CALL cb.addItem("IA","Iowa")
CALL cb.addItem("ID","Idaho")
CALL cb.addItem("IL","Illinois")
CALL cb.addItem("IN","Indiana")
CALL cb.addItem("KS","Kansas")
CALL cb.addItem("KY","Kentucky")
CALL cb.addItem("LA","Louisiana")
CALL cb.addItem("MA","Massachusetts")
CALL cb.addItem("MD","Maryland")
CALL cb.addItem("ME","Maine")
CALL cb.addItem("MI","Michigan")
CALL cb.addItem("MN","Minnesota")
CALL cb.addItem("MO","Missouri")
CALL cb.addItem("MS","Mississippi")
CALL cb.addItem("MT","Montana")
CALL cb.addItem("NC","North Carolina")
CALL cb.addItem("ND","North Dakota")
CALL cb.addItem("NE","Nebraska")
CALL cb.addItem("NH","New Hampshire")
CALL cb.addItem("NJ","New Jersey")
CALL cb.addItem("NM","New Mexico")
CALL cb.addItem("NV","Nevada")
CALL cb.addItem("NY","New York")
CALL cb.addItem("OH","Ohio")
CALL cb.addItem("OK","Oklahoma")
CALL cb.addItem("OR","Oregon")
CALL cb.addItem("PA","Pennsylvania")
CALL cb.addItem("PR","Puerto Rico")
CALL cb.addItem("RI","Rhode Island")
CALL cb.addItem("SC","South Carolina")
CALL cb.addItem("SD","South Dakota")
CALL cb.addItem("TN","Tennessee")
CALL cb.addItem("TX","Texas")
CALL cb.addItem("UT","Utah")
CALL cb.addItem("VA","Virginia")
CALL cb.addItem("VT","Vermont")
CALL cb.addItem("WA","Washington")
CALL cb.addItem("WI","Wisconsin")
CALL cb.addItem("WV","West Virginia")
CALL cb.addItem("WY","Wyoming")
END FUNCTION
@@ -0,0 +1,105 @@
# Property of Four Js*
# (c) Copyright Four Js 1995, 2019. All Rights Reserved.
# * Trademark of Four Js Development Tools Europe Ltd
# in the United States and elsewhere
#
# Four Js and its suppliers do not warrant or guarantee that these
# samples are accurate and suitable for your purposes. Their inclusion is
# purely for information purposes only.
ACTION DEFAULTS
ACTION close(VALIDATE=NO)
ACTION cancel(VALIDATE=NO)
ACTION cust_query ( IMAGE="find", COMMENT = "Query customers", ACCELERATOR = control-q )
ACTION cust_append ( IMAGE="new", COMMENT = "Create new customer", ACCELERATOR = control-n )
ACTION cust_undo ( IMAGE="refresh", COMMENT = "Undo last changes", ACCELERATOR = control-u, VALIDATE = NO )
ACTION cust_save ( IMAGE="disk", COMMENT = "Save changes in database", ACCELERATOR = control-s )
ACTION cust_delete ( IMAGE="eraser", COMMENT = "Delete current customer", ACCELERATOR = control-d, VALIDATE = NO )
ACTION cust_first ( IMAGE = "gobegin", COMMENT = "Move to first customer in list")
ACTION cust_previous ( IMAGE = "gorev", COMMENT = "Move to previous customer in list")
ACTION cust_next ( IMAGE = "goforw", COMMENT = "Move to next customer in list")
ACTION cust_last ( IMAGE = "goend", COMMENT = "Move to last customer in list")
ACTION cust_print ( IMAGE="printer", COMMENT = "Print customer")
ACTION ord_append ( IMAGE = "new", COMMENT="Add a new order")
ACTION ord_modify ( IMAGE = "pen", COMMENT="Modify selected order")
ACTION ord_delete ( IMAGE = "eraser", COMMENT="Delete selected order")
END
LAYOUT (TEXT = "Customers and Orders" )
GRID
{
<g g1 >
[f1 ][f2 ]
State: [f3 ] [f4 ]
City: [f5 ]
Zipcode: [f6 ]
Address: [f7 ]
[ ]
-------------------------------------------------------------------
[tr : :cq:cf:cp:cn:cl:ca:cd:cu:cs]
< >
<g g2 >
<t t1 >[oad]
[c1 |c2 |c3 ][omo]
[c1 |c2 |c3 ][ode]
[c1 |c2 |c3 ]
[c1 |c2 |c3 ]
[c1 |c2 |c3 ]
[c1 |c2 |c3 ]
[c1 |c2 |c3 ]
[c1 |c2 |c3 ]
[c1 |c2 |c3 ]
[c1 |c2 |c3 ]
< >
< >
}
END
END
ATTRIBUTES
GROUP g1: TEXT = "Customer information";
EDIT f1 = FORMONLY.cust_id TYPE INTEGER, NOT NULL, NOENTRY;
EDIT f2 = FORMONLY.cust_name TYPE VARCHAR, NOT NULL, REQUIRED;
COMBOBOX f3 = FORMONLY.cust_state TYPE VARCHAR, NOT NULL, REQUIRED,
QUERYEDITABLE, DEFAULT="CA", INITIALIZER=combo_fill_states;
CHECKBOX f4 = FORMONLY.cust_checked TYPE CHAR, TEXT="Checked";
BUTTONEDIT f5 = FORMONLY.cust_city TYPE CHAR, NOT NULL, REQUIRED,
ACTION=zoom_city, IMAGE="find";
EDIT f6 = FORMONLY.cust_zipcode TYPE VARCHAR, NOT NULL, REQUIRED,
COMMENT="Possible values: 10000 to 99999",
INCLUDE=(10000 to 99999);
TEXTEDIT f7 = FORMONLY.cust_address TYPE CHAR,
COMMENT="Must hold '<number>, <street name>'";
LABEL tr = FORMONLY.cust_info, STYLE="information",SIZEPOLICY=FIXED;
BUTTON cq: cust_query, TABINDEX=0;
BUTTON cf: cust_first, TABINDEX=0;
BUTTON cp: cust_previous, TABINDEX=0;
BUTTON cn: cust_next, TABINDEX=0;
BUTTON cl: cust_last, TABINDEX=0;
BUTTON ca: cust_append, TABINDEX=0;
BUTTON cd: cust_delete, TABINDEX=0;
BUTTON cu: cust_undo, TABINDEX=0;
BUTTON cs: cust_save, TABINDEX=0;
GROUP g2: TEXT = "Orders";
TABLE t1: DOUBLECLICK=ord_modify;
EDIT c1 = FORMONLY.ord_id TYPE INTEGER, TITLE = "Id";
EDIT c2 = FORMONLY.ord_crea TYPE DATE, TITLE = "Creation";
EDIT c3 = FORMONLY.ord_amount TYPE MONEY(15,2), TITLE = "Amount";
BUTTON oad: ord_append, TABINDEX=0;
BUTTON omo: ord_modify, TABINDEX=0;
BUTTON ode: ord_delete, TABINDEX=0;
END
INSTRUCTIONS
SCREEN RECORD custrec (FORMONLY.cust_id THROUGH FORMONLY.cust_address);
SCREEN RECORD ordlist (FORMONLY.ord_id THROUGH FORMONLY.ord_amount);
END
@@ -0,0 +1,309 @@
# Property of Four Js*
# (c) Copyright Four Js 1995, 2019. All Rights Reserved.
# * Trademark of Four Js Development Tools Europe Ltd
# in the United States and elsewhere
#
# Four Js and its suppliers do not warrant or guarantee that these
# samples are accurate and suitable for your purposes. Their inclusion is
# purely for information purposes only.
IMPORT FGL CityList
IMPORT FGL OrderInput
IMPORT FGL MsgBox
GLOBALS "Globals.4gl"
CONSTANT mytitle = "Customer"
DEFINE myd ui.Dialog
DEFINE cust_new INT
MAIN
DEFINE deleted INT
DEFINE oldcust custrec
OPTIONS INPUT WRAP
OPEN FORM f_cust FROM "CustOrders3"
DISPLAY FORM f_cust
CALL cust_loadlist(9)
DIALOG ATTRIBUTES(FIELD ORDER FORM, UNBUFFERED)
INPUT ARRAY customers FROM custrec.* ATTRIBUTES(INSERT ROW=0)
BEFORE ROW
CALL order_of_cust(arr_curr())
CALL cust_setup(0)
LET oldcust.*=customers[arr_curr()].*
AFTER FIELD cust_name
IF customers[arr_curr()].cust_name MATCHES "*[@#$%^&]*" THEN
ERROR "The customer name contains invalid characters."
NEXT FIELD cust_name
END IF
AFTER FIELD cust_address
IF NOT cust_check_address(customers[arr_curr()].cust_address) THEN
NEXT FIELD cust_address
END IF
BEFORE INSERT
LET cust_new=1
CALL cust_setup(0)
LET oldcust.*=customers[arr_curr()].*
CALL order_of_cust(arr_curr())
AFTER ROW
--be aware of the temp row...
IF arr_curr()<= arr_count() AND NOT deleted THEN
IF customers[arr_curr()].cust_city IS NULL AND customers[arr_curr()].cust_zipcode IS NOT NULL THEN
ERROR "You must specify a city because zipcode is not null."
NEXT FIELD cust_city
END IF
IF FIELD_TOUCHED(custrec.*) THEN
IF cust_new THEN
--INSERT INTO ....
DISPLAY sfmt("INSERT into DB for name:%1",customers[arr_curr()].cust_name)
MESSAGE sfmt("INSERT into DB for name:%1",customers[arr_curr()].cust_name)
ELSE
--UPDATE
DISPLAY sfmt("UPDATE DB for name:%1",customers[arr_curr()].cust_name)
MESSAGE sfmt("UPDATE DB for name:%1",customers[arr_curr()].cust_name)
END IF
IF customers[arr_curr()].cust_name="err" THEN --simulate error
CALL mbox_ok("Error","Can't write record to DB","stop")
NEXT FIELD cust_name
END IF
END IF
END IF
LET deleted=0
LET cust_new=0
BEFORE DELETE
IF NOT cust_new THEN
IF NOT mbox_yn(mytitle,"Delete current customer?","question") THEN
CANCEL DELETE
ELSE
DISPLAY "DELETE from DB"
MESSAGE "DELETED from DB:",customers[arr_curr()].cust_name
END IF
END IF
AFTER DELETE
LET deleted=1
CALL cust_setup(0)
ON ACTION zoom_city
CALL cust_zoom_city()
ON ACTION undo
DISPLAY BY NAME oldcust.*
LET customers[arr_curr()].* = oldcust.*
CALL DIALOG.setFieldTouched("custrec.*",FALSE)
CALL cust_setup(0)
ON ACTION dialogtouched
CALL cust_setup(1)
END INPUT
DISPLAY ARRAY orders TO ordlist.*
BEFORE ROW
MESSAGE SFMT("Order record %1 / %2", ARR_CURR(), ARR_COUNT())
ON ACTION append
CALL ord_append()
ON ACTION ord_modify
CALL ord_modify()
ON ACTION delete
CALL ord_delete()
END DISPLAY
BEFORE DIALOG
LET myd=DIALOG
ON ACTION cancel
IF FIELD_TOUCHED(custrec.*) THEN
CASE mbox_ync(mytitle,"Save your changes?","question")
WHEN 1 --yes
ACCEPT DIALOG --go thru the validation
WHEN 2 --no
EXIT DIALOG
WHEN 3 --cancel
CONTINUE DIALOG
END CASE
END IF
EXIT DIALOG
END DIALOG
LET myd=NULL
END MAIN
FUNCTION cust_setup(touched)
DEFINE touched INT
--because the dialogtouched/undo action is only defined in the INPUT ARRAY
--may run into an error if we try them to switch off in another context
CALL myd.setActionActive("dialogtouched",NOT touched)
CALL myd.setActionActive("undo", touched)
IF cust_new THEN
DISPLAY "Input new customer" TO cust_info
ELSE
DISPLAY SFMT("Customer record %1 / %2",c_arr_curr(),customers.getLength()) TO cust_info
END IF
END FUNCTION
FUNCTION c_arr_curr() RETURN myd.getCurrentRow("custrec") END FUNCTION
FUNCTION o_arr_curr() RETURN myd.getCurrentRow("ordlist") END FUNCTION
FUNCTION cust_zoom_city()
DEFINE s INTEGER
DEFINE name, state STRING
CALL select_city(myd.getFieldBuffer("cust_city")) RETURNING s, name, state
IF s==0 THEN
LET customers[c_arr_curr()].cust_city = name
LET customers[c_arr_curr()].cust_state = state
CALL myd.setFieldTouched("cust_city",TRUE)
CALL myd.setFieldTouched("cust_state",TRUE)
CALL cust_setup(1)
END IF
END FUNCTION
FUNCTION cust_verify_address_format(value)
DEFINE value STRING
DEFINE num STRING, p,x INTEGER
IF value IS NULL THEN RETURN TRUE END IF
LET p = value.getIndexOf(",", 1)
IF p == 0 THEN RETURN FALSE END IF
LET num = value.subString(1, p)
LET x = num
IF x <= 0 THEN RETURN FALSE END IF
IF value.getLength() <= p THEN RETURN FALSE END IF
RETURN TRUE
END FUNCTION
FUNCTION cust_check_address(value)
DEFINE value STRING
IF NOT cust_verify_address_format(value) THEN
CALL mbox_ok(mytitle,"Address must hold a number followed by a comma and the street name.","stop")
RETURN FALSE
END IF
RETURN TRUE
END FUNCTION
FUNCTION order_of_cust(index)
DEFINE index INTEGER
--MESSAGE SFMT("Customer: %1/%2", cust_curr, customers.getLength())
IF cust_new THEN
CALL orders.clear()
ELSE
CALL ord_loadlist(customers[index].cust_id, (index mod 5) * 3)
END IF
END FUNCTION
-- Dummy function to fill the list of customers (normally, SELECT from database)
FUNCTION cust_loadlist(m)
DEFINE m,i INTEGER
CALL customers.clear()
FOR i=1 TO m
LET customers[i].cust_id = i
LET customers[i].cust_name = "Name " || i
LET customers[i].cust_state = "CA"
LET customers[i].cust_city = "San Fransisco"
LET customers[i].cust_zipcode = 94102
LET customers[i].cust_address = "52, Marlon Street"
LET customers[i].cust_checked = i mod 2
END FOR
END FUNCTION
FUNCTION ord_loadlist(mid,m)
DEFINE mid,m,i INTEGER
CALL orders.clear()
FOR i=1 TO m
CALL orders.appendElement()
LET orders[i].ord_id = mid * 100 + i
LET orders[i].ord_crea = CURRENT YEAR TO SECOND
LET orders[i].ord_amount = ( i mod 5 ) * 133.56
END FOR
END FUNCTION
FUNCTION ord_append()
DEFINE r INT
LET r=c_arr_curr()
CALL ord_input('A', customers[r].cust_id, customers[r].cust_name, 0)
END FUNCTION
FUNCTION ord_modify()
DEFINE r INT
LET r=c_arr_curr()
IF o_arr_curr()==0 THEN RETURN END IF
CALL ord_input('M', customers[r].cust_id, customers[r].cust_name, orders[o_arr_curr()].ord_id)
END FUNCTION
FUNCTION ord_delete()
IF NOT mbox_yn(mytitle,"Delete select order from database?","question") THEN
RETURN
END IF
CALL myd.deleteRow("ordlist",o_arr_curr())
END FUNCTION
FUNCTION combo_fill_states(cb)
DEFINE cb ui.ComboBox
CALL cb.addItem("AK","Alaska")
CALL cb.addItem("AL","Alabama")
CALL cb.addItem("AR","Arkansas")
CALL cb.addItem("AZ","Arizona")
CALL cb.addItem("CA","California")
CALL cb.addItem("CO","Colorado")
CALL cb.addItem("CT","Connecticut")
CALL cb.addItem("DC","D.C.")
CALL cb.addItem("DE","Delaware")
CALL cb.addItem("FL","Florida")
CALL cb.addItem("GA","Georgia")
CALL cb.addItem("HI","Hawaii")
CALL cb.addItem("IA","Iowa")
CALL cb.addItem("ID","Idaho")
CALL cb.addItem("IL","Illinois")
CALL cb.addItem("IN","Indiana")
CALL cb.addItem("KS","Kansas")
CALL cb.addItem("KY","Kentucky")
CALL cb.addItem("LA","Louisiana")
CALL cb.addItem("MA","Massachusetts")
CALL cb.addItem("MD","Maryland")
CALL cb.addItem("ME","Maine")
CALL cb.addItem("MI","Michigan")
CALL cb.addItem("MN","Minnesota")
CALL cb.addItem("MO","Missouri")
CALL cb.addItem("MS","Mississippi")
CALL cb.addItem("MT","Montana")
CALL cb.addItem("NC","North Carolina")
CALL cb.addItem("ND","North Dakota")
CALL cb.addItem("NE","Nebraska")
CALL cb.addItem("NH","New Hampshire")
CALL cb.addItem("NJ","New Jersey")
CALL cb.addItem("NM","New Mexico")
CALL cb.addItem("NV","Nevada")
CALL cb.addItem("NY","New York")
CALL cb.addItem("OH","Ohio")
CALL cb.addItem("OK","Oklahoma")
CALL cb.addItem("OR","Oregon")
CALL cb.addItem("PA","Pennsylvania")
CALL cb.addItem("PR","Puerto Rico")
CALL cb.addItem("RI","Rhode Island")
CALL cb.addItem("SC","South Carolina")
CALL cb.addItem("SD","South Dakota")
CALL cb.addItem("TN","Tennessee")
CALL cb.addItem("TX","Texas")
CALL cb.addItem("UT","Utah")
CALL cb.addItem("VA","Virginia")
CALL cb.addItem("VT","Vermont")
CALL cb.addItem("WA","Washington")
CALL cb.addItem("WI","Wisconsin")
CALL cb.addItem("WV","West Virginia")
CALL cb.addItem("WY","Wyoming")
END FUNCTION
@@ -0,0 +1,89 @@
# Property of Four Js*
# (c) Copyright Four Js 1995, 2019. All Rights Reserved.
# * Trademark of Four Js Development Tools Europe Ltd
# in the United States and elsewhere
#
# Four Js and its suppliers do not warrant or guarantee that these
# samples are accurate and suitable for your purposes. Their inclusion is
# purely for information purposes only.
ACTION DEFAULTS
ACTION close(VALIDATE=NO)
ACTION cancel(VALIDATE=NO)
ACTION undo(TEXT="Undo",IMAGE="refresh",VALIDATE=NO)
ACTION ord_modify ( TEXT="Edit Order", IMAGE="pen", COMMENT="Modifies selected order")
END
TOOLBAR
ITEM firstrow (COMMENT="Move to the first record")
ITEM prevrow (TEXT="Prev",COMMENT="Move to the previous record")
ITEM nextrow (COMMENT="Move to the next record")
ITEM lastrow (COMMENT="Move to the last record")
SEPARATOR
ITEM append (TEXT="New",IMAGE="new",COMMENT="Input a new record")
ITEM delete (TEXT="Del",IMAGE="delete",COMMENT="Delete the current record")
SEPARATOR
ITEM ord_modify
END
LAYOUT (TEXT = "Customers and Orders" )
GRID
{
<g g1 >
[f1 ][f2 ]
State: [f3 ] [f4 ]
City: [f5 ]
Zipcode: [f6 ]
Address: [f7 ]
[ ]
-------------------------------------------------------------------
[tr : :undo ]
<g g2 >
<t t1 >
[c1 |c2 |c3 ]
[c1 |c2 |c3 ]
[c1 |c2 |c3 ]
[c1 |c2 |c3 ]
[c1 |c2 |c3 ]
[c1 |c2 |c3 ]
[c1 |c2 |c3 ]
[c1 |c2 |c3 ]
[c1 |c2 |c3 ]
[c1 |c2 |c3 ]
< >
}
END
END
ATTRIBUTES
GROUP g1: TEXT = "Customer information";
f1 = FORMONLY.cust_id TYPE INTEGER, NOT NULL, NOENTRY,DEFAULT=0;
EDIT f2 = FORMONLY.cust_name TYPE VARCHAR, NOT NULL, REQUIRED;
COMBOBOX f3 = FORMONLY.cust_state TYPE VARCHAR, NOT NULL, REQUIRED,
QUERYEDITABLE, DEFAULT="CA", INITIALIZER=combo_fill_states;
CHECKBOX f4 = FORMONLY.cust_checked TYPE CHAR, TEXT="Checked",HIDDEN;
BUTTONEDIT f5 = FORMONLY.cust_city TYPE CHAR, NOT NULL, REQUIRED,
ACTION=zoom_city, IMAGE="find";
f6 = FORMONLY.cust_zipcode TYPE VARCHAR, NOT NULL, REQUIRED,DEFAULT=11111, COMMENT="Possible values: 10000 to 99999",
INCLUDE=(10000 to 99999);
TEXTEDIT f7 = FORMONLY.cust_address TYPE CHAR,
COMMENT="Must hold '<number>, <street name>'";
LABEL tr = FORMONLY.cust_info, STYLE="information",SIZEPOLICY=FIXED;
BUTTON undo:undo,TEXT="Undo";
GROUP g2: TEXT = "Orders";
TABLE t1: DOUBLECLICK=ord_modify;
EDIT c1 = FORMONLY.ord_id TYPE INTEGER, TITLE = "Id";
EDIT c2 = FORMONLY.ord_crea TYPE DATE, TITLE = "Creation";
EDIT c3 = FORMONLY.ord_amount TYPE MONEY(15,2), TITLE = "Amount";
END
INSTRUCTIONS
SCREEN RECORD custrec (FORMONLY.cust_id THROUGH FORMONLY.cust_address);
SCREEN RECORD ordlist (FORMONLY.ord_id THROUGH FORMONLY.ord_amount);
END
@@ -0,0 +1,38 @@
# Property of Four Js*
# (c) Copyright Four Js 1995, 2019. All Rights Reserved.
# * Trademark of Four Js Development Tools Europe Ltd
# in the United States and elsewhere
#
# Four Js and its suppliers do not warrant or guarantee that these
# samples are accurate and suitable for your purposes. Their inclusion is
# purely for information purposes only.
GLOBALS
TYPE custrec RECORD
cust_id INTEGER,
cust_name VARCHAR(50),
cust_state CHAR(3),
cust_checked SMALLINT,
cust_city VARCHAR(50),
cust_zipcode INT,
cust_address VARCHAR(200)
END RECORD
DEFINE customers DYNAMIC ARRAY OF custrec
DEFINE orders DYNAMIC ARRAY OF RECORD
ord_id INTEGER,
ord_crea DATE,
ord_amount MONEY(15,2)
END RECORD
DEFINE ordrec RECORD
ord_id INTEGER,
ord_daddr VARCHAR(50),
ord_crea DATE,
ord_deliv DATE,
ord_valid CHAR(1),
ord_amount MONEY(15,2)
END RECORD
END GLOBALS
@@ -0,0 +1,33 @@
# Property of Four Js*
# (c) Copyright Four Js 1995, 2019. All Rights Reserved.
# * Trademark of Four Js Development Tools Europe Ltd
# in the United States and elsewhere
#
# Four Js and its suppliers do not warrant or guarantee that these
# samples are accurate and suitable for your purposes. Their inclusion is
# purely for information purposes only.
LAYOUT
TABLE
{
Id Item Price
[id |name |price ]
[id |name |price ]
[id |name |price ]
[id |name |price ]
[id |name |price ]
[id |name |price ]
}
END
END
ATTRIBUTES
EDIT id = FORMONLY.item_id TYPE INTEGER;
EDIT name = FORMONLY.item_name TYPE VARCHAR;
EDIT price = FORMONLY.item_price TYPE DECIMAL(10,2);
END
INSTRUCTIONS
SCREEN RECORD sr (FORMONLY.*);
END
@@ -0,0 +1,26 @@
# Property of Four Js*
# (c) Copyright Four Js 1995, 2019. All Rights Reserved.
# * Trademark of Four Js Development Tools Europe Ltd
# in the United States and elsewhere
#
# Four Js and its suppliers do not warrant or guarantee that these
# samples are accurate and suitable for your purposes. Their inclusion is
# purely for information purposes only.
MODULES =\
MsgBox.42m\
CityList.42m\
CityList.42f\
OrderInput.42m\
CustOrders.42m\
CustOrders2.42m\
CustOrders3.42m
FORMS = \
CustOrders.42f\
CustOrders2.42f\
CustOrders3.42f\
Orders.42f\
ItemList.42f
include ../../Makeincl
@@ -0,0 +1,36 @@
# Property of Four Js*
# (c) Copyright Four Js 1995, 2019. All Rights Reserved.
# * Trademark of Four Js Development Tools Europe Ltd
# in the United States and elsewhere
#
# Four Js and its suppliers do not warrant or guarantee that these
# samples are accurate and suitable for your purposes. Their inclusion is
# purely for information purposes only.
FUNCTION mbox_ok(t, m, i)
DEFINE t,m,i STRING
MENU t ATTRIBUTES(STYLE="dialog",COMMENT=m,IMAGE=i)
COMMAND "Ok" EXIT MENU
END MENU
END FUNCTION
FUNCTION mbox_yn(t, m, i)
DEFINE t,m,i STRING
DEFINE r INTEGER
MENU t ATTRIBUTES(STYLE="dialog",COMMENT=m,IMAGE=i)
COMMAND "Yes" LET r = TRUE EXIT MENU
COMMAND "No" LET r = FALSE EXIT MENU
END MENU
RETURN r
END FUNCTION
FUNCTION mbox_ync(t, m, i)
DEFINE t,m,i STRING
DEFINE r INTEGER
MENU t ATTRIBUTES(STYLE="dialog",COMMENT=m,IMAGE=i)
COMMAND "Yes" LET r = 1 EXIT MENU
COMMAND "No" LET r = 2 EXIT MENU
COMMAND "Cancel" LET r = 3 EXIT MENU
END MENU
RETURN r
END FUNCTION
@@ -0,0 +1,186 @@
# Property of Four Js*
# (c) Copyright Four Js 1995, 2019. All Rights Reserved.
# * Trademark of Four Js Development Tools Europe Ltd
# in the United States and elsewhere
#
# Four Js and its suppliers do not warrant or guarantee that these
# samples are accurate and suitable for your purposes. Their inclusion is
# purely for information purposes only.
IMPORT FGL MsgBox
GLOBALS "Globals.4gl"
DEFINE items DYNAMIC ARRAY OF RECORD
item_line INTEGER,
item_name VARCHAR(50),
item_quant DECIMAL(8,2),
item_price MONEY(10,2),
item_total MONEY(15,2),
item_id INTEGER
END RECORD
CONSTANT mytitle = "Orders"
FUNCTION ord_input(mode, cust_id, cust_name, ord_id)
DEFINE mode CHAR(1)
DEFINE cust_id INTEGER
DEFINE cust_name VARCHAR(50)
DEFINE ord_id INTEGER
DEFINE i, id, s,r INTEGER
DEFINE name STRING
DEFINE price DECIMAL(10,2)
DEFINE touched,reorder INT
OPEN WINDOW w_ord WITH FORM "Orders"
CALL items.clear()
IF mode == 'M' THEN
-- Simulate database fetch
LET ordrec.ord_id = ord_id
LET ordrec.ord_daddr = "5, Market Street"
LET ordrec.ord_crea = TODAY - ord_id
LET ordrec.ord_deliv = ordrec.ord_crea + 3
LET ordrec.ord_valid = "n"
-- Some item lines...
LET i=0
LET items[i:=i+1].item_line = i
LET items[1].item_id = 2
LET items[i].item_name = "Oranges (1Kg)"
LET items[i].item_quant = 2
LET items[i].item_price = 4.65
LET items[i].item_total = items[i].item_quant * items[i].item_price
LET items[i:=i+1].item_line = i
LET items[1].item_id = 1
LET items[i].item_name = "Apples (1Kg)"
LET items[i].item_quant = 3
LET items[i].item_price = 3.45
LET items[i].item_total = items[i].item_quant * items[i].item_price
LET ordrec.ord_amount = 0
FOR i=1 TO items.getLength()
LET ordrec.ord_amount = ordrec.ord_amount + items[i].item_total
END FOR
END IF
DIALOG ATTRIBUTES(FIELD ORDER FORM, UNBUFFERED)
INPUT BY NAME ordrec.* ATTRIBUTE(WITHOUT DEFAULTS=(mode=='M'))
END INPUT
INPUT ARRAY items FROM itemlist.* ATTRIBUTE(WITHOUT DEFAULTS)
BEFORE INSERT
CALL orditem_reorder(DIALOG)
AFTER INSERT
LET reorder=1
AFTER DELETE
LET reorder=1
AFTER ROW
IF FIELD_TOUCHED(itemlist.*) THEN
LET touched=TRUE
END IF
IF reorder THEN
CALL orditem_reorder(DIALOG)
LET reorder=0
END IF
ON CHANGE item_quant
CALL orditem_total(DIALOG, arr_curr())
ON ACTION zoom_item
LET r=DIALOG.getCurrentRow("itemlist")
CALL select_item(DIALOG.getFieldBuffer("item_name")) RETURNING s, id, name, price
IF s==0 THEN
LET items[r].item_id = id
LET items[r].item_name = name
LET items[r].item_price = price
CALL orditem_total(DIALOG, r)
CALL DIALOG.setFieldTouched("item_name",TRUE)
END IF
END INPUT
BEFORE DIALOG
DISPLAY BY NAME cust_id, cust_name
--init some items in the new record after the defaults have been filled in
IF mode='A' THEN
LET ordrec.ord_deliv = TODAY+1
END IF
ON ACTION accept
ACCEPT DIALOG
ON ACTION cancel
IF touched OR FIELD_TOUCHED(ordrec.*) OR
FIELD_TOUCHED(itemlist.*) THEN
IF NOT mbox_yn(mytitle,"Close window without saving input?","question") THEN
CONTINUE DIALOG
END IF
END IF
EXIT DIALOG
AFTER DIALOG
-- Save data in database.
DISPLAY "save data"
END DIALOG
CLOSE WINDOW w_ord
END FUNCTION
FUNCTION orditem_reorder(d)
DEFINE d ui.Dialog
DEFINE i INTEGER
FOR i=1 TO d.getArrayLength("itemlist")
LET items[i].item_line = i
END FOR
END FUNCTION
FUNCTION orditem_total(d, r)
DEFINE d ui.Dialog
DEFINE r, i INTEGER
LET items[r].item_total = items[r].item_quant * items[r].item_price
LET ordrec.ord_amount = 0
FOR i=1 TO d.getArrayLength("itemlist")
LET ordrec.ord_amount = ordrec.ord_amount + items[i].item_total
END FOR
END FUNCTION
FUNCTION select_item(filter)
DEFINE filter STRING
DEFINE i INTEGER
DEFINE itemarr DYNAMIC ARRAY OF RECORD
item_id INTEGER,
item_name VARCHAR(50),
item_price MONEY(10,2)
END RECORD
OPEN WINDOW w_item WITH FORM "ItemList"
LET i = 0
LET itemarr[i:=i+1].item_id = i LET itemarr[i].item_name = "Apples (1Kg)" LET itemarr[i].item_price = 4.25
LET itemarr[i:=i+1].item_id = i LET itemarr[i].item_name = "Oranges (1Kg)" LET itemarr[i].item_price = 5.35
LET itemarr[i:=i+1].item_id = i LET itemarr[i].item_name = "Lemons (1Kg)" LET itemarr[i].item_price = 4.95
LET itemarr[i:=i+1].item_id = i LET itemarr[i].item_name = "Melons (unit)" LET itemarr[i].item_price = 1.55
LET itemarr[i:=i+1].item_id = i LET itemarr[i].item_name = "Peanuts (1Kg)" LET itemarr[i].item_price = 2.25
DISPLAY ARRAY itemarr TO sr.*
AFTER DISPLAY
IF NOT int_flag THEN
LET i = arr_curr()
ELSE
LET int_flag = FALSE
LET i = -1
END IF
END DISPLAY
CLOSE WINDOW w_item
IF i > 0 THEN
RETURN 0, itemarr[i].item_id, itemarr[i].item_name, itemarr[i].item_price
ELSE
RETURN -1, NULL, NULL, NULL
END IF
END FUNCTION
@@ -0,0 +1,86 @@
# Property of Four Js*
# (c) Copyright Four Js 1995, 2019. All Rights Reserved.
# * Trademark of Four Js Development Tools Europe Ltd
# in the United States and elsewhere
#
# Four Js and its suppliers do not warrant or guarantee that these
# samples are accurate and suitable for your purposes. Their inclusion is
# purely for information purposes only.
ACTION DEFAULTS
ACTION insert ( IMAGE = "listins", COMMENT="Insert a new item line")
ACTION append ( IMAGE = "listadd", COMMENT="Append a new item line")
ACTION delete ( IMAGE = "listdel", COMMENT="Delete current item line", VALIDATE=NO)
ACTION validate ( TEXT = "Validate", COMMENT="Validate the order", ACCELERATOR=Control-V)
END
LAYOUT ( STYLE="dialog4", TEXT = "Order" )
GRID
{
<g g1 >
Id: [f1 ] Name: [f2 ]
< >
<g g2 >
Id: [o1 ] Crea: [o3 ]
Delivery address: [o2 ]
Delivery date: [o4 ][o5 ]
< >
<g g3 >
<t t1 >[iad]
[c1 |c2 |c3 |c4 |c5 |c6][imo]
[c1 |c2 |c3 |c4 |c5 |c6][ide]
[c1 |c2 |c3 |c4 |c5 |c6]
[c1 |c2 |c3 |c4 |c5 |c6]
[c1 |c2 |c3 |c4 |c5 |c6]
[c1 |c2 |c3 |c4 |c5 |c6]
[c1 |c2 |c3 |c4 |c5 |c6]
< >
< >
<g g4 >
[ :o6 ]
< >
}
END
END
ATTRIBUTES
GROUP g1: TEXT = "Customer information";
EDIT f1 = FORMONLY.cust_id TYPE INTEGER, NOENTRY;
EDIT f2 = FORMONLY.cust_name TYPE VARCHAR, NOENTRY;
GROUP g2: TEXT = "Order header";
EDIT o1 = FORMONLY.ord_id, NOENTRY;
EDIT o2 = FORMONLY.ord_daddr TYPE VARCHAR, NOT NULL, REQUIRED;
EDIT o3 = FORMONLY.ord_crea TYPE DATE, NOENTRY,DEFAULT=TODAY;
DATEEDIT o4 = FORMONLY.ord_deliv TYPE DATE;
GROUP g3: TEXT = "Order items";
TABLE t1: DOUBLECLICK=none;
LABEL c1 = FORMONLY.item_line TYPE INTEGER, TITLE = "Pos";
BUTTONEDIT c2 = FORMONLY.item_name TYPE VARCHAR, TITLE = "Item name",
SCROLL, NOT NULL, REQUIRED, ACTION=zoom_item, IMAGE="find";
EDIT c3 = FORMONLY.item_quant TYPE DECIMAL(5,2), TITLE = "Quantity",
SCROLL, NOT NULL, REQUIRED, INCLUDE=(0.0001 TO 10000),DEFAULT=1;
LABEL c4 = FORMONLY.item_price TYPE MONEY(10,2), TITLE = "Unit price";
LABEL c5 = FORMONLY.item_total TYPE MONEY(15,2), TITLE = "Total";
LABEL c6 = FORMONLY.item_id TYPE INTEGER, TITLE = "Item id", HIDDEN=USER;
BUTTON iad: itemlist.insert;
BUTTON imo: itemlist.append;
BUTTON ide: itemlist.delete;
GROUP g4: TEXT = "Order amount", GRIDCHILDRENINPARENT;
CHECKBOX o5 = FORMONLY.ord_valid TYPE CHAR, NOT NULL, REQUIRED,
TEXT="Validated", VALUECHECKED="y", VALUEUNCHECKED="n",DEFAULT='n';
EDIT o6 = FORMONLY.ord_amount TYPE MONEY(15,2), STYLE="amount", NOENTRY;
END
INSTRUCTIONS
SCREEN RECORD custrec (FORMONLY.cust_id, FORMONLY.cust_name);
SCREEN RECORD ordrec (FORMONLY.ord_id, FORMONLY.ord_daddr,
FORMONLY.ord_crea, FORMONLY.ord_deliv,
FORMONLY.ord_valid, FORMONLY.ord_amount);
SCREEN RECORD itemlist (FORMONLY.item_line THROUGH FORMONLY.item_id);
END
@@ -0,0 +1,104 @@
Input multiple master records + details
---------------------------------------
This directory contains some examples to perform the following task:
a master and a detail table should be edited simultaneously.
(a customer record acts as the master record and an orders table contains the details)
It should be possible to have forward/backward actions to advance to the next physical record for the master in the database and update the details display/input as well.
This is something pretty new in 4GL, this was not possible before.
Particularly, the INPUT of the master by staying visually 'in the dialog' was only possible before by using an INPUT ARRAY on a single record (which is still a good idea as shown later on).
3 DIALOG samples try to achieve this: CustOrders.4gl, CustOrders2.4gl, and CustOrders3.4gl
CustOrders.4gl and CustOrders2.4gl have the same user interface but show
different methods for vaidation of the record data.
Both use an INPUT for the master data and a DISPLAY ARRAY for the details.
CustOrders3.4gl has a more simple user interface (no confirm for the row and no save button) and shows how an INPUT ARRAY on a single row could be used to achieve basically the same result as in CustOrders.4gl and CustOrders2.4gl (with other limitations however).
OrderInput.4gl shows the INPUT of a single master record + details in an INPUT ARRAY (FUNCTION ord_input) and is common to all CustOrders*.4gl samples.
In the AFTER DIALOG of ord_input there is only a small DISPLAY "save data", which is of course a bigger piece of code to write a complete order to the DB.
A real database demo with a similar DIALOG is shown in $FGLDIR/demo/MultipleDialogs/StoresMD/d4_orders.4gl (FUNCTION order_modify)
A central problem in this demo is validation:
When the current customer record is modified, clicking on the previous/next/append buttons should cause the validation of the current record. That means the built-in constraints for the fields must be run thru, as well as the AFTER FIELD of the current field, and the code that is normally in AFTER INPUT to perform checks that cannot be done in AFTER FIELD (due to multiple field dependencies).
In the first 2 samples code is written to achieve that, in the 3rd the INPUT ARRAY with it's built-in triggers does a lot of the work.
CustOrders.4gl
--------------
Implements all customer record validation by 'staying in the dialog' and performs explicit field validation for the user programmed actions.
Each action that require a validation, like going to the next customer or invoking a search, must validate the current INPUT.
Further, those actions must perform the same checking as the built-in 'accept' action of a traditional INFORMIX INPUT instruction:
v1:verify if the field constraints are met (achieved by calling the new DIALOG.validate() method).
v2:call the code that is called in the AFTER FIELD trigger of the field you are currently in.
v3:call the code that is called in the AFTER INPUT trigger.
This is achieved by calling the cust_check_changed() function, which checks the validity in cust_save() by calling cust_check_fields()(calling cust_check_data(), then performing DIALOG.validate() + checking all user programmed field constraints)
The user programmed field constraints are the same in each sample:
1. The customer's address is checked when entered
2. A city cannot be entered without entering a zip code
Advantages: 1. you never have to leave the DIALOG instruction (in comparison to sample2)
Disadvantages:1. need to perform self made (explicite) validation for each of the standard actions
2. a lot of boilerplate code needed to achieve the standard
first/next/previous/last/append/delete actions (in comparison to sample3)
3. the WITHOUT DEFAULTS flag for the INPUT is not usable (see in ../ListAndEditableDetail/README what this means)
CustOrders2.4gl
---------------
- implents all customer record validations by using implicit validation: calling ACCEPT DIALOG
That means that each action which requires validation calls ACCEPT DIALOG.
This lets the DIALOG run thru all 3 validation stages and causes a falling out of the current DIALOG instruction if the validation was succesful.
That means also that one has to maintain a state variable to reenter the DIALOG in a WHILE loop around the DIALOG (in addition to maintain a variable to distinguish between new record or record modification).
The main advantage using that technology is that all you AFTER FIELD constraints can be left in 1 place, it's not needed to use a function for a simple constraint and not needed to duplicate the call in your private validating function compared to sample 1 (see for example the AFTER FIELD cust_name checking) .
Advantages: 1. you only need to check the field constraints at the usual places:
AFTER FIELD and AFTER INPUT and not in each action
2. the WITHOUT DEFAULTS flag for the INPUT is still working like intended (see in ../ListAndEditableDetail/README what this means)
Disadvantages: 1. need to write a WHILE loop with a state variable
2. a lot of boilerplate code needed to achieve the standard
first/next/previous/last/append/delete actions (in comparison to sample3)
CustOrders3.4gl
---------------
-uses an INPUT ARRAY in contradiction to the previous samples where an INPUT was used for the master record
-shows that the standard next/previous/append/delete actions are already builtin into INPUT ARRAY and usable nearly like intended
-implements an MS Access behavior: going to the next/prevoius/first/last row saves the current record to the database without confirmation. If one wants to throw away the changes in the current record, he must press 'Undo'
-shows how the firstrow/prevrow/nextrow/lastrow/append/delete actions can be put into the toolbar and work depending on the context .
That means: if the focus is in INPUT ARRAY-> toolbar buttons act on the INPUT ARRAY
if the focus is in the DISPLAY ARRAY-> toolbar buttons act on the DISPLAY ARRAY
This is similar to how the toolbar is working in MS Access.
(Please note that in earlier versions the single row INPUT ARRAY had only prevrow/nextrow actions)
A further goodie is that INPUT ARRAY/DISPLAY ARRAY now manage the activation of firstrow/prevrow/nextrow/lastrow automatically: no need to call setActionActive by yourself.
Advantages: 1. *a lot* shorter code than in the 2 previous samples because the
next/previous/append/delete mechanisms are already foreseen in INPUT ARRAY
2. you only need to check the field constraints at the usual places:
AFTER FIELD and AFTER ROW (which comparable to the AFTER INPUT trigger of the single record INPUT) and not in each action
Disadvantages: 1. Pressing 'Tab' at the last customer record field moves to the next customer record instead of jumoing to the DISPLAY ARRAY
2. you need to operate on an array which may be inadequate for a large number of database items
CustOrders3.4gl sounds promising but at the moment it's impossible to implement confirmation for the new record (which is not absolutely necessary like MS Access shows).
It's still the usual INPUT ARRAY to input an array and not much foreseen to be synchronized with the data base. However it's intended to be improved in one of the next releases. Then it very likely includes built-in undo support as well as built-in support for synchronizing a row with the database.
The navigation is probably also not what the end user expects, pressing the 'Down' arrow moves to the next record.
Pressing 'Tab' at the end of the record advances also to the next record (which is fine if the INPUT ARRAY runs in a table but not expected for the single record case).
This could be enhanced by giving the INPUT ARRAY a hint that it should make the navigation different in the single record case.
Conclusion:
----------
The first 2 samples show that it's at least possible to achieve the wanted behavior however with a pretty large overhead for implementing the standard actions.
In both cases you need to be careful about the validations for the standard actions and need to add extra code to achieve this.
Both samples lack easy readability because of all the necessary validation stuff which would be an easy game using an INPUT ARRAY.
Sample 3 shows that you can have much less code using INPUT ARRAY, but you still need to know well the pitfalls of how to synchronize the INPUT ARRAY with the database and you will have to live with the limitations (navigation a bit different from expectation)
If this kind of dialog is not a top priority for you, you better wait for an enhanced INPUT ARRAY which has built-in support to sync with the database and enhanced navigation for the single record case to make programming this a joy.