Commit inicial: fuentes MBS ERP (Genero 6) + .gitignore + docs/SETUP_PC_MBS.md
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
# 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
|
||||
|
||||
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,180 @@
|
||||
# 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 MsgBox
|
||||
|
||||
DEFINE sql_cond STRING
|
||||
|
||||
TYPE t_custrec RECORD
|
||||
c_id INTEGER,
|
||||
c_name VARCHAR(50),
|
||||
c_city VARCHAR(30),
|
||||
c_state CHAR(2),
|
||||
c_zipcode VARCHAR(6),
|
||||
c_address VARCHAR(100)
|
||||
END RECORD
|
||||
|
||||
DEFINE custarr DYNAMIC ARRAY OF t_custrec
|
||||
|
||||
DEFINE changing, temprow SMALLINT
|
||||
|
||||
MAIN
|
||||
DEFINE s, r INTEGER
|
||||
DEFINE name, state STRING
|
||||
|
||||
OPTIONS INPUT WRAP
|
||||
|
||||
OPEN FORM f1 FROM "CustListEdit"
|
||||
DISPLAY FORM f1
|
||||
|
||||
CALL cust_loadlist(2)
|
||||
DIALOG ATTRIBUTES(FIELD ORDER FORM, UNBUFFERED)
|
||||
|
||||
CONSTRUCT BY NAME sql_cond ON cust_id, cust_name, cust_city
|
||||
END CONSTRUCT
|
||||
|
||||
INPUT ARRAY custarr FROM sr.* ATTRIBUTE(WITHOUT DEFAULTS, AUTO APPEND = FALSE)
|
||||
|
||||
BEFORE ROW
|
||||
CALL cust_clean(DIALOG)
|
||||
CALL cust_setup(DIALOG)
|
||||
|
||||
ON ROW CHANGE
|
||||
CALL cust_save(DIALOG)
|
||||
|
||||
BEFORE DELETE
|
||||
CALL cust_save(DIALOG)
|
||||
|
||||
BEFORE INSERT
|
||||
LET temprow = (DIALOG.getCurrentRow("sr") == Dialog.getArrayLength("sr"))
|
||||
LET r = DIALOG.getCurrentRow("sr")
|
||||
LET custarr[r].c_id = r
|
||||
CALL cust_touch_setup(DIALOG)
|
||||
|
||||
AFTER INSERT
|
||||
CALL cust_save(DIALOG)
|
||||
LET temprow = FALSE
|
||||
|
||||
END INPUT
|
||||
|
||||
BEFORE DIALOG
|
||||
CALL cust_clean(DIALOG)
|
||||
CALL cust_setup(DIALOG)
|
||||
|
||||
ON ACTION dialogtouched
|
||||
CALL cust_touch_setup(DIALOG)
|
||||
|
||||
ON ACTION zoom_city_1
|
||||
CALL select_city(DIALOG.getFieldBuffer("cust_city")) RETURNING s, name, state
|
||||
IF s==0 THEN
|
||||
LET r = DIALOG.getCurrentRow("sr")
|
||||
DISPLAY name TO cust_city
|
||||
END IF
|
||||
|
||||
ON ACTION zoom_city_2
|
||||
CALL select_city(DIALOG.getFieldBuffer("c_city")) RETURNING s, name, state
|
||||
IF s==0 THEN
|
||||
LET r = DIALOG.getCurrentRow("sr")
|
||||
LET custarr[r].c_city = name
|
||||
LET custarr[r].c_state = state
|
||||
CALL DIALOG.setFieldTouched("c_city",TRUE)
|
||||
CALL cust_touch_setup(DIALOG)
|
||||
END IF
|
||||
|
||||
ON ACTION fetch
|
||||
DISPLAY sql_cond TO condition
|
||||
CALL cust_loadlist(length(sql_cond))
|
||||
|
||||
ON ACTION save
|
||||
CALL cust_save(DIALOG)
|
||||
IF temprow THEN
|
||||
CALL DIALOG.setFieldTouched("sr.*", TRUE)
|
||||
LET temprow = FALSE
|
||||
END IF
|
||||
|
||||
ON ACTION close
|
||||
IF NOT cust_check_close(DIALOG) THEN
|
||||
CONTINUE DIALOG
|
||||
END IF
|
||||
EXIT DIALOG
|
||||
|
||||
END DIALOG
|
||||
|
||||
END MAIN
|
||||
|
||||
FUNCTION cust_clean(d)
|
||||
DEFINE d ui.Dialog
|
||||
LET changing = FALSE
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION cust_touch_setup(d)
|
||||
DEFINE d ui.Dialog
|
||||
-- ignore changes in the filter fields!
|
||||
LET changing = (d.getCurrentItem() MATCHES "sr.*")
|
||||
CALL cust_setup(d)
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION cust_check_changing(d)
|
||||
DEFINE d ui.Dialog
|
||||
IF changing THEN
|
||||
CALL cust_setup(d)
|
||||
CALL mbox_ok("Customers", "You must first save your changes!","stop")
|
||||
RETURN FALSE
|
||||
END IF
|
||||
RETURN TRUE
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION cust_check_close(d)
|
||||
DEFINE d ui.Dialog
|
||||
IF changing THEN
|
||||
CALL cust_setup(d)
|
||||
RETURN mbox_yn("Customers","Do you want to close the window without saving changes?","question")
|
||||
END IF
|
||||
RETURN TRUE
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION cust_setup(d)
|
||||
DEFINE d ui.Dialog
|
||||
CALL d.setActionActive("dialogtouched", NOT changing)
|
||||
CALL d.setActionActive("fetch", NOT changing)
|
||||
CALL d.setActionActive("save", changing)
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION cust_save(d)
|
||||
DEFINE d ui.Dialog
|
||||
-- UDPATE/INSERT/DELETE to database...
|
||||
LET changing = FALSE
|
||||
CALL d.setFieldTouched("sr.*", FALSE)
|
||||
CALL cust_clean(d)
|
||||
CALL cust_setup(d)
|
||||
MESSAGE "Changes saved in database - ", CURRENT YEAR TO SECOND
|
||||
END FUNCTION
|
||||
|
||||
-- Dummy function to fill the list of customers (normally, SELECT from database)
|
||||
FUNCTION cust_loadlist(m)
|
||||
DEFINE m,i INTEGER
|
||||
CALL custarr.clear()
|
||||
FOR i=1 TO m
|
||||
CALL custarr.appendElement()
|
||||
LET custarr[i].c_id = i
|
||||
LET custarr[i].c_name = "Customer #"||i
|
||||
IF i MOD 3 == 0 THEN
|
||||
LET custarr[i].c_city = "San Francisco"
|
||||
LET custarr[i].c_state = "CA"
|
||||
LET custarr[i].c_zipcode = "76231"
|
||||
LET custarr[i].c_address = "24, Blue Lagon Street"
|
||||
ELSE
|
||||
LET custarr[i].c_city = "Richmond"
|
||||
LET custarr[i].c_state = "VA"
|
||||
LET custarr[i].c_zipcode = "71212"
|
||||
LET custarr[i].c_address = "45, Yellow Lake"
|
||||
END IF
|
||||
END FOR
|
||||
END FUNCTION
|
||||
@@ -0,0 +1,69 @@
|
||||
# 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 save (TEXT="Save changes",ACCELERATOR=control-s)
|
||||
ACTION close (DEFAULTVIEW=Yes)
|
||||
END
|
||||
|
||||
LAYOUT ( TEXT = "Query and editable list" )
|
||||
GRID
|
||||
{
|
||||
<g g1 >
|
||||
Customer id: [q1 ]
|
||||
Name: [q2 ]
|
||||
City: [q3 ]
|
||||
[bf ]
|
||||
[sql ]
|
||||
[ ]
|
||||
[ ]
|
||||
< >
|
||||
<g g2 >
|
||||
<t t1 >
|
||||
Id Name City State ZipCode Address
|
||||
[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 ]
|
||||
< >
|
||||
[ins :app :del :sav ]
|
||||
< >
|
||||
}
|
||||
END
|
||||
END
|
||||
|
||||
ATTRIBUTES
|
||||
|
||||
GROUP g1: TEXT = "Filter";
|
||||
EDIT q1 = FORMONLY.cust_id TYPE INTEGER;
|
||||
EDIT q2 = FORMONLY.cust_name TYPE VARCHAR;
|
||||
BUTTONEDIT q3 = FORMONLY.cust_city TYPE VARCHAR, ACTION=zoom_city_1, IMAGE="find";
|
||||
TEXTEDIT sql = FORMONLY.condition, NOENTRY;
|
||||
BUTTON bf: fetch, TEXT="Fetch";
|
||||
|
||||
GROUP g2: TEXT = "Customers";
|
||||
EDIT c1 = FORMONLY.c_id TYPE INTEGER, SCROLL, DEFAULT = 0;
|
||||
EDIT c2 = FORMONLY.c_name TYPE VARCHAR, SCROLL, DEFAULT = "<no name>";
|
||||
BUTTONEDIT c3 = FORMONLY.c_city TYPE CHAR, SCROLL, ACTION=zoom_city_2, IMAGE="find";
|
||||
COMBOBOX c4 = FORMONLY.c_state TYPE VARCHAR, SCROLL,
|
||||
QUERYEDITABLE, DEFAULT="CA", INITIALIZER=combo_fill_states;
|
||||
EDIT c5 = FORMONLY.c_zipcode TYPE VARCHAR, SCROLL;
|
||||
EDIT c6 = FORMONLY.c_address TYPE VARCHAR, SCROLL;
|
||||
BUTTON ins: sr.insert;
|
||||
BUTTON app: sr.append;
|
||||
BUTTON del: sr.delete;
|
||||
|
||||
BUTTON sav: save;
|
||||
|
||||
END
|
||||
|
||||
INSTRUCTIONS
|
||||
SCREEN RECORD sr (FORMONLY.c_id THROUGH FORMONLY.c_address);
|
||||
END
|
||||
@@ -0,0 +1,12 @@
|
||||
# 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.
|
||||
|
||||
include ../../Makeincl
|
||||
|
||||
all:: testfiledlg.42m filedlg.42m filedlg.42f
|
||||
@@ -0,0 +1,53 @@
|
||||
File Dialog sample
|
||||
------------------
|
||||
|
||||
This example tries to work like the Windows File Open box as closely as possible
|
||||
(but omits the Favorites/Desktop etc buttons at the left hand side and the Search combo at the top; this is really platform-dependent).
|
||||
To use the file dialog, fill a predefined record with the desired title, file patterns, etc., and then call the functions
|
||||
filedlg_open() or filedlg_save() with the record as the parameter.
|
||||
|
||||
This works similar to the windows common dialog C-library (comdlg32.dll).
|
||||
|
||||
The advantage in using a record (FILEDLG_RECORD) instead of a large numer of parameters to those functions is that it's possible to use the autocompletion feature.
|
||||
|
||||
The DIALOG construct is used in FUNCTION _filedlg_doDlg() to let a DISPLAY ARRAY and an INPUT run in parallel to display the list of files, and to be able to enter the filename and switch between several file types using a combobox ( for example between *.per and *.*).
|
||||
|
||||
The accept action works in a special way in this dialog:
|
||||
|
||||
It determines first where the focus is, and then does the following:
|
||||
|
||||
1.focus is in the DISPLAY ARRAY
|
||||
a)If a file is highlighted, the dialog terminates and returns the file name
|
||||
b)If a directory is highlighted, the dialog switches to that directory and
|
||||
reloads the file array
|
||||
|
||||
2. focus is in the INPUT
|
||||
a)if the typed name is an existing file, the dialog terminates and returns the file name.
|
||||
b)if the typed name is a directory, the code tries to switch to that directory, and to reload the file array.
|
||||
c) if the typed name is neither -> failure box in open mode, return file name in save mode
|
||||
|
||||
The new DIALOG.getCurrentItem() built-in function is used to determine where the focus is, because INFIELD does not work for DISPLAY ARRAY.
|
||||
DIALOG.getCurrentItem() returns a string thst is a field name for the usual input fields. and the screen record name for the DISPLAY ARRAY.
|
||||
|
||||
As an example, you can choose (as in the Windows original) that a file in the dialog can be deleted with the "Del" key if the focus is in the DISPLAY ARRAY.
|
||||
This is achieved by a sub dialog action in the DISPLAY ARRAY sub dialog statement:
|
||||
DIALOG
|
||||
DISPLAY ARRAY
|
||||
...
|
||||
ON ACTION del
|
||||
--perform code to delete the current highlighted file
|
||||
--the action is only active if the focus is inside this display array
|
||||
..
|
||||
|
||||
END DISPLAY
|
||||
END DIALOG
|
||||
The runtime system ensures that this action is only active when the focus is inside the DISPLAY ARRAY, in contradiction to the global actions below the sub dialog statements.
|
||||
|
||||
(If the action was also active in the INPUT sub dialog, the result would be conflicting accelerators; the 'Del' key should erase the text in the edit fields)
|
||||
|
||||
|
||||
$FGLDIR/demo/tools/fglped makes use of this file dialog.
|
||||
|
||||
In $FGLDIR/demo/tools/fgltlib/fgltfiledlg.4gl there is a similar file dialog that uses a larger parameter list for the dialog.
|
||||
|
||||
Use whichever method is appropriate for you.
|
||||
@@ -0,0 +1,415 @@
|
||||
# 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.
|
||||
|
||||
#+ File open/save dialog box using MD
|
||||
IMPORT FGL fgldialog
|
||||
IMPORT os
|
||||
|
||||
PUBLIC TYPE FILEDLG_RECORD RECORD
|
||||
title STRING,
|
||||
defaultfilename STRING,
|
||||
defaultpath STRING,
|
||||
opt_create_dirs SMALLINT, -- allows the creation of a new subdirectory
|
||||
--(not yet implemented)
|
||||
opt_delete_files SMALLINT, -- allows to delete files when running the dialog
|
||||
types DYNAMIC ARRAY OF RECORD -- list for the file type combobox
|
||||
description STRING, -- string to display
|
||||
suffixes STRING -- pipe separated string of all possible suffixes for one entry
|
||||
-- example "*.per|*.4gl"
|
||||
END RECORD
|
||||
END RECORD
|
||||
|
||||
DEFINE _filedlg_list DYNAMIC ARRAY OF RECORD
|
||||
eimage STRING, -- image name
|
||||
entry STRING, -- Filename or Dirname
|
||||
esize INT, --file size
|
||||
emodt STRING, --modification time
|
||||
etype STRING -- C_DIRECTORY or "*.xxx File"
|
||||
END RECORD
|
||||
|
||||
DEFINE last_opendlg_directory STRING
|
||||
DEFINE last_savedlg_directory STRING
|
||||
DEFINE m_typearr DYNAMIC ARRAY OF STRING
|
||||
DEFINE m_typelen INT
|
||||
CONSTANT C_DIRECTORY="Directory"
|
||||
CONSTANT C_OPEN="open"
|
||||
CONSTANT C_SAVE="save"
|
||||
|
||||
#+ Opens a file dialog to open a file.
|
||||
#+ @returnType String
|
||||
#+ @return The selected file path, or NULL is canceled.
|
||||
#+ @param r the record describing the file dialog
|
||||
#
|
||||
FUNCTION filedlg_open(r)
|
||||
DEFINE r FILEDLG_RECORD
|
||||
DEFINE t, fn STRING
|
||||
IF r.defaultpath IS NULL THEN
|
||||
IF last_opendlg_directory IS NULL THEN
|
||||
LET last_opendlg_directory = "."
|
||||
END IF
|
||||
LET r.defaultpath = last_opendlg_directory
|
||||
END IF
|
||||
LET t= "Open File"
|
||||
IF r.title IS NOT NULL THEN
|
||||
LET t = r.title
|
||||
END IF
|
||||
LET fn = _filedlg_doDlg(C_OPEN,t,r.*)
|
||||
IF fn IS NOT NULL THEN
|
||||
LET last_opendlg_directory = _file_get_dirname(fn)
|
||||
END IF
|
||||
RETURN fn
|
||||
END FUNCTION
|
||||
|
||||
#+ Opens a file dialog to save a file.
|
||||
#+ @returnType String
|
||||
#+ @return The selected file path, or NULL is canceled.
|
||||
#+ @param r The record describing the file dialog
|
||||
#
|
||||
FUNCTION filedlg_save(r)
|
||||
DEFINE r FILEDLG_RECORD
|
||||
DEFINE t, fn STRING
|
||||
IF r.defaultpath IS NULL THEN
|
||||
IF last_savedlg_directory IS NULL THEN
|
||||
LET last_savedlg_directory = "."
|
||||
END IF
|
||||
LET r.defaultpath = last_savedlg_directory
|
||||
END IF
|
||||
LET t = "Save File"
|
||||
IF r.title IS NOT NULL THEN
|
||||
LET t = r.title
|
||||
END IF
|
||||
LET fn = _filedlg_doDlg(C_SAVE,t,r.*)
|
||||
IF fn IS NOT NULL THEN
|
||||
LET last_savedlg_directory = _file_get_dirname(fn)
|
||||
END IF
|
||||
RETURN fn
|
||||
END FUNCTION
|
||||
|
||||
------------------- internal _filedlg_xxx functions ----------------------------
|
||||
|
||||
FUNCTION _filedlg_doDlg(dlgtype,title,r)
|
||||
DEFINE dlgtype STRING
|
||||
DEFINE title STRING
|
||||
DEFINE r FILEDLG_RECORD
|
||||
DEFINE currpath, path, filename, ftype, dirname, filepath STRING
|
||||
DEFINE delfilename, errstr STRING
|
||||
DEFINE doContinue, i INT
|
||||
DEFINE cb ui.ComboBox
|
||||
|
||||
OPEN WINDOW _filedlg WITH FORM "filedlg"
|
||||
ATTRIBUTES(STYLE='dialog',TEXT=title)
|
||||
|
||||
CALL fgl_settitle(title)
|
||||
|
||||
LET currpath = r.defaultpath
|
||||
LET filename = r.defaultfilename
|
||||
DISPLAY BY NAME filename
|
||||
|
||||
IF currpath="." THEN
|
||||
LET currpath=os.Path.pwd()
|
||||
END IF
|
||||
DISPLAY currpath TO currpath
|
||||
LET cb = ui.ComboBox.forName("formonly.ftype")
|
||||
IF cb IS NULL THEN
|
||||
DISPLAY "ERROR:form field \"ftype\" not found in form filedlg"
|
||||
EXIT PROGRAM
|
||||
END IF
|
||||
FOR i=1 TO r.types.getLength()
|
||||
CALL cb.addItem(r.types[i].suffixes,r.types[i].description)
|
||||
END FOR
|
||||
LET ftype=r.types[1].suffixes
|
||||
DIALOG ATTRIBUTE(UNBUFFERED)
|
||||
--use a DISPLAY ARRAY for showing the file list
|
||||
DISPLAY ARRAY _filedlg_list TO sr.*
|
||||
BEFORE DISPLAY
|
||||
--the following call is not effective, but should be!!!
|
||||
CALL DIALOG.setActionActive("del",r.opt_delete_files)
|
||||
|
||||
BEFORE ROW
|
||||
IF _filedlg_list[arr_curr()].etype<>C_DIRECTORY THEN
|
||||
LET filename=_filedlg_list[arr_curr()].entry
|
||||
DISPLAY BY NAME filename
|
||||
END IF
|
||||
|
||||
ON ACTION del --ask for deleting the highlighted file
|
||||
LET delfilename=_filedlg_list[arr_curr()].entry
|
||||
IF _filedlg_mbox_yn("Confirm delete",sfmt("Really delete '%1'?",delfilename),"question") THEN
|
||||
CALL _file_delete(os.Path.join(currpath,delfilename))
|
||||
CALL _filedlg_fetch_filenames(DIALOG,currpath,ftype,NULL)
|
||||
END IF
|
||||
|
||||
END DISPLAY
|
||||
|
||||
INPUT BY NAME filename,ftype ATTRIBUTE(WITHOUT DEFAULTS)
|
||||
--when the type combobox changes we need to redisplay
|
||||
ON CHANGE ftype
|
||||
CALL _filedlg_fetch_filenames(DIALOG,currpath,ftype,NULL)
|
||||
|
||||
END INPUT
|
||||
|
||||
BEFORE DIALOG
|
||||
CALL _filedlg_fetch_filenames(DIALOG,currpath,ftype,filename)
|
||||
IF dlgtype = C_SAVE THEN
|
||||
NEXT FIELD filename
|
||||
END IF
|
||||
|
||||
ON ACTION accept
|
||||
LET doContinue=FALSE
|
||||
--we use DIALOG.getCurrentItem() to detect where the focus currently is
|
||||
--it works similar like INFIELD however also for DISPLAY ARRAY
|
||||
IF DIALOG.getCurrentItem()="sr" THEN
|
||||
--we are in the display array
|
||||
LET filepath = os.Path.join(currpath,_filedlg_list[arr_curr()].entry)
|
||||
ELSE
|
||||
--not in display array
|
||||
LET filepath = os.Path.join(currpath,filename)
|
||||
END IF
|
||||
IF os.Path.exists(filepath) AND os.Path.isdirectory(filepath) THEN
|
||||
--switch the directory and refill the array
|
||||
LET currpath=_file_normalize_dir(filepath)
|
||||
CALL _filedlg_fetch_filenames(DIALOG,filepath,ftype,"..")
|
||||
DISPLAY BY NAME currpath
|
||||
LET filename=""
|
||||
LET doContinue=TRUE
|
||||
END IF
|
||||
IF NOT doContinue AND dlgtype = C_OPEN THEN
|
||||
IF NOT os.Path.exists(filepath) THEN
|
||||
LET errstr=SFMT(%"file '%1' does not exist!",
|
||||
os.Path.basename(filepath))
|
||||
CALL _filedlg_mbox_ok("Error", errstr, "stop")
|
||||
ERROR errstr
|
||||
LET doContinue=TRUE
|
||||
END IF
|
||||
END IF
|
||||
IF NOT doContinue AND dlgtype = C_SAVE THEN
|
||||
LET dirname=_file_get_dirname(filepath)
|
||||
IF NOT os.Path.exists(dirname) THEN
|
||||
CALL _filedlg_mbox_ok("Error", SFMT(%"directory '%1' does not exist!",filepath), "stop")
|
||||
LET doContinue=TRUE
|
||||
END IF
|
||||
END IF
|
||||
IF NOT doContinue THEN
|
||||
EXIT DIALOG
|
||||
END IF
|
||||
|
||||
ON ACTION cancel
|
||||
LET filepath=NULL
|
||||
EXIT DIALOG
|
||||
|
||||
ON ACTION move_up --move 1 directory level up
|
||||
LET path = _file_get_dirname(currpath)
|
||||
CALL _filedlg_fetch_filenames(DIALOG,path,ftype,currpath)
|
||||
LET currpath=path
|
||||
DISPLAY BY NAME currpath
|
||||
|
||||
|
||||
END DIALOG
|
||||
CLOSE WINDOW _filedlg
|
||||
RETURN filepath
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION _filedlg_fetch_filenames(d,currpath,typelist,currfile)
|
||||
DEFINE d ui.Dialog
|
||||
DEFINE currpath STRING
|
||||
DEFINE typelist STRING
|
||||
DEFINE currfile STRING
|
||||
DEFINE i,len,found INT
|
||||
DEFINE st base.StringTokenizer
|
||||
LET st = base.StringTokenizer.create(typelist,"|")
|
||||
CALL m_typearr.clear()
|
||||
WHILE st.hasMoreTokens()
|
||||
LET m_typearr[m_typearr.getLength()+1]=st.nextToken()
|
||||
END WHILE
|
||||
LET m_typelen=m_typearr.getLength()
|
||||
CALL _filedlg_getfiles_int(currpath)
|
||||
LET len=_filedlg_list.getLength()
|
||||
--jump to the current file
|
||||
LET currfile=os.Path.basename(currfile)
|
||||
FOR i=1 TO len
|
||||
IF currfile=_filedlg_list[i].entry THEN
|
||||
LET found=1
|
||||
CALL d.setCurrentRow("sr",i)
|
||||
EXIT FOR
|
||||
END IF
|
||||
END FOR
|
||||
IF NOT found THEN
|
||||
CALL d.setCurrentRow("sr",1)
|
||||
END IF
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION _filedlg_getfiles_int(dirpath)
|
||||
DEFINE dirpath STRING
|
||||
DEFINE dh, isdir INTEGER
|
||||
DEFINE fname, pname, size STRING
|
||||
CALL _filedlg_list.clear()
|
||||
LET dh = os.Path.diropen(dirpath)
|
||||
IF dh == 0 THEN
|
||||
RETURN
|
||||
END IF
|
||||
WHILE TRUE
|
||||
LET fname = os.Path.dirnext(dh)
|
||||
IF fname IS NULL THEN
|
||||
EXIT WHILE
|
||||
END IF
|
||||
IF fname == "." THEN
|
||||
CONTINUE WHILE
|
||||
END IF
|
||||
LET pname = os.Path.join(dirpath,fname)
|
||||
LET isdir=os.Path.isdirectory(pname)
|
||||
IF isdir THEN
|
||||
LET size = NULL
|
||||
ELSE
|
||||
LET size = os.Path.size(pname)
|
||||
END IF
|
||||
CALL _filedlg_appendEntry(isdir,fname,size,os.Path.mtime(pname))
|
||||
END WHILE
|
||||
CALL os.Path.dirclose(dh)
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION _filedlg_in_typearr(type)
|
||||
DEFINE type STRING
|
||||
DEFINE i INT
|
||||
FOR i=1 TO m_typelen
|
||||
IF type=m_typearr[i] THEN
|
||||
RETURN TRUE
|
||||
END IF
|
||||
END FOR
|
||||
RETURN FALSE
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION _filedlg_checktypeandext(ext)
|
||||
DEFINE ext STRING
|
||||
IF _filedlg_in_typearr("*") THEN
|
||||
RETURN TRUE
|
||||
END IF
|
||||
IF ext IS NOT NULL THEN
|
||||
IF _filedlg_in_typearr("*.*") THEN
|
||||
RETURN TRUE
|
||||
END IF
|
||||
IF _filedlg_in_typearr("*"||ext) THEN
|
||||
RETURN TRUE
|
||||
END IF
|
||||
END IF
|
||||
RETURN FALSE
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION _filedlg_appendEntry(isdir,name,size,modDate)
|
||||
DEFINE isdir INT
|
||||
DEFINE name STRING
|
||||
DEFINE size INT
|
||||
DEFINE modDate STRING
|
||||
DEFINE type,image,ext STRING
|
||||
DEFINE len INT
|
||||
IF isdir THEN
|
||||
LET ext=""
|
||||
LET type=C_DIRECTORY
|
||||
LET image="folder"
|
||||
ELSE
|
||||
LET ext = _file_extension(name)
|
||||
LET type = SFMT(%"%1-File",ext)
|
||||
LET image="file"
|
||||
END IF
|
||||
IF NOT isdir AND NOT _filedlg_checktypeandext(ext) THEN
|
||||
RETURN
|
||||
END IF
|
||||
CALL _filedlg_list.appendElement()
|
||||
LET len=_filedlg_list.getLength()
|
||||
LET _filedlg_list[len].entry = name
|
||||
LET _filedlg_list[len].etype = type
|
||||
LET _filedlg_list[len].eimage = image
|
||||
LET _filedlg_list[len].esize = size
|
||||
LET _filedlg_list[len].emodt = modDate
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION _filedlg_mbox_ok(title,message,icon)
|
||||
DEFINE title, message, icon STRING
|
||||
CALL fgl_winMessage(title,message,icon)
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION _filedlg_mbox_yn(title,message,icon)
|
||||
DEFINE title, message, icon STRING
|
||||
DEFINE r STRING
|
||||
LET r = fgl_winQuestion(title,message,"yes","yes|no",icon,0)
|
||||
RETURN ( r == "yes" )
|
||||
END FUNCTION
|
||||
|
||||
-------------------------- _file helpers ---------------------------------------
|
||||
|
||||
FUNCTION _file_get_dirname(filename)
|
||||
DEFINE filename STRING
|
||||
DEFINE dirname STRING
|
||||
LET dirname=os.Path.dirname(filename)
|
||||
IF dirname IS NULL THEN
|
||||
LET dirname="."
|
||||
END IF
|
||||
RETURN dirname
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION _file_extension(filename)
|
||||
DEFINE filename STRING
|
||||
DEFINE extension STRING
|
||||
LET extension=os.Path.extension(filename)
|
||||
IF extension IS NOT NULL THEN
|
||||
LET extension=".",extension
|
||||
END IF
|
||||
RETURN extension
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION _file_delete(filename)
|
||||
DEFINE filename STRING
|
||||
IF NOT os.Path.delete(filename) THEN
|
||||
CALL _filedlg_mbox_ok("Error",sfmt("Can't delete %1",filename),"stop")
|
||||
END IF
|
||||
END FUNCTION
|
||||
|
||||
--normalizes a given directory name
|
||||
--example /home/foo/bar/../spong -> /home/foo/spong
|
||||
FUNCTION _file_normalize_dir(fname)
|
||||
DEFINE fname STRING
|
||||
DEFINE cmd STRING
|
||||
DEFINE arr DYNAMIC ARRAY OF STRING
|
||||
IF NOT os.Path.isdirectory(fname) THEN
|
||||
RETURN fname
|
||||
END IF
|
||||
IF fgl_getenv("WINDIR") IS NOT NULL THEN
|
||||
LET cmd="cd ",fname,"&&cd"
|
||||
ELSE
|
||||
LET cmd="cd ",fname,"&&pwd"
|
||||
END IF
|
||||
IF NOT _file_get_output(cmd,arr) THEN
|
||||
RETURN fname
|
||||
END IF
|
||||
RETURN arr[1]
|
||||
END FUNCTION
|
||||
|
||||
--runs a program and returns an array with the output lines,
|
||||
--used by _file_normalize_dir
|
||||
FUNCTION _file_get_output(program,arr)
|
||||
DEFINE program,linestr STRING
|
||||
DEFINE arr DYNAMIC ARRAY OF STRING
|
||||
DEFINE mystatus,idx INTEGER
|
||||
DEFINE c base.Channel
|
||||
LET c = base.channel.create()
|
||||
CALL c.setDelimiter("")
|
||||
WHENEVER ERROR CONTINUE
|
||||
CALL c.openpipe(program,"r")
|
||||
LET mystatus=status
|
||||
WHENEVER ERROR STOP
|
||||
IF mystatus THEN
|
||||
DISPLAY "error in file_get_output(program,arr)"
|
||||
RETURN 0
|
||||
END IF
|
||||
CALL arr.clear()
|
||||
WHILE (linestr:=c.readline()) IS NOT NULL
|
||||
LET idx=idx+1
|
||||
LET arr[idx]=linestr
|
||||
END WHILE
|
||||
CALL c.close()
|
||||
RETURN 1
|
||||
END FUNCTION
|
||||
@@ -0,0 +1,53 @@
|
||||
# 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 del (TEXT="Delete",ACCELERATOR=Delete)
|
||||
END
|
||||
LAYOUT(text="",style="dialog3")
|
||||
GRID
|
||||
{
|
||||
[lfd |path |bu ]
|
||||
<T t1 >
|
||||
[ei |entry |esize |emodt |etype ]
|
||||
[ei |entry |esize |emodt |etype ]
|
||||
[ei |entry |esize |emodt |etype ]
|
||||
[ei |entry |esize |emodt |etype ]
|
||||
[ei |entry |esize |emodt |etype ]
|
||||
[ei |entry |esize |emodt |etype ]
|
||||
[ei |entry |esize |emodt |etype ]
|
||||
[ei |entry |esize |emodt |etype ]
|
||||
|
||||
[lfn |file | open]
|
||||
[lft |ftype | canc]
|
||||
}
|
||||
END
|
||||
|
||||
ATTRIBUTES
|
||||
LABEL lfd:text="Directory:";
|
||||
path = FORMONLY.currpath,SCROLL,comment="Displays the current directory";
|
||||
BUTTON bu : move_up,image="uplevel",comment="Moves one directory up";
|
||||
|
||||
IMAGE ei = FORMONLY.eimage;
|
||||
entry = FORMONLY.entry, TITLE="Name";
|
||||
esize = FORMONLY.esize, TITLE="Size", JUSTIFY = RIGHT;
|
||||
emodt = FORMONLY.emodt, TITLE="Time";
|
||||
etype = FORMONLY.etype, TITLE="Type";
|
||||
|
||||
LABEL lfn:text="File:";
|
||||
LABEL lft:text="Files of type:";
|
||||
file = FORMONLY.filename,SCROLL,COMMENT="Input the wanted file name";
|
||||
COMBOBOX ftype = FORMONLY.ftype,SCROLL,sizepolicy=DYNAMIC,NOT NULL;
|
||||
BUTTON open:accept,text="Open";
|
||||
BUTTON canc:cancel,text="Cancel";
|
||||
END
|
||||
|
||||
INSTRUCTIONS
|
||||
SCREEN RECORD sr(eimage THRU etype);
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# 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 filedlg
|
||||
|
||||
MAIN
|
||||
DEFINE cname STRING
|
||||
DEFINE r1 FILEDLG_RECORD
|
||||
DEFINE r2 FILEDLG_RECORD
|
||||
|
||||
OPTIONS INPUT WRAP
|
||||
|
||||
DISPLAY "Try options in the action panel" AT 5,5
|
||||
|
||||
MENU "File dialog"
|
||||
|
||||
COMMAND "Choose .per"
|
||||
-- get back an existing .per filename
|
||||
LET r1.title="Please choose a form"
|
||||
LET r1.defaultfilename="filedlg.per"
|
||||
LET r1.types[1].description="Form files (*.per)"
|
||||
LET r1.types[1].suffixes="*.per"
|
||||
LET r1.types[2].description="All files (*)"
|
||||
LET r1.types[2].suffixes="*"
|
||||
LET r1.opt_delete_files=TRUE
|
||||
LET cname= filedlg_open(r1.*)
|
||||
MESSAGE ">>>>chosen file:",cname
|
||||
|
||||
COMMAND "New .per"
|
||||
-- get back a name for a file not existing yet
|
||||
LET r2.title="Please enter a filename for save"
|
||||
LET r2.defaultfilename="spong"
|
||||
LET r2.types[1].description="All files (*.*)"
|
||||
LET r2.types[1].suffixes="*.*"
|
||||
LET cname= filedlg_save(r2.*)
|
||||
MESSAGE ">>>>new file:",cname
|
||||
|
||||
COMMAND "Quit"
|
||||
EXIT MENU
|
||||
|
||||
END MENU
|
||||
|
||||
END MAIN
|
||||
@@ -0,0 +1,58 @@
|
||||
# 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 donations
|
||||
DEFINE fname,last VARCHAR(50)
|
||||
|
||||
DEFINE m_details RECORD
|
||||
clubmember INT,
|
||||
otherdetail CHAR(50)
|
||||
END RECORD
|
||||
|
||||
MAIN
|
||||
OPEN FORM f FROM "Folder"
|
||||
OPTIONS INPUT WRAP
|
||||
DISPLAY FORM f
|
||||
LET fname="Willi"
|
||||
CALL donations.fill()
|
||||
DIALOG ATTRIBUTES(UNBUFFERED)
|
||||
INPUT BY NAME fname,last ATTRIBUTES(WITHOUT DEFAULTS)
|
||||
AFTER INPUT
|
||||
--some checking if we changed things
|
||||
IF FIELD_TOUCHED(fname) OR FIELD_TOUCHED(last) THEN
|
||||
DISPLAY "Update customer in the database"
|
||||
CALL DIALOG.setFieldTouched("fname",0)
|
||||
CALL DIALOG.setFieldTouched("last",0)
|
||||
END IF
|
||||
END INPUT
|
||||
SUBDIALOG donations
|
||||
INPUT m_details.* FROM details.*
|
||||
AFTER FIELD otherdetail
|
||||
--just have a uselesse contraint here to check we come thru
|
||||
--in case of leaving the folder
|
||||
IF m_details.otherdetail<>'o' THEN
|
||||
ERROR "otherdetail must be 'o'"
|
||||
NEXT FIELD otherdetail
|
||||
END IF
|
||||
AFTER INPUT
|
||||
--some checking if we changed things
|
||||
IF FIELD_TOUCHED(details.*) THEN
|
||||
DISPLAY "Update detail in the database"
|
||||
CALL DIALOG.setFieldTouched("details.*",0)
|
||||
END IF
|
||||
END INPUT
|
||||
ON ACTION accept
|
||||
DISPLAY "accept"
|
||||
ACCEPT DIALOG
|
||||
ON ACTION cancel
|
||||
EXIT DIALOG
|
||||
END DIALOG
|
||||
END MAIN
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# 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 cancel(VALIDATE=NO)
|
||||
END
|
||||
LAYOUT
|
||||
VBOX
|
||||
GROUP(TEXT="Customer (Master Record)")
|
||||
GRID
|
||||
{
|
||||
First Name:[fname ] Last Name:[last ]
|
||||
}
|
||||
END
|
||||
END --GROUP
|
||||
FOLDER
|
||||
PAGE(TEXT="Donations",COMMENT="edits the customers donations")
|
||||
FORM "donations"
|
||||
END --PAGE
|
||||
PAGE(TEXT="More Details",COMMENT="edits some more customer details")
|
||||
GROUP(TEXT="some data")
|
||||
GRID
|
||||
{
|
||||
Member in the organization [clubmember]
|
||||
Other detail [otherdetail ]
|
||||
|
||||
|
||||
}
|
||||
END
|
||||
END
|
||||
END
|
||||
END
|
||||
END
|
||||
|
||||
ATTRIBUTES
|
||||
fname = FORMONLY.fname;
|
||||
last = FORMONLY.last,NOT NULL;
|
||||
|
||||
CHECKBOX clubmember = FORMONLY.clubmember,NOT NULL;
|
||||
otherdetail = FORMONLY.otherdetail,REQUIRED;
|
||||
|
||||
|
||||
INSTRUCTIONS
|
||||
SCREEN RECORD cr(FORMONLY.fname,FORMONLY.last);
|
||||
SCREEN RECORD details(FORMONLY.clubmember THRU FORMONLY.otherdetail);
|
||||
@@ -0,0 +1,61 @@
|
||||
# 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 donations
|
||||
DEFINE fname,last VARCHAR(50)
|
||||
|
||||
DEFINE m_details RECORD
|
||||
clubmember INT,
|
||||
otherdetail CHAR(50)
|
||||
END RECORD
|
||||
|
||||
MAIN
|
||||
DEFINE i INT
|
||||
OPTIONS INPUT WRAP
|
||||
FOR i=1 TO 2
|
||||
IF i=1 THEN
|
||||
OPEN FORM f FROM "Folder_unrolled"
|
||||
ELSE
|
||||
OPEN FORM f FROM "Folder"
|
||||
END IF
|
||||
DISPLAY FORM f
|
||||
LET fname="Willi"
|
||||
CALL donations.fill()
|
||||
DIALOG ATTRIBUTES(UNBUFFERED)
|
||||
INPUT BY NAME fname,last ATTRIBUTES(WITHOUT DEFAULTS)
|
||||
END INPUT
|
||||
SUBDIALOG donations
|
||||
INPUT m_details.* FROM details.*
|
||||
AFTER FIELD otherdetail
|
||||
--just have a uselesse contraint here to check we come thru
|
||||
--in case of leaving the folder
|
||||
IF m_details.otherdetail<>'o' THEN
|
||||
ERROR "otherdetail must be 'o'"
|
||||
NEXT FIELD otherdetail
|
||||
END IF
|
||||
AFTER INPUT
|
||||
END INPUT
|
||||
ON ACTION accept
|
||||
ACCEPT DIALOG
|
||||
ON ACTION cancel
|
||||
EXIT DIALOG
|
||||
AFTER DIALOG
|
||||
--do complete database handling here
|
||||
DISPLAY "AFTER DIALOG:save customer to database"
|
||||
FOR i=1 TO arr_count()
|
||||
DISPLAY "eventually save donation row :",i
|
||||
END FOR
|
||||
IF FIELD_TOUCHED(details.*) THEN
|
||||
DISPLAY "Update detail in the database"
|
||||
END IF
|
||||
END DIALOG
|
||||
CLOSE FORM f
|
||||
END FOR
|
||||
END MAIN
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
# 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.
|
||||
|
||||
DEFINE fname,last VARCHAR(50)
|
||||
DEFINE donations DYNAMIC ARRAY OF RECORD
|
||||
p_date DATE,
|
||||
p_amount MONEY,
|
||||
p_reason VARCHAR(250)
|
||||
END RECORD
|
||||
|
||||
DEFINE m_details RECORD
|
||||
clubmember INT,
|
||||
otherdetail CHAR(50)
|
||||
END RECORD
|
||||
|
||||
MAIN
|
||||
DEFINE state STRING
|
||||
DEFINE flag INT
|
||||
OPTIONS INPUT WRAP
|
||||
OPEN FORM f FROM "Folder_old"
|
||||
DISPLAY FORM f
|
||||
CALL fill_arrays()
|
||||
--manage the 'state' variable to jump into different dialogs depending
|
||||
--on the state value
|
||||
LET fname="Willi"
|
||||
LET state="master" --initially go to the master input
|
||||
WHILE state="master" OR state="page1" OR state="page2"
|
||||
LET int_flag=FALSE
|
||||
CASE state
|
||||
--master input
|
||||
WHEN "master"
|
||||
LET state="invalid"
|
||||
INPUT BY NAME fname, last WITHOUT DEFAULTS
|
||||
--no master
|
||||
ON ACTION page1 LET state="page1" ACCEPT INPUT
|
||||
ON ACTION page2 LET state="page2" ACCEPT INPUT
|
||||
ON ACTION accept LET state="ok" ACCEPT INPUT
|
||||
AFTER INPUT
|
||||
IF int_flag THEN
|
||||
LET state="cancel"
|
||||
EXIT INPUT
|
||||
END IF
|
||||
--some checking if we changed things
|
||||
IF FIELD_TOUCHED(fname) OR FIELD_TOUCHED(last) THEN
|
||||
DISPLAY "AFTER INPUT master, save to the database"
|
||||
END IF
|
||||
END INPUT
|
||||
--donations input
|
||||
WHEN "page1"
|
||||
LET state="invalid"
|
||||
INPUT ARRAY donations WITHOUT DEFAULTS FROM donations.* ATTRIBUTES(UNBUFFERED)
|
||||
ON ACTION master LET state="master" ACCEPT INPUT
|
||||
--no page1
|
||||
ON ACTION page2 LET state="page2" ACCEPT INPUT
|
||||
ON ACTION accept LET state="ok" ACCEPT INPUT
|
||||
AFTER FIELD p_amount
|
||||
IF donations[arr_curr()].p_amount<2.0 THEN
|
||||
ERROR "must be at least 2 dollar:-)"
|
||||
NEXT FIELD CURRENT
|
||||
END IF
|
||||
AFTER ROW
|
||||
DISPLAY "AFTER ROW donations arr_curr:",arr_curr(),",arr_count:",arr_count()
|
||||
IF int_flag THEN --check int_flag in AFTER ROW in contradiction to
|
||||
--checking int_flag for INPUT in AFTER INPUT
|
||||
LET state="cancel"
|
||||
EXIT INPUT
|
||||
END IF
|
||||
IF arr_curr()<=arr_count() THEN
|
||||
DISPLAY "AFTER ROW donations:check row and save to database"
|
||||
END IF
|
||||
AFTER INPUT
|
||||
DISPLAY "AFTER INPUT donations,state:",state
|
||||
END INPUT
|
||||
--more details input
|
||||
WHEN "page2"
|
||||
LET state="invalid"
|
||||
INPUT m_details.* FROM details.* ATTRIBUTES(UNBUFFERED,WITHOUT DEFAULTS=flag)
|
||||
ON ACTION master LET state="master" ACCEPT INPUT
|
||||
ON ACTION page1 LET state="page1" ACCEPT INPUT
|
||||
--no page2
|
||||
ON ACTION accept LET state="ok" ACCEPT INPUT
|
||||
AFTER FIELD otherdetail
|
||||
--just have a uselesse contraint here to check we come thru
|
||||
--in case of leaving the folder
|
||||
IF m_details.otherdetail<>'o' THEN
|
||||
ERROR "otherdetail must be 'o'"
|
||||
NEXT FIELD otherdetail
|
||||
END IF
|
||||
AFTER INPUT
|
||||
IF int_flag THEN
|
||||
LET state="cancel"
|
||||
EXIT INPUT
|
||||
END IF
|
||||
DISPLAY "AFTER INPUT details,state:",state
|
||||
--some checking if we changed things
|
||||
IF FIELD_TOUCHED(details.*) THEN
|
||||
DISPLAY "Update detail in the database"
|
||||
LET flag=1
|
||||
END IF
|
||||
END INPUT
|
||||
END CASE
|
||||
END WHILE
|
||||
CASE state
|
||||
WHEN "ok"
|
||||
DISPLAY "save"
|
||||
WHEN "cancel"
|
||||
DISPLAY "cancel"
|
||||
WHEN "invalid"
|
||||
DISPLAY "error occured"
|
||||
END CASE
|
||||
END MAIN
|
||||
|
||||
FUNCTION fill_arrays()
|
||||
DEFINE i INT
|
||||
FOR i=1 TO 5
|
||||
LET donations[i].p_date = TODAY -10 +i
|
||||
LET donations[i].p_amount = i+1
|
||||
END FOR
|
||||
END FUNCTION
|
||||
@@ -0,0 +1,65 @@
|
||||
# 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
|
||||
VBOX
|
||||
GROUP(TEXT="Customer (Master Record)")
|
||||
GRID
|
||||
{
|
||||
First Name:[fname ] Last Name:[last ] [bmaster]
|
||||
}
|
||||
END --GRID
|
||||
END --GROUP
|
||||
FOLDER
|
||||
PAGE(TEXT="Donations",COMMENT="edits the customers donations",ACTION=page1)
|
||||
TABLE
|
||||
{
|
||||
DATE Amount Reason
|
||||
[p_date][p_amount ][p_reason ]
|
||||
[p_date][p_amount ][p_reason ]
|
||||
[p_date][p_amount ][p_reason ]
|
||||
[p_date][p_amount ][p_reason ]
|
||||
[p_date][p_amount ][p_reason ]
|
||||
[p_date][p_amount ][p_reason ]
|
||||
[p_date][p_amount ][p_reason ]
|
||||
[p_date][p_amount ][p_reason ]
|
||||
[p_date][p_amount ][p_reason ]
|
||||
}
|
||||
END
|
||||
END --PAGE
|
||||
PAGE(TEXT="More Details",COMMENT="edits some more customer details",ACTION=page2)
|
||||
GROUP(TEXT="some data")
|
||||
GRID
|
||||
{
|
||||
Member in the organization [clubmember]
|
||||
Other detail [otherdetail ]
|
||||
|
||||
|
||||
}
|
||||
END --GRID
|
||||
END --GROUP
|
||||
END --PAGE
|
||||
END --FOLDER
|
||||
END --VBOX
|
||||
|
||||
ATTRIBUTES
|
||||
fname = FORMONLY.fname;
|
||||
last = FORMONLY.last,NOT NULL;
|
||||
BUTTON bmaster:master,TEXT="Edit Master";
|
||||
p_date = FORMONLY.p_date,NOT NULL,DEFAULT=TODAY;
|
||||
p_amount = FORMONLY.p_amount,NOT NULL;
|
||||
p_reason = FORMONLY.p_reason,NOT NULL;
|
||||
|
||||
CHECKBOX clubmember = FORMONLY.clubmember,NOT NULL,DEFAULT="";
|
||||
otherdetail = FORMONLY.otherdetail,REQUIRED;
|
||||
|
||||
INSTRUCTIONS
|
||||
SCREEN RECORD cr(FORMONLY.fname,FORMONLY.last);
|
||||
SCREEN RECORD donations(FORMONLY.p_date THRU FORMONLY.p_reason);
|
||||
SCREEN RECORD details(FORMONLY.clubmember THRU FORMONLY.otherdetail);
|
||||
@@ -0,0 +1,44 @@
|
||||
# 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(TEXT="Folders unrolled to a plain form")
|
||||
VBOX
|
||||
GROUP(TEXT="Customer (Master Record)")
|
||||
GRID
|
||||
{
|
||||
First Name:[fname ] Last Name:[last ]
|
||||
}
|
||||
END
|
||||
END --GROUP
|
||||
GROUP (TEXT="Donations")
|
||||
FORM "donations"
|
||||
END --GROUP
|
||||
GROUP(TEXT="some data")
|
||||
GRID
|
||||
{
|
||||
Member in the organization [clubmember]
|
||||
Other detail [otherdetail ]
|
||||
|
||||
|
||||
}
|
||||
END --GRID
|
||||
END --GROUP
|
||||
END --VBOX
|
||||
|
||||
ATTRIBUTES
|
||||
fname = FORMONLY.fname;
|
||||
last = FORMONLY.last,NOT NULL;
|
||||
|
||||
CHECKBOX clubmember = FORMONLY.clubmember,NOT NULL;
|
||||
otherdetail = FORMONLY.otherdetail,REQUIRED;
|
||||
|
||||
|
||||
INSTRUCTIONS
|
||||
SCREEN RECORD cr(FORMONLY.fname,FORMONLY.last);
|
||||
SCREEN RECORD details(FORMONLY.clubmember THRU FORMONLY.otherdetail);
|
||||
@@ -0,0 +1,21 @@
|
||||
# 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 = \
|
||||
Folder.42m \
|
||||
Folder_old.42m\
|
||||
Folder2.42m
|
||||
|
||||
FORMS = \
|
||||
donations.42f \
|
||||
Folder.42f\
|
||||
Folder_old.42f\
|
||||
Folder_unrolled.42f
|
||||
|
||||
include ../../Makeincl
|
||||
@@ -0,0 +1,57 @@
|
||||
Folder example
|
||||
--------------
|
||||
|
||||
This example shows how to use a master record and multiple sub dialogs to input details in several folder pages.
|
||||
|
||||
To show how complex it was before, there is a Folder_old.4gl example that compiles with older versions and does basically the same as the Folder.4gl sample.
|
||||
Each sub dialog contains some useful validation code to prove that we are coming thru when jumping from one page to the other.
|
||||
|
||||
Folder_old.4gl:
|
||||
A big while loop was needed with page actions in each page to leave the current interaction statement, set the state, and jump into the next dialog. The difficulty in leaving the dialogs to move to another page is in using the ACCEPT INPUT statement properly (introduced for this purpose). Otherwise, the AFTER FIELD/AFTER ROW/AFTER INPUT triggers are not called (exiting the current dialog with EXIT INPUT will cause this to happen, and you will need to call your validations again for each page action).
|
||||
You need to set the correct state before each ACCEPT INPUT, also for the 'accept' action, because if a validation in between fails, state still has the value of the last clicked action. In the cancel case, we need to check int_flag.
|
||||
If you want to add another sub dialog, you must edit all existing dialog statements to add the new action for activating the new sub dialog.
|
||||
It's annoying that it is not possible for the user to simply click into the master dialog if the focus is in one of the pages.
|
||||
You cant't tab simply from the master to the details.
|
||||
When leaving a particular INPUT, you should check if something has been changed for the insert/update case.
|
||||
It's obvious that this programming style is only a vehicle to make things happen, and the visual results are also not fully satisfying.
|
||||
|
||||
|
||||
Folder.4gl
|
||||
All those headaches now disappear when you use the DIALOG statement.
|
||||
Each page can be reached without the need for an action to raise it:
|
||||
the master record is always reachable with this mode. The AFTER FIELD/(AFTER ROW)/AFTER INPUT triggers of one sub dialog are called when you move to another sub dialog. In the AFTER INPUT trigger you can place database instructions as in the Folder_old sample.
|
||||
But, just like in the old sample, a sub dialog can be visited multiple times, so it must be checked in case something has to be inserted/updated.
|
||||
If we want to reset the internal flags that indicate a field of a sub dialog has been changed, we must call the DIALOG.setTouchedFlag() function (of course, there are also other methods to check whether a field has changed: make a record copy or use ON CHANGE).
|
||||
To indicate that a validation has gone wrong, the NEXT FIELD statement is used (as usual) to interrupt the DIALOG's internal state machine and stay in the current (invalid) sub dialog.
|
||||
|
||||
Folder2.4gl
|
||||
The Folder2 sample shows that it's also possible to fill in all data in the sub dialogs first, and to do the database activity later in the AFTER DIALOG. This lets the database code run at one central place of the multiple dialog in a transaction. It also avoids checking whether a sub dialog has been entered/left twice.
|
||||
The 'ok' action then starts the transaction, the 'cancel' action cancels everything (with no need to cleanup things done earlier in the dialog).
|
||||
The Folder2 sample also shows that there is no difference in how the sub dialogs work if they are not placed inside folders, by running the same code again in another form (with no folder at all).
|
||||
|
||||
Source code splitting:
|
||||
------------------------------
|
||||
In the Folder_old.4gl it was possible to put the code for the individual (sub) dialogs into different source files (not actually done in the sample )
|
||||
For example:
|
||||
|
||||
WHEN "page1"
|
||||
LET state="invalid"
|
||||
INPUT ARRAY donations WITHOUT DEFAULTS FROM donations.* ATTRIBUTES(UNBUFFERED)
|
||||
...
|
||||
END INPUT
|
||||
|
||||
could be coded by using a function call to perform the INPUT ARRAY
|
||||
|
||||
WHEN "page1"
|
||||
LET state="invalid"
|
||||
CALL input_donations()
|
||||
|
||||
and "input_donations" could reside in an extra source code module. This could be done for all other dialogs used in the WHILE loop. This helps to prevent a huge monolithic source code module, regardless of the fact that the modules have a strong affinity in terms of the folder states.
|
||||
|
||||
Now, with multiple dialogs and SUBDIALOGs and embeddable FORM's this is done but in a much cleaner way.
|
||||
Each SUBDIALOG can be moved into an individual module and the form belonging to that source code module can be in a separate form file which is then included via "FORM" in the master form.
|
||||
In the example this is done for the "donations" sub DIALOG. It resides in a module called "donations.4gl" and the form for that module is in "donations.per".
|
||||
Extra sugar: donations.4gl contains a MAIN function which can call the individual dialog standalone for testing (just do fglrun donations)
|
||||
This shows that there is no relation to the master dialog anymore,
|
||||
it helps much for splitting the work for individual dialogs into separate testable pieces.
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# 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.
|
||||
|
||||
DEFINE donations DYNAMIC ARRAY OF RECORD
|
||||
p_date DATE,
|
||||
p_amount MONEY,
|
||||
p_reason VARCHAR(250)
|
||||
END RECORD
|
||||
|
||||
FUNCTION fill()
|
||||
DEFINE i INT
|
||||
FOR i=1 TO 5
|
||||
LET donations[i].p_date = TODAY -10 +i
|
||||
LET donations[i].p_amount = i+1
|
||||
END FOR
|
||||
END FUNCTION
|
||||
|
||||
DIALOG donations()
|
||||
INPUT ARRAY donations FROM donations.*
|
||||
AFTER FIELD p_amount
|
||||
IF donations[arr_curr()].p_amount<2.0 THEN
|
||||
ERROR "must be at least 2 dollar:-)"
|
||||
NEXT FIELD CURRENT
|
||||
END IF
|
||||
AFTER ROW
|
||||
IF arr_curr()<=arr_count() THEN
|
||||
DISPLAY "AFTER ROW donations:check row and save to database"
|
||||
END IF
|
||||
END INPUT
|
||||
END DIALOG
|
||||
|
||||
FUNCTION MAIN() -- This MAIN is for testing only.
|
||||
OPEN FORM f FROM "donations"
|
||||
DISPLAY FORM f
|
||||
CALL FILL()
|
||||
DIALOG
|
||||
SUBDIALOG donations
|
||||
ON ACTION ACCEPT
|
||||
ACCEPT DIALOG
|
||||
END DIALOG
|
||||
END FUNCTION
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# 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
|
||||
{
|
||||
DATE Amount Reason
|
||||
[p_date][p_amount ][p_reason ]
|
||||
[p_date][p_amount ][p_reason ]
|
||||
[p_date][p_amount ][p_reason ]
|
||||
[p_date][p_amount ][p_reason ]
|
||||
[p_date][p_amount ][p_reason ]
|
||||
[p_date][p_amount ][p_reason ]
|
||||
[p_date][p_amount ][p_reason ]
|
||||
[p_date][p_amount ][p_reason ]
|
||||
[p_date][p_amount ][p_reason ]
|
||||
}
|
||||
END
|
||||
END
|
||||
ATTRIBUTES
|
||||
p_date = FORMONLY.p_date,NOT NULL,DEFAULT=TODAY;
|
||||
p_amount = FORMONLY.p_amount,NOT NULL;
|
||||
p_reason = FORMONLY.p_reason,NOT NULL;
|
||||
INSTRUCTIONS
|
||||
SCREEN RECORD donations(FORMONLY.*);
|
||||
@@ -0,0 +1,18 @@
|
||||
# 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= \
|
||||
formwizard.42m \
|
||||
formwizard_old.42m
|
||||
|
||||
FORMS= \
|
||||
formwizard.42f \
|
||||
formwizard_old.42f
|
||||
|
||||
include ../../Makeincl
|
||||
@@ -0,0 +1,56 @@
|
||||
Form wizard
|
||||
-----------
|
||||
|
||||
This is an example to mimic the main component of the Microsoft Access form wizard, the dialog to choose which database columns should be handled in the generated form.
|
||||
It turned out that it is pretty easy to mimic with the help of the multiple dialogs; it behaves exactly like the original dialog (with the same logic behind and all Keyboard shortcuts).
|
||||
The code to do this is pretty short and readable.
|
||||
This dialog is used in the $FGLDIR/demo/tools/fglped program to generate a form from a list of chosen columns.
|
||||
|
||||
formwizard_old
|
||||
This example shows what you would have to do (theoretically) to program a similar dialog in 4GL without multiple dialogs available. As you can see in the wizard() function, 3 tradional dialogs are called in a loop and a lot of actions are needed to jump from one dialog to the other. 3 additional buttons are inside the form to leave the current active dialog and jump to a different dialog...pure headache!
|
||||
Unfortunately the GDC by default renders non-active tables in the same way as active tables, so the end user may think that he could click on a table and edit there; but, bummer, you must use the "Edit Left/Right" button or <Tab> to leave the current active table (Shift-Tab is not even possible).
|
||||
This example also shows why it is not sufficient to add a trigger like
|
||||
ON OTHERTABLECLICKED (demanded by a lot of our customers).
|
||||
Although it makes the "Edit Left/Right" button unnecessary, it still forces you to write such a big state loop of traditional dialogs.
|
||||
|
||||
Further Differences
|
||||
-------------------
|
||||
If you look at the code in formwizard.4gl and formwizard_old.4gl there are also differences in performing the actions.
|
||||
|
||||
|
||||
FUNCTION right(d) --multiple dialog variant
|
||||
DEFINE d ui.Dialog
|
||||
DEFINE idx,lastC INT
|
||||
--get index of the left array; cannot use arr_curr() here because arr_curr()
|
||||
--returns the index of the current active row in the current focused table
|
||||
--which is not necessarily the right hand side array
|
||||
--the "right" action is always active regardless of the focus
|
||||
LET idx = d.getCurrentRow("a")
|
||||
LET lastC=cfields.getLength()+1
|
||||
LET cfields[lastC].ctable=afields[idx].atable
|
||||
LET cfields[lastC].ccol=afields[idx].acol
|
||||
CALL afields.deleteElement(idx)
|
||||
--set the current line to the last index
|
||||
CALL d.setCurrentRow("c",lastC)
|
||||
END FUNCTION
|
||||
|
||||
You may also note that you don't need to refresh the arrays on the screen; this is done with the ATTRIBUTES(UNBUFFERED) flag of the entire dialog. Since all arrays are active, you won't need to refresh one of them manually.
|
||||
Please note that you can do this only for DISPLAY ARRAY; for INPUT ARRAY it is strongly recommended that you don't manipulate the bound arrays with the array methods (deleteElement(), insertElement()) because this conflicts with the built-in triggers of INPUT ARRAY.
|
||||
|
||||
Now the "right" function from the formwizard_old example:
|
||||
|
||||
--move the current item to the right hand side and delete it on the left hand side
|
||||
FUNCTION right(d)
|
||||
DEFINE d ui.Dialog
|
||||
DEFINE idx,lastC,i INT
|
||||
LET idx = arr_curr()
|
||||
LET lastC=cfields.getLength()+1
|
||||
LET cfields[lastC].ctable=afields[idx].atable
|
||||
LET cfields[lastC].ccol=afields[idx].acol
|
||||
CALL afields.deleteElement(idx)
|
||||
FOR i=1 TO cfields.getLength()
|
||||
DISPLAY cfields[i].* TO c[i].*
|
||||
END FOR
|
||||
END FUNCTION
|
||||
|
||||
The "right" action is only active if the focus is inside the left hand side array; that's why it's possible to use arr_curr(). At the end of the function we need to redisplay the right hand side array, because it's not active (we are in a DISPLAY ARRAY of the left hand side).
|
||||
@@ -0,0 +1,239 @@
|
||||
# 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.
|
||||
|
||||
TYPE schema_rec RECORD
|
||||
table_name STRING, -- customer
|
||||
field_name STRING, -- customer_name
|
||||
field_type SMALLINT, -- 0
|
||||
field_strtype STRING, -- CHAR(30)
|
||||
field_length SMALLINT -- 80
|
||||
END RECORD
|
||||
DEFINE col_arr DYNAMIC ARRAY OF schema_rec
|
||||
DEFINE tab_arr DYNAMIC ARRAY OF STRING
|
||||
DEFINE currtable STRING --current table in combobox
|
||||
--a(vailable) fields
|
||||
DEFINE afields DYNAMIC ARRAY OF RECORD
|
||||
atable STRING,
|
||||
acol STRING
|
||||
END RECORD
|
||||
--c(hosen) fields
|
||||
DEFINE cfields DYNAMIC ARRAY OF RECORD
|
||||
ctable STRING,
|
||||
ccol STRING
|
||||
END RECORD
|
||||
-- Current wizard page
|
||||
DEFINE currpage SMALLINT
|
||||
DEFINE myd ui.Dialog
|
||||
CONSTANT TOTAL_PAGES = 5
|
||||
|
||||
MAIN
|
||||
CLOSE WINDOW screen
|
||||
CALL wizard()
|
||||
END MAIN
|
||||
|
||||
--contains the wizard MD dialog
|
||||
FUNCTION wizard()
|
||||
DEFINE i INT
|
||||
CALL readTables("stores.sch")
|
||||
OPEN WINDOW formwizard WITH FORM "formwizard"
|
||||
CALL init_tables()
|
||||
OPTIONS FIELD ORDER FORM
|
||||
DIALOG ATTRIBUTES(UNBUFFERED)
|
||||
INPUT BY NAME currtable ATTRIBUTES(WITHOUT DEFAULTS=TRUE)
|
||||
ON CHANGE currtable
|
||||
CALL on_change_currtable()
|
||||
CALL setup(DIALOG)
|
||||
END INPUT
|
||||
DISPLAY ARRAY afields TO a.* END DISPLAY
|
||||
DISPLAY ARRAY cfields TO c.* END DISPLAY
|
||||
ON ACTION right CALL right(DIALOG)
|
||||
ON ACTION allright CALL allright(DIALOG)
|
||||
ON ACTION left CALL left(DIALOG)
|
||||
ON ACTION allleft CALL allleft(DIALOG)
|
||||
ON ACTION prevwiz DISPLAY "prevwiz" EXIT DIALOG
|
||||
ON ACTION nextwiz DISPLAY "nextwiz" EXIT DIALOG
|
||||
ON ACTION cancel
|
||||
EXIT DIALOG
|
||||
ON ACTION accept
|
||||
ACCEPT DIALOG
|
||||
BEFORE DIALOG
|
||||
LET currpage = 1
|
||||
CALL setup(DIALOG)
|
||||
AFTER DIALOG
|
||||
--we just show all chosen columns in the terminal
|
||||
FOR i=1 TO cfields.getLength()
|
||||
DISPLAY sfmt("table:%1,column:%2",cfields[i].ctable,cfields[i].ccol)
|
||||
END FOR
|
||||
END DIALOG
|
||||
LET myd=NULL
|
||||
CLOSE WINDOW formwizard
|
||||
END FUNCTION
|
||||
|
||||
--fill the combobox with all table names
|
||||
FUNCTION init_tables()
|
||||
DEFINE cb ui.ComboBox
|
||||
DEFINE i INTEGER
|
||||
LET cb = ui.ComboBox.forName("formonly.currtable")
|
||||
IF cb IS NULL THEN
|
||||
CALL myerror("combobox not found")
|
||||
END IF
|
||||
FOR i=1 TO tab_arr.getLength()
|
||||
CALL cb.addItem(tab_arr[i],tab_arr[i])
|
||||
END FOR
|
||||
LET currtable=tab_arr[1]
|
||||
CALL on_change_currtable()
|
||||
END FUNCTION
|
||||
|
||||
--combo value has changed,refill the left array and omit
|
||||
--values of the right hand side
|
||||
FUNCTION on_change_currtable()
|
||||
DEFINE tab,col STRING
|
||||
DEFINE col_len,used_col_len,idx,i,j,foundUsed INT
|
||||
IF myd IS NOT NULL THEN
|
||||
CALL myd.deleteAllRows("a")
|
||||
ELSE
|
||||
CALL afields.clear()
|
||||
END IF
|
||||
LET idx=0
|
||||
LET col_len=col_arr.getLength()
|
||||
FOR i=1 TO col_len
|
||||
LET tab=col_arr[i].table_name
|
||||
LET col=col_arr[i].field_name
|
||||
LET used_col_len=cfields.getLength()
|
||||
LET foundUsed=FALSE
|
||||
FOR j=1 TO used_col_len
|
||||
IF cfields[j].ctable=tab AND cfields[j].ccol=col THEN
|
||||
LET foundUsed=TRUE
|
||||
EXIT FOR
|
||||
END IF
|
||||
END FOR
|
||||
IF NOT foundUsed AND tab=currtable THEN
|
||||
LET idx=idx+1
|
||||
IF myd IS NOT NULL THEN
|
||||
CALL myd.appendRow("a")
|
||||
ELSE
|
||||
CALL afields.appendElement()
|
||||
END IF
|
||||
LET afields[idx].atable=tab
|
||||
LET afields[idx].acol=col
|
||||
END IF
|
||||
END FOR
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION setup(d)
|
||||
DEFINE d ui.Dialog
|
||||
DEFINE rl,rr INT
|
||||
LET myd=d
|
||||
LET rl = afields.getLength()
|
||||
LET rr = cfields.getLength()
|
||||
CALL d.setActionActive("right", rl>0)
|
||||
CALL d.setActionActive("allright", rl>0)
|
||||
CALL d.setActionActive("left", rr>0)
|
||||
CALL d.setActionActive("allleft", rr>0)
|
||||
CALL d.setActionActive("prevwiz", currpage>1)
|
||||
CALL d.setActionActive("nextwiz", currpage<TOTAL_PAGES)
|
||||
END FUNCTION
|
||||
|
||||
--move the current item to the right hand side and delete it on the left hand side
|
||||
FUNCTION right(d)
|
||||
DEFINE d ui.Dialog
|
||||
DEFINE idx,lastC INT
|
||||
LET idx = d.getCurrentRow("a")
|
||||
IF idx<1 THEN RETURN END IF
|
||||
LET lastC=cfields.getLength()+1
|
||||
CALL myd.appendRow("c")
|
||||
LET cfields[lastC].ctable=afields[idx].atable
|
||||
LET cfields[lastC].ccol=afields[idx].acol
|
||||
CALL afields.deleteElement(idx)
|
||||
CALL d.setCurrentRow("c",lastC)
|
||||
CALL setup(d)
|
||||
END FUNCTION
|
||||
|
||||
--move all items to the right hand side
|
||||
FUNCTION allright(d)
|
||||
DEFINE d ui.Dialog
|
||||
DEFINE lastC,i INT
|
||||
LET lastC=cfields.getLength()
|
||||
FOR i=1 TO afields.getLength()
|
||||
LET lastC=lastC+1
|
||||
CALL myd.appendRow("c")
|
||||
LET cfields[lastC].ctable=afields[i].atable
|
||||
LET cfields[lastC].ccol=afields[i].acol
|
||||
END FOR
|
||||
CALL myd.deleteAllRows("a") --CALL afields.clear()
|
||||
CALL d.setCurrentRow("c",lastC)
|
||||
CALL setup(d)
|
||||
END FUNCTION
|
||||
|
||||
--remove the current item from the right hand side and refill the left hand side
|
||||
FUNCTION left(d)
|
||||
DEFINE d ui.Dialog
|
||||
DEFINE idx,i INT
|
||||
DEFINE col, tab STRING
|
||||
LET idx =d.getCurrentRow("c")
|
||||
IF idx<1 THEN RETURN END IF
|
||||
LET tab=cfields[idx].ctable
|
||||
LET col=cfields[idx].ccol
|
||||
CALL cfields.deleteElement(idx)
|
||||
CALL on_change_currtable()
|
||||
FOR i=1 TO afields.getLength()
|
||||
IF afields[i].atable=tab AND afields[i].acol=col THEN
|
||||
CALL d.setCurrentRow("a",i)
|
||||
EXIT FOR
|
||||
END IF
|
||||
END FOR
|
||||
CALL setup(d)
|
||||
END FUNCTION
|
||||
|
||||
--remove all items from the right hand side and refill the left hand side
|
||||
FUNCTION allleft(d)
|
||||
DEFINE d ui.Dialog
|
||||
CALL cfields.clear()
|
||||
CALL on_change_currtable()
|
||||
CALL setup(d)
|
||||
END FUNCTION
|
||||
|
||||
--read in a schema file
|
||||
FUNCTION readTables(schemaFile)
|
||||
DEFINE schemaFile STRING
|
||||
DEFINE ch base.channel
|
||||
DEFINE prevTabName STRING
|
||||
DEFINE sch schema_rec
|
||||
CALL col_arr.clear()
|
||||
CALL tab_arr.clear()
|
||||
LET ch = base.channel.create()
|
||||
WHENEVER ERROR CONTINUE
|
||||
CALL ch.openFile(schemaFile, "r")
|
||||
WHENEVER ERROR STOP
|
||||
IF STATUS!=0 THEN
|
||||
CALL myerror( "error: could not open schema file '"||schemaFile||"'")
|
||||
END IF
|
||||
CALL ch.setdelimiter("^")
|
||||
LET prevTabName=NULL
|
||||
WHILE ch.read(sch)
|
||||
IF sch.table_name.getIndexOf("sys",1)==1 THEN
|
||||
CONTINUE WHILE
|
||||
END IF
|
||||
LET col_arr[col_arr.getLength()+1].* = sch.*
|
||||
IF prevTabName IS NULL OR sch.table_name <> prevTabName THEN
|
||||
LET tab_arr[tab_arr.getLength()+1]=sch.table_name
|
||||
LET prevTabName=sch.table_name
|
||||
END IF
|
||||
END WHILE
|
||||
CALL ch.close()
|
||||
IF tab_arr.getLength()==0 THEN
|
||||
CALL myerror("did not find any coluns in "||schemaFile)
|
||||
END IF
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION myerror(str)
|
||||
DEFINE str STRING
|
||||
DISPLAY "ERROR:",str
|
||||
EXIT PROGRAM 1
|
||||
END FUNCTION
|
||||
@@ -0,0 +1,60 @@
|
||||
# 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(STYLE="dialog3",TEXT="Form assistant")
|
||||
GRID
|
||||
{
|
||||
[img ]
|
||||
[ ] [quest : ]
|
||||
[ ]
|
||||
[ ]
|
||||
Tables:
|
||||
[currtable ]
|
||||
<T t > <T z >
|
||||
Available fields Chosen fields
|
||||
[x|afields ] [r ][y|cfields ]
|
||||
[x|afields ] [rr ][y|cfields ]
|
||||
[x|afields ] [y|cfields ]
|
||||
[x|afields ] [l ][y|cfields ]
|
||||
[x|afields ] [ll ][y|cfields ]
|
||||
|
||||
[""]
|
||||
---------------------------------------------------------------
|
||||
[ :cancel:" ":back : next :" ":finish ]
|
||||
}
|
||||
END
|
||||
END
|
||||
|
||||
ATTRIBUTES
|
||||
|
||||
IMAGE img:IMAGE="formwizard.png";
|
||||
LABEL quest:TEXT="Which fields your form should contain ?";
|
||||
COMBOBOX currtable=FORMONLY.currtable,COMMENT="You can choose from more than one table.",NOT NULL;
|
||||
TABLE t:DOUBLECLICK=right;
|
||||
TABLE z:DOUBLECLICK=left;
|
||||
x=FORMONLY.atable,SCROLL,HIDDEN;
|
||||
afields=FORMONLY.acol,SCROLL;
|
||||
y=FORMONLY.ctable,SCROLL,HIDDEN;
|
||||
cfields=FORMONLY.ccol;
|
||||
|
||||
BUTTON r:right,TEXT=">";
|
||||
BUTTON rr:allright,TEXT=">>";
|
||||
BUTTON l:left,TEXT="<";
|
||||
BUTTON ll:allleft,TEXT="<<";
|
||||
BUTTON cancel:cancel,TEXT="Cancel";
|
||||
BUTTON back:prevwiz,TEXT="< &Back";
|
||||
BUTTON next:nextwiz,TEXT="&Next >";
|
||||
BUTTON finish:accept,TEXT="&Finish";
|
||||
|
||||
END
|
||||
|
||||
INSTRUCTIONS
|
||||
SCREEN RECORD a(atable,acol);
|
||||
SCREEN RECORD c(ctable,ccol);
|
||||
END
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,247 @@
|
||||
# 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.
|
||||
|
||||
TYPE schema_rec RECORD
|
||||
table_name STRING, -- customer
|
||||
field_name STRING, -- customer_name
|
||||
field_type SMALLINT, -- 0
|
||||
field_strtype STRING, -- CHAR(30)
|
||||
field_length SMALLINT -- 80
|
||||
END RECORD
|
||||
DEFINE col_arr DYNAMIC ARRAY OF schema_rec
|
||||
DEFINE tab_arr DYNAMIC ARRAY OF STRING
|
||||
DEFINE currtable STRING --current table in combobox
|
||||
--a(vailable) fields
|
||||
DEFINE afields DYNAMIC ARRAY OF RECORD
|
||||
atable STRING,
|
||||
acol STRING
|
||||
END RECORD
|
||||
--c(hosen) fields
|
||||
DEFINE cfields DYNAMIC ARRAY OF RECORD
|
||||
ctable STRING,
|
||||
ccol STRING
|
||||
END RECORD
|
||||
|
||||
MAIN
|
||||
CLOSE WINDOW screen
|
||||
CALL wizard()
|
||||
END MAIN
|
||||
|
||||
--contains the wizard dialog build with traditional 4GL dialogs called in a loop
|
||||
--the common actions must be cloned into each dialog
|
||||
FUNCTION wizard()
|
||||
DEFINE i INT
|
||||
DEFINE state STRING
|
||||
CALL readTables("stores.sch")
|
||||
OPEN WINDOW formwizard WITH FORM "formwizard_old"
|
||||
CALL init_tables()
|
||||
OPTIONS FIELD ORDER FORM
|
||||
LET state="combo"
|
||||
WHILE state<>"exit" AND state<>"accept"
|
||||
CASE state
|
||||
WHEN "combo"
|
||||
LET state="left"
|
||||
INPUT BY NAME currtable WITHOUT DEFAULTS ATTRIBUTES(UNBUFFERED)
|
||||
ON CHANGE currtable CALL on_change_currtable()
|
||||
ON ACTION gocombo LET state="combo" EXIT INPUT
|
||||
ON ACTION goleft LET state="left" EXIT INPUT
|
||||
ON ACTION goright LET state="right" EXIT INPUT
|
||||
ON ACTION cancel LET state="exit" EXIT INPUT
|
||||
ON ACTION accept LET state="accept" EXIT INPUT
|
||||
END INPUT
|
||||
WHEN "left"
|
||||
LET state="right"
|
||||
DISPLAY ARRAY afields TO a.* ATTRIBUTES(UNBUFFERED)
|
||||
ON ACTION right CALL right(DIALOG)
|
||||
ON KEY(RIGHT) CALL right(DIALOG)
|
||||
ON ACTION allright CALL allright(DIALOG)
|
||||
ON KEY(TAB) EXIT DISPLAY
|
||||
ON ACTION gocombo LET state="combo" EXIT DISPLAY
|
||||
ON ACTION goleft LET state="left" EXIT DISPLAY
|
||||
ON ACTION goright LET state="right" EXIT DISPLAY
|
||||
ON ACTION cancel LET state="exit" EXIT DISPLAY
|
||||
ON ACTION accept LET state="accept" EXIT DISPLAY
|
||||
END DISPLAY
|
||||
WHEN "right"
|
||||
LET state="combo"
|
||||
DISPLAY ARRAY cfields TO c.* ATTRIBUTES(UNBUFFERED)
|
||||
ON ACTION left CALL left(DIALOG)
|
||||
ON KEY(LEFT) CALL left(DIALOG)
|
||||
ON ACTION allleft CALL allleft(DIALOG)
|
||||
ON KEY(TAB) EXIT DISPLAY
|
||||
ON ACTION gocombo LET state="combo" EXIT DISPLAY
|
||||
ON ACTION goleft LET state="left" EXIT DISPLAY
|
||||
ON ACTION goright LET state="right" EXIT DISPLAY
|
||||
ON ACTION cancel LET state="exit" EXIT DISPLAY
|
||||
ON ACTION accept LET state="accept" EXIT DISPLAY
|
||||
ON ACTION prevwiz LET state="exit" EXIT DISPLAY
|
||||
ON ACTION nextwiz LET state="exit" EXIT DISPLAY
|
||||
END DISPLAY
|
||||
END CASE
|
||||
END WHILE
|
||||
--we just show all chosen columns in the terminal
|
||||
IF state=="accept" THEN
|
||||
FOR i=1 TO cfields.getLength()
|
||||
DISPLAY sfmt("table:%1,column:%2",cfields[i].ctable,cfields[i].ccol)
|
||||
END FOR
|
||||
END IF
|
||||
CLOSE WINDOW formwizard
|
||||
END FUNCTION
|
||||
|
||||
--fill the combobox with all table names
|
||||
FUNCTION init_tables()
|
||||
DEFINE cb ui.ComboBox
|
||||
DEFINE i INTEGER
|
||||
LET cb = ui.ComboBox.forName("formonly.currtable")
|
||||
IF cb IS NULL THEN
|
||||
CALL myerror("combobox not found")
|
||||
END IF
|
||||
FOR i=1 TO tab_arr.getLength()
|
||||
CALL cb.addItem(tab_arr[i],tab_arr[i])
|
||||
END FOR
|
||||
LET currtable=tab_arr[1]
|
||||
CALL on_change_currtable()
|
||||
END FUNCTION
|
||||
|
||||
--combo value has changed,refill the left array and omit
|
||||
--values of the right hand side
|
||||
FUNCTION on_change_currtable()
|
||||
DEFINE tab,col STRING
|
||||
DEFINE col_len,used_col_len,idx,i,j,foundUsed,tabSize INT
|
||||
CALL afields.clear()
|
||||
LET idx=0
|
||||
LET col_len=col_arr.getLength()
|
||||
FOR i=1 TO col_len
|
||||
LET tab=col_arr[i].table_name
|
||||
LET col=col_arr[i].field_name
|
||||
LET used_col_len=cfields.getLength()
|
||||
LET foundUsed=FALSE
|
||||
FOR j=1 TO used_col_len
|
||||
IF cfields[j].ctable=tab AND cfields[j].ccol=col THEN
|
||||
LET foundUsed=TRUE
|
||||
EXIT FOR
|
||||
END IF
|
||||
END FOR
|
||||
IF NOT foundUsed AND tab=currtable THEN
|
||||
LET idx=idx+1
|
||||
LET afields[idx].atable=tab
|
||||
LET afields[idx].acol=col
|
||||
END IF
|
||||
END FOR
|
||||
LET tabSize=tableSize("a")
|
||||
FOR i=1 TO tabSize
|
||||
DISPLAY "" TO a[i].acol
|
||||
END FOR
|
||||
FOR i=1 TO afields.getLength()
|
||||
DISPLAY afields[i].* TO a[i].*
|
||||
END FOR
|
||||
END FUNCTION
|
||||
|
||||
--move the current item to the right hand side and delete it on the left hand side
|
||||
FUNCTION right(d)
|
||||
DEFINE d ui.Dialog
|
||||
DEFINE idx,lastC,i INT
|
||||
LET idx = arr_curr()
|
||||
LET lastC=cfields.getLength()+1
|
||||
LET cfields[lastC].ctable=afields[idx].atable
|
||||
LET cfields[lastC].ccol=afields[idx].acol
|
||||
CALL afields.deleteElement(idx)
|
||||
FOR i=1 TO cfields.getLength()
|
||||
DISPLAY cfields[i].* TO c[i].*
|
||||
END FOR
|
||||
END FUNCTION
|
||||
|
||||
--move all items to the right hand side
|
||||
FUNCTION allright(d)
|
||||
DEFINE d ui.Dialog
|
||||
DEFINE lastC,i INT
|
||||
LET lastC=cfields.getLength()
|
||||
FOR i=1 TO afields.getLength()
|
||||
LET lastC=lastC+1
|
||||
LET cfields[lastC].ctable=afields[i].atable
|
||||
LET cfields[lastC].ccol=afields[i].acol
|
||||
END FOR
|
||||
CALL afields.clear()
|
||||
FOR i=1 TO cfields.getLength()
|
||||
DISPLAY cfields[i].* TO c[i].*
|
||||
END FOR
|
||||
END FUNCTION
|
||||
|
||||
--remove the current item from the right hand side and refill the left hand side
|
||||
FUNCTION left(d)
|
||||
DEFINE d ui.Dialog
|
||||
CALL cfields.deleteElement(arr_curr())
|
||||
CALL on_change_currtable()
|
||||
END FUNCTION
|
||||
|
||||
--remove all items from the right hand side and refill the left hand side
|
||||
FUNCTION allleft(d)
|
||||
DEFINE d ui.Dialog
|
||||
CALL cfields.clear()
|
||||
CALL on_change_currtable()
|
||||
END FUNCTION
|
||||
|
||||
--read in a schema file
|
||||
FUNCTION readTables(schemaFile)
|
||||
DEFINE schemaFile STRING
|
||||
DEFINE ch base.channel
|
||||
DEFINE prevTabName STRING
|
||||
DEFINE sch schema_rec
|
||||
CALL col_arr.clear()
|
||||
CALL tab_arr.clear()
|
||||
LET ch = base.channel.create()
|
||||
WHENEVER ERROR CONTINUE
|
||||
CALL ch.openFile(schemaFile, "r")
|
||||
WHENEVER ERROR STOP
|
||||
IF STATUS!=0 THEN
|
||||
CALL myerror( "error: could not open schema file '"||schemaFile||"'")
|
||||
END IF
|
||||
CALL ch.setdelimiter("^")
|
||||
LET prevTabName=NULL
|
||||
WHILE ch.read(sch)
|
||||
IF sch.table_name.getIndexOf("sys",1)==1 THEN
|
||||
CONTINUE WHILE
|
||||
END IF
|
||||
LET col_arr[col_arr.getLength()+1].* = sch.*
|
||||
IF prevTabName IS NULL OR sch.table_name <> prevTabName THEN
|
||||
LET tab_arr[tab_arr.getLength()+1]=sch.table_name
|
||||
LET prevTabName=sch.table_name
|
||||
END IF
|
||||
END WHILE
|
||||
CALL ch.close()
|
||||
IF tab_arr.getLength()==0 THEN
|
||||
CALL myerror("did not find any coluns in "||schemaFile)
|
||||
END IF
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION myerror(str)
|
||||
DEFINE str STRING
|
||||
DISPLAY "ERROR:",str
|
||||
EXIT PROGRAM 1
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION tableSize(screenRec)
|
||||
DEFINE screenRec STRING
|
||||
DEFINE nl om.NodeList
|
||||
DEFINE root,tab om.DomNode
|
||||
DEFINE doc om.DomDocument
|
||||
DEFINE currWinId,bufferSize INT
|
||||
LET root=ui.Interface.getRootNode()
|
||||
LET currWinId=root.getAttribute("currentWindow")
|
||||
LET doc =ui.Interface.getDocument()
|
||||
LET root=doc.getElementById(currWinId)
|
||||
LET nl=root.selectByPath(sfmt("//Table[@tabName=\"%1\"]",screenRec))
|
||||
IF nl.getLength()>0 THEN
|
||||
LET tab=nl.item(1)
|
||||
LET bufferSize=tab.getAttribute("bufferSize")
|
||||
ELSE
|
||||
DISPLAY "can't find Table"
|
||||
END IF
|
||||
RETURN bufferSize
|
||||
END FUNCTION
|
||||
@@ -0,0 +1,54 @@
|
||||
# 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(STYLE="dialog3",TEXT="Form assistant")
|
||||
GRID
|
||||
{
|
||||
[img ]
|
||||
[ ] [quest : ]
|
||||
[ ]
|
||||
[ ]
|
||||
[gocombo: ]
|
||||
[currtable ]
|
||||
<T t > <T z >
|
||||
Available fields Chosen fields
|
||||
[x|afields ] [r ][y|cfields ]
|
||||
[x|afields ] [rr ][y|cfields ]
|
||||
[x|afields ] [y|cfields ]
|
||||
[x|afields ] [l ][y|cfields ]
|
||||
[x|afields ] [ll ][y|cfields ]
|
||||
|
||||
[goleft ] [goright ]
|
||||
[""]
|
||||
---------------------------------------------------------------
|
||||
[ :cancel:" ":back : next :" ":finish ]
|
||||
}
|
||||
END
|
||||
ATTRIBUTES
|
||||
IMAGE img:IMAGE="formwizard.png";
|
||||
LABEL quest:TEXT="Which fields your form should contain ?";
|
||||
COMBOBOX currtable=FORMONLY.currtable,COMMENT="You can choose from more than one table.",NOT NULL;
|
||||
x=FORMONLY.atable,SCROLL,HIDDEN;
|
||||
afields=FORMONLY.acol,SCROLL;
|
||||
y=FORMONLY.ctable,SCROLL,HIDDEN;
|
||||
cfields=FORMONLY.ccol;
|
||||
BUTTON gocombo:gocombo,TEXT="Tables:";
|
||||
BUTTON goleft:goleft,TEXT="Edit Left";
|
||||
BUTTON goright:goright,TEXT="Edit Right";
|
||||
BUTTON r:right,TEXT=">";
|
||||
BUTTON rr:allright,TEXT=">>";
|
||||
BUTTON l:left,TEXT="<";
|
||||
BUTTON ll:allleft,TEXT="<<";
|
||||
BUTTON cancel:cancel,TEXT="Cancel";
|
||||
BUTTON back:prevwiz,TEXT="< &Back";
|
||||
BUTTON next:nextwiz,TEXT="&Next >";
|
||||
BUTTON finish:accept,TEXT="&Finish";
|
||||
INSTRUCTIONS
|
||||
SCREEN RECORD a(atable,acol);
|
||||
SCREEN RECORD c(ctable,ccol);
|
||||
@@ -0,0 +1,410 @@
|
||||
customer^customer_num^262^4^1^
|
||||
customer^fname^0^15^2^
|
||||
customer^lname^0^15^3^
|
||||
customer^company^0^20^4^
|
||||
customer^address1^0^20^5^
|
||||
customer^address2^0^20^6^
|
||||
customer^city^0^15^7^
|
||||
customer^state^0^2^8^
|
||||
customer^zipcode^0^5^9^
|
||||
customer^phone^0^18^10^
|
||||
items^item_num^1^2^1^
|
||||
items^order_num^2^4^2^
|
||||
items^stock_num^1^2^3^
|
||||
items^manu_code^0^3^4^
|
||||
items^quantity^1^2^5^
|
||||
items^total_price^8^2050^6^
|
||||
manufact^manu_code^0^3^1^
|
||||
manufact^manu_name^0^15^2^
|
||||
orders^order_num^262^4^1^
|
||||
orders^order_date^7^4^2^
|
||||
orders^customer_num^2^4^3^
|
||||
orders^ship_instruct^0^40^4^
|
||||
orders^backlog^0^1^5^
|
||||
orders^po_num^0^10^6^
|
||||
orders^ship_date^7^4^7^
|
||||
orders^ship_weight^5^2050^8^
|
||||
orders^ship_charge^8^1538^9^
|
||||
orders^paid_date^7^4^10^
|
||||
state^code^0^2^1^
|
||||
state^sname^0^15^2^
|
||||
stock^stock_num^1^2^1^
|
||||
stock^manu_code^0^3^2^
|
||||
stock^description^0^15^3^
|
||||
stock^unit_price^8^1538^4^
|
||||
stock^unit^0^4^5^
|
||||
stock^unit_descr^0^15^6^
|
||||
sysaggregates^name^13^128^1^
|
||||
sysaggregates^owner^0^32^2^
|
||||
sysaggregates^aggid^6^4^3^
|
||||
sysaggregates^init_func^13^128^4^
|
||||
sysaggregates^iter_func^13^128^5^
|
||||
sysaggregates^combine_func^13^128^6^
|
||||
sysaggregates^final_func^13^128^7^
|
||||
sysams^am_name^13^128^1^
|
||||
sysams^am_owner^0^32^2^
|
||||
sysams^am_id^2^4^3^
|
||||
sysams^am_type^0^1^4^
|
||||
sysams^am_sptype^0^3^5^
|
||||
sysams^am_defopclass^2^4^6^
|
||||
sysams^am_keyscan^2^4^7^
|
||||
sysams^am_unique^2^4^8^
|
||||
sysams^am_cluster^2^4^9^
|
||||
sysams^am_rowids^2^4^10^
|
||||
sysams^am_readwrite^2^4^11^
|
||||
sysams^am_parallel^2^4^12^
|
||||
sysams^am_costfactor^4^4^13^
|
||||
sysams^am_create^2^4^14^
|
||||
sysams^am_drop^2^4^15^
|
||||
sysams^am_open^2^4^16^
|
||||
sysams^am_close^2^4^17^
|
||||
sysams^am_insert^2^4^18^
|
||||
sysams^am_delete^2^4^19^
|
||||
sysams^am_update^2^4^20^
|
||||
sysams^am_stats^2^4^21^
|
||||
sysams^am_scancost^2^4^22^
|
||||
sysams^am_check^2^4^23^
|
||||
sysams^am_beginscan^2^4^24^
|
||||
sysams^am_endscan^2^4^25^
|
||||
sysams^am_rescan^2^4^26^
|
||||
sysams^am_getnext^2^4^27^
|
||||
sysams^am_getbyid^2^4^28^
|
||||
sysams^am_build^2^4^29^
|
||||
sysams^am_init^2^4^30^
|
||||
sysattrtypes^extended_id^2^4^1^
|
||||
sysattrtypes^seqno^1^2^2^
|
||||
sysattrtypes^levelno^1^2^3^
|
||||
sysattrtypes^parent_no^1^2^4^
|
||||
sysattrtypes^fieldname^13^128^5^
|
||||
sysattrtypes^fieldno^1^2^6^
|
||||
sysattrtypes^type^1^2^7^
|
||||
sysattrtypes^length^1^2^8^
|
||||
sysattrtypes^xtd_type_id^2^4^9^
|
||||
sysblobs^spacename^13^128^1^
|
||||
sysblobs^type^0^1^2^
|
||||
sysblobs^tabid^2^4^3^
|
||||
sysblobs^colno^1^2^4^
|
||||
syscasts^owner^0^32^1^
|
||||
syscasts^argument_type^1^2^2^
|
||||
syscasts^argument_xid^2^4^3^
|
||||
syscasts^result_type^1^2^4^
|
||||
syscasts^result_xid^2^4^5^
|
||||
syscasts^routine_name^13^128^6^
|
||||
syscasts^routine_owner^0^32^7^
|
||||
syscasts^class^0^1^8^
|
||||
syschecks^constrid^2^4^1^
|
||||
syschecks^type^0^1^2^
|
||||
syschecks^seqno^1^2^3^
|
||||
syschecks^checktext^0^32^4^
|
||||
syscolatt^tabname^0^18^1^
|
||||
syscolatt^colname^0^18^2^
|
||||
syscolatt^seqno^262^4^3^
|
||||
syscolatt^color^1^2^4^
|
||||
syscolatt^inverse^0^1^5^
|
||||
syscolatt^underline^0^1^6^
|
||||
syscolatt^blink^0^1^7^
|
||||
syscolatt^left^0^1^8^
|
||||
syscolatt^def_format^0^64^9^
|
||||
syscolatt^condition^0^64^10^
|
||||
syscolattribs^tabid^2^4^1^
|
||||
syscolattribs^colno^1^2^2^
|
||||
syscolattribs^extentsize^2^4^3^
|
||||
syscolattribs^flags^2^4^4^
|
||||
syscolattribs^flags1^2^4^5^
|
||||
syscolattribs^sbspace^13^128^6^
|
||||
syscolauth^grantor^0^32^1^
|
||||
syscolauth^grantee^0^32^2^
|
||||
syscolauth^tabid^2^4^3^
|
||||
syscolauth^colno^1^2^4^
|
||||
syscolauth^colauth^0^3^5^
|
||||
syscoldepend^constrid^2^4^1^
|
||||
syscoldepend^tabid^2^4^2^
|
||||
syscoldepend^colno^1^2^3^
|
||||
syscolumns^colname^13^128^1^
|
||||
syscolumns^tabid^2^4^2^
|
||||
syscolumns^colno^1^2^3^
|
||||
syscolumns^coltype^1^2^4^
|
||||
syscolumns^collength^1^2^5^
|
||||
syscolumns^colmin^2^4^6^
|
||||
syscolumns^colmax^2^4^7^
|
||||
syscolumns^extended_id^2^4^8^
|
||||
sysconstraints^constrid^6^4^1^
|
||||
sysconstraints^constrname^13^128^2^
|
||||
sysconstraints^owner^0^32^3^
|
||||
sysconstraints^tabid^2^4^4^
|
||||
sysconstraints^constrtype^0^1^5^
|
||||
sysconstraints^idxname^13^128^6^
|
||||
sysconstraints^collation^0^36^7^
|
||||
sysdefaults^tabid^2^4^1^
|
||||
sysdefaults^colno^1^2^2^
|
||||
sysdefaults^type^0^1^3^
|
||||
sysdefaults^default^0^256^4^
|
||||
sysdefaults^class^0^1^5^
|
||||
sysdepend^btabid^2^4^1^
|
||||
sysdepend^btype^0^1^2^
|
||||
sysdepend^dtabid^2^4^3^
|
||||
sysdepend^dtype^0^1^4^
|
||||
sysdistrib^tabid^2^4^1^
|
||||
sysdistrib^colno^1^2^2^
|
||||
sysdistrib^seqno^2^4^3^
|
||||
sysdistrib^constructed^7^4^4^
|
||||
sysdistrib^mode^0^1^5^
|
||||
sysdistrib^resolution^4^4^6^
|
||||
sysdistrib^confidence^4^4^7^
|
||||
sysdomains^id^262^4^1^
|
||||
sysdomains^owner^0^32^2^
|
||||
sysdomains^name^13^128^3^
|
||||
sysdomains^type^1^2^4^
|
||||
syserrors^sqlstate^0^5^1^
|
||||
syserrors^locale^0^36^2^
|
||||
syserrors^level^1^2^3^
|
||||
syserrors^seqno^1^2^4^
|
||||
syserrors^message^13^255^5^
|
||||
sysfragauth^grantor^0^32^1^
|
||||
sysfragauth^grantee^0^32^2^
|
||||
sysfragauth^tabid^2^4^3^
|
||||
sysfragauth^fragment^13^128^4^
|
||||
sysfragauth^fragauth^0^6^5^
|
||||
sysfragments^fragtype^0^1^1^
|
||||
sysfragments^tabid^2^4^2^
|
||||
sysfragments^indexname^13^128^3^
|
||||
sysfragments^colno^2^4^4^
|
||||
sysfragments^partn^2^4^5^
|
||||
sysfragments^strategy^0^1^6^
|
||||
sysfragments^location^0^1^7^
|
||||
sysfragments^servername^13^128^8^
|
||||
sysfragments^evalpos^2^4^9^
|
||||
sysfragments^exprtext^12^56^10^
|
||||
sysfragments^exprbin^11^56^11^
|
||||
sysfragments^exprarr^11^56^12^
|
||||
sysfragments^flags^2^4^13^
|
||||
sysfragments^dbspace^13^128^14^
|
||||
sysfragments^levels^1^2^15^
|
||||
sysfragments^npused^2^4^16^
|
||||
sysfragments^nrows^2^4^17^
|
||||
sysfragments^clust^2^4^18^
|
||||
sysindexes^idxname^13^128^1^
|
||||
sysindexes^owner^0^32^2^
|
||||
sysindexes^tabid^2^4^3^
|
||||
sysindexes^idxtype^0^1^4^
|
||||
sysindexes^clustered^0^1^5^
|
||||
sysindexes^part1^1^2^6^
|
||||
sysindexes^part2^1^2^7^
|
||||
sysindexes^part3^1^2^8^
|
||||
sysindexes^part4^1^2^9^
|
||||
sysindexes^part5^1^2^10^
|
||||
sysindexes^part6^1^2^11^
|
||||
sysindexes^part7^1^2^12^
|
||||
sysindexes^part8^1^2^13^
|
||||
sysindexes^part9^1^2^14^
|
||||
sysindexes^part10^1^2^15^
|
||||
sysindexes^part11^1^2^16^
|
||||
sysindexes^part12^1^2^17^
|
||||
sysindexes^part13^1^2^18^
|
||||
sysindexes^part14^1^2^19^
|
||||
sysindexes^part15^1^2^20^
|
||||
sysindexes^part16^1^2^21^
|
||||
sysindexes^levels^1^2^22^
|
||||
sysindexes^leaves^2^4^23^
|
||||
sysindexes^nunique^2^4^24^
|
||||
sysindexes^clust^2^4^25^
|
||||
sysindices^idxname^13^128^1^
|
||||
sysindices^owner^0^32^2^
|
||||
sysindices^tabid^2^4^3^
|
||||
sysindices^idxtype^0^1^4^
|
||||
sysindices^clustered^0^1^5^
|
||||
sysindices^levels^1^2^6^
|
||||
sysindices^leaves^2^4^7^
|
||||
sysindices^nunique^2^4^8^
|
||||
sysindices^clust^2^4^9^
|
||||
sysindices^nrows^3^8^10^
|
||||
sysinherits^child^2^4^1^
|
||||
sysinherits^parent^2^4^2^
|
||||
sysinherits^class^0^1^3^
|
||||
syslangauth^grantor^0^32^1^
|
||||
syslangauth^grantee^0^32^2^
|
||||
syslangauth^langid^2^4^3^
|
||||
syslangauth^langauth^0^1^4^
|
||||
syslogmap^tabloc^2^4^1^
|
||||
syslogmap^tabid^2^4^2^
|
||||
syslogmap^fragid^2^4^3^
|
||||
syslogmap^flags^2^4^4^
|
||||
sysobjstate^objtype^0^1^1^
|
||||
sysobjstate^owner^0^32^2^
|
||||
sysobjstate^name^13^128^3^
|
||||
sysobjstate^tabid^2^4^4^
|
||||
sysobjstate^state^0^1^5^
|
||||
sysopclasses^opclassname^13^128^1^
|
||||
sysopclasses^owner^0^32^2^
|
||||
sysopclasses^amid^2^4^3^
|
||||
sysopclasses^opclassid^6^4^4^
|
||||
sysopclstr^owner^0^32^1^
|
||||
sysopclstr^clstrname^13^128^2^
|
||||
sysopclstr^clstrsize^2^4^3^
|
||||
sysopclstr^tabid^2^4^4^
|
||||
sysopclstr^blobcol1^1^2^5^
|
||||
sysopclstr^blobcol2^1^2^6^
|
||||
sysopclstr^blobcol3^1^2^7^
|
||||
sysopclstr^blobcol4^1^2^8^
|
||||
sysopclstr^blobcol5^1^2^9^
|
||||
sysopclstr^blobcol6^1^2^10^
|
||||
sysopclstr^blobcol7^1^2^11^
|
||||
sysopclstr^blobcol8^1^2^12^
|
||||
sysopclstr^blobcol9^1^2^13^
|
||||
sysopclstr^blobcol10^1^2^14^
|
||||
sysopclstr^blobcol11^1^2^15^
|
||||
sysopclstr^blobcol12^1^2^16^
|
||||
sysopclstr^blobcol13^1^2^17^
|
||||
sysopclstr^blobcol14^1^2^18^
|
||||
sysopclstr^blobcol15^1^2^19^
|
||||
sysopclstr^blobcol16^1^2^20^
|
||||
sysopclstr^clstrkey1^1^2^21^
|
||||
sysopclstr^clstrkey2^1^2^22^
|
||||
sysopclstr^clstrkey3^1^2^23^
|
||||
sysopclstr^clstrkey4^1^2^24^
|
||||
sysopclstr^clstrkey5^1^2^25^
|
||||
sysopclstr^clstrkey6^1^2^26^
|
||||
sysopclstr^clstrkey7^1^2^27^
|
||||
sysopclstr^clstrkey8^1^2^28^
|
||||
sysopclstr^clstrkey9^1^2^29^
|
||||
sysopclstr^clstrkey10^1^2^30^
|
||||
sysopclstr^clstrkey11^1^2^31^
|
||||
sysopclstr^clstrkey12^1^2^32^
|
||||
sysopclstr^clstrkey13^1^2^33^
|
||||
sysopclstr^clstrkey14^1^2^34^
|
||||
sysopclstr^clstrkey15^1^2^35^
|
||||
sysopclstr^clstrkey16^1^2^36^
|
||||
sysprocauth^grantor^0^32^1^
|
||||
sysprocauth^grantee^0^32^2^
|
||||
sysprocauth^procid^2^4^3^
|
||||
sysprocauth^procauth^0^1^4^
|
||||
sysprocbody^procid^2^4^1^
|
||||
sysprocbody^datakey^0^1^2^
|
||||
sysprocbody^seqno^2^4^3^
|
||||
sysprocbody^data^0^256^4^
|
||||
sysprocedures^procname^13^128^1^
|
||||
sysprocedures^owner^0^32^2^
|
||||
sysprocedures^procid^6^4^3^
|
||||
sysprocedures^mode^0^1^4^
|
||||
sysprocedures^retsize^2^4^5^
|
||||
sysprocedures^symsize^2^4^6^
|
||||
sysprocedures^datasize^2^4^7^
|
||||
sysprocedures^codesize^2^4^8^
|
||||
sysprocedures^numargs^2^4^9^
|
||||
sysprocedures^isproc^0^1^10^
|
||||
sysprocedures^specificname^13^128^11^
|
||||
sysprocedures^externalname^13^255^12^
|
||||
sysprocedures^paramstyle^0^1^13^
|
||||
sysprocedures^langid^2^4^14^
|
||||
sysprocplan^procid^2^4^1^
|
||||
sysprocplan^planid^2^4^2^
|
||||
sysprocplan^datakey^0^1^3^
|
||||
sysprocplan^seqno^2^4^4^
|
||||
sysprocplan^created^7^4^5^
|
||||
sysprocplan^datasize^2^4^6^
|
||||
sysprocplan^data^0^256^7^
|
||||
sysreferences^constrid^2^4^1^
|
||||
sysreferences^primary^2^4^2^
|
||||
sysreferences^ptabid^2^4^3^
|
||||
sysreferences^updrule^0^1^4^
|
||||
sysreferences^delrule^0^1^5^
|
||||
sysreferences^matchtype^0^1^6^
|
||||
sysreferences^pendant^0^1^7^
|
||||
sysroleauth^rolename^0^32^1^
|
||||
sysroleauth^grantee^0^32^2^
|
||||
sysroleauth^is_grantable^0^1^3^
|
||||
sysroutinelangs^langid^6^4^1^
|
||||
sysroutinelangs^langname^0^30^2^
|
||||
sysroutinelangs^langinitfunc^13^128^3^
|
||||
sysroutinelangs^langpath^0^255^4^
|
||||
sysroutinelangs^langclass^0^18^5^
|
||||
syssequences^seqid^6^4^1^
|
||||
syssequences^tabid^2^4^2^
|
||||
syssynonyms^owner^0^32^1^
|
||||
syssynonyms^synname^13^128^2^
|
||||
syssynonyms^created^7^4^3^
|
||||
syssynonyms^tabid^2^4^4^
|
||||
syssyntable^tabid^2^4^1^
|
||||
syssyntable^servername^13^128^2^
|
||||
syssyntable^dbname^13^128^3^
|
||||
syssyntable^owner^0^32^4^
|
||||
syssyntable^tabname^13^128^5^
|
||||
syssyntable^btabid^2^4^6^
|
||||
systabamdata^tabid^2^4^1^
|
||||
systabamdata^am_param^0^256^2^
|
||||
systabamdata^am_space^13^128^3^
|
||||
systabauth^grantor^0^32^1^
|
||||
systabauth^grantee^0^32^2^
|
||||
systabauth^tabid^2^4^3^
|
||||
systabauth^tabauth^0^9^4^
|
||||
systables^tabname^13^128^1^
|
||||
systables^owner^0^32^2^
|
||||
systables^partnum^2^4^3^
|
||||
systables^tabid^6^4^4^
|
||||
systables^rowsize^1^2^5^
|
||||
systables^ncols^1^2^6^
|
||||
systables^nindexes^1^2^7^
|
||||
systables^nrows^2^4^8^
|
||||
systables^created^7^4^9^
|
||||
systables^version^2^4^10^
|
||||
systables^tabtype^0^1^11^
|
||||
systables^locklevel^0^1^12^
|
||||
systables^npused^2^4^13^
|
||||
systables^fextsize^2^4^14^
|
||||
systables^nextsize^2^4^15^
|
||||
systables^flags^1^2^16^
|
||||
systables^site^13^128^17^
|
||||
systables^dbname^13^128^18^
|
||||
systables^type_xid^2^4^19^
|
||||
systables^am_id^2^4^20^
|
||||
systraceclasses^name^0^18^1^
|
||||
systraceclasses^classid^6^4^2^
|
||||
systracemsgs^name^13^128^1^
|
||||
systracemsgs^msgid^6^4^2^
|
||||
systracemsgs^locale^0^36^3^
|
||||
systracemsgs^seqno^1^2^4^
|
||||
systracemsgs^message^13^255^5^
|
||||
systrigbody^trigid^2^4^1^
|
||||
systrigbody^datakey^0^1^2^
|
||||
systrigbody^seqno^2^4^3^
|
||||
systrigbody^data^0^256^4^
|
||||
systriggers^trigid^6^4^1^
|
||||
systriggers^trigname^13^128^2^
|
||||
systriggers^owner^0^32^3^
|
||||
systriggers^tabid^2^4^4^
|
||||
systriggers^event^0^1^5^
|
||||
systriggers^old^13^128^6^
|
||||
systriggers^new^13^128^7^
|
||||
systriggers^mode^0^1^8^
|
||||
systriggers^collation^0^36^9^
|
||||
sysusers^username^0^32^1^
|
||||
sysusers^usertype^0^1^2^
|
||||
sysusers^priority^1^2^3^
|
||||
sysusers^password^0^16^4^
|
||||
sysviews^tabid^2^4^1^
|
||||
sysviews^seqno^1^2^2^
|
||||
sysviews^viewtext^0^64^3^
|
||||
sysviolations^targettid^2^4^1^
|
||||
sysviolations^viotid^2^4^2^
|
||||
sysviolations^diatid^2^4^3^
|
||||
sysviolations^maxrows^2^4^4^
|
||||
sysxtddesc^extended_id^2^4^1^
|
||||
sysxtddesc^seqno^1^2^2^
|
||||
sysxtddesc^description^0^256^3^
|
||||
sysxtdtypeauth^grantor^0^32^1^
|
||||
sysxtdtypeauth^grantee^0^32^2^
|
||||
sysxtdtypeauth^type^2^4^3^
|
||||
sysxtdtypeauth^auth^0^2^4^
|
||||
sysxtdtypes^extended_id^6^4^1^
|
||||
sysxtdtypes^domain^0^1^2^
|
||||
sysxtdtypes^mode^0^1^3^
|
||||
sysxtdtypes^owner^0^32^4^
|
||||
sysxtdtypes^name^13^128^5^
|
||||
sysxtdtypes^type^1^2^6^
|
||||
sysxtdtypes^source^2^4^7^
|
||||
sysxtdtypes^maxlen^2^4^8^
|
||||
sysxtdtypes^length^2^4^9^
|
||||
sysxtdtypes^byvalue^0^1^10^
|
||||
sysxtdtypes^cannothash^0^1^11^
|
||||
sysxtdtypes^align^1^2^12^
|
||||
sysxtdtypes^locator^2^4^13^
|
||||
@@ -0,0 +1,330 @@
|
||||
# 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.
|
||||
|
||||
TYPE t_custrec RECORD
|
||||
cust_id INTEGER,
|
||||
cust_name VARCHAR(50),
|
||||
cust_ts DATETIME YEAR TO SECOND,
|
||||
cust_city VARCHAR(30),
|
||||
cust_state CHAR(2),
|
||||
cust_zipcode SMALLINT,
|
||||
cust_address VARCHAR(100)
|
||||
END RECORD
|
||||
|
||||
DEFINE custrec, old_custrec t_custrec
|
||||
|
||||
DEFINE custarr DYNAMIC ARRAY OF t_custrec
|
||||
|
||||
CONSTANT STATE_NAVIGATE = 0
|
||||
CONSTANT STATE_MODIFY = 1
|
||||
CONSTANT STATE_APPEND = 2
|
||||
|
||||
DEFINE cust_state SMALLINT
|
||||
DEFINE last_row INTEGER
|
||||
|
||||
CONSTANT ctitle = "Customers"
|
||||
|
||||
MAIN
|
||||
DEFINE r INTEGER
|
||||
|
||||
OPTIONS INPUT WRAP
|
||||
|
||||
OPEN FORM f1 FROM "CustListDetail"
|
||||
DISPLAY FORM f1
|
||||
|
||||
CALL cust_loadlist(10)
|
||||
DIALOG ATTRIBUTES(FIELD ORDER FORM, UNBUFFERED)
|
||||
|
||||
DISPLAY ARRAY custarr TO sr.*
|
||||
BEFORE ROW
|
||||
LET r = DIALOG.getCurrentRow("sr")
|
||||
IF last_row != r THEN
|
||||
IF NOT cust_check_changing(DIALOG) THEN
|
||||
CALL DIALOG.setCurrentRow("sr",last_row)
|
||||
CONTINUE DIALOG
|
||||
END IF
|
||||
CALL cust_getdetail(DIALOG, r)
|
||||
END IF
|
||||
AFTER ROW
|
||||
LET last_row = DIALOG.getCurrentRow("sr")
|
||||
END DISPLAY
|
||||
|
||||
INPUT custrec.* FROM cr.*
|
||||
AFTER FIELD cust_address
|
||||
IF NOT cust_validate_address(DIALOG) THEN NEXT FIELD CURRENT END IF
|
||||
END INPUT
|
||||
|
||||
BEFORE DIALOG
|
||||
LET r = DIALOG.getCurrentRow("sr")
|
||||
LET last_row = r
|
||||
CALL cust_getdetail(DIALOG, r)
|
||||
|
||||
ON ACTION dialogtouched
|
||||
CALL cust_touched(DIALOG)
|
||||
|
||||
ON ACTION search
|
||||
CALL cust_search(DIALOG)
|
||||
|
||||
ON ACTION undo -- VALIDATE=NO!
|
||||
CALL cust_undo(DIALOG)
|
||||
DISPLAY BY NAME custrec.* -- To sync current field value (validate=no)
|
||||
|
||||
ON ACTION save
|
||||
IF NOT cust_save(DIALOG) THEN CONTINUE DIALOG END IF
|
||||
|
||||
ON ACTION append
|
||||
IF NOT cust_validate(DIALOG) THEN CONTINUE DIALOG END IF
|
||||
CALL cust_append(DIALOG)
|
||||
LET last_row = DIALOG.getCurrentRow("sr")
|
||||
NEXT FIELD cust_name
|
||||
|
||||
ON ACTION delete -- VALIDATE=NO!
|
||||
CALL cust_delete(DIALOG)
|
||||
DISPLAY BY NAME custrec.* -- To sync current field value (validate=no)
|
||||
|
||||
ON ACTION close -- VALIDATE=NO!
|
||||
IF cust_check_changing(DIALOG) THEN EXIT DIALOG END IF
|
||||
|
||||
END DIALOG
|
||||
|
||||
END MAIN
|
||||
|
||||
FUNCTION cust_modifying()
|
||||
RETURN cust_state != STATE_NAVIGATE
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION cust_validate_address(d)
|
||||
DEFINE d ui.Dialog
|
||||
IF d.getFieldTouched("cust_address") AND custrec.cust_address NOT MATCHES "*,*" THEN
|
||||
ERROR "Address must contain a comma (',')"
|
||||
RETURN FALSE
|
||||
END IF
|
||||
RETURN TRUE
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION cust_validate(d)
|
||||
DEFINE d ui.Dialog
|
||||
IF d.validate("cr.*") < 0 OR NOT cust_validate_address(d) THEN
|
||||
RETURN FALSE
|
||||
END IF
|
||||
RETURN TRUE
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION cust_touched(d)
|
||||
DEFINE d ui.Dialog
|
||||
DEFINE r INTEGER
|
||||
LET r = d.getCurrentRow("sr")
|
||||
LET cust_state = STATE_MODIFY
|
||||
LET custrec.cust_ts = CURRENT
|
||||
LET custarr[r].cust_ts = custrec.cust_ts
|
||||
CALL cust_setup(d)
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION cust_clean(d, r)
|
||||
DEFINE d ui.Dialog
|
||||
DEFINE r INTEGER
|
||||
LET old_custrec.* = custrec.*
|
||||
IF r>0 THEN
|
||||
LET custarr[r].* = custrec.*
|
||||
END IF
|
||||
LET cust_state = STATE_NAVIGATE
|
||||
CALL cust_setup(d)
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION cust_check_changing(d)
|
||||
DEFINE d ui.Dialog
|
||||
DEFINE r INTEGER
|
||||
IF cust_modifying() THEN
|
||||
CALL cust_setup(d)
|
||||
LET r = mbox_ync(ctitle,"Do you want to save your changes?","question")
|
||||
CASE r
|
||||
WHEN 1 IF NOT cust_save(d) THEN LET r = 3 END IF
|
||||
WHEN 2 CALL cust_undo(d)
|
||||
END CASE
|
||||
RETURN (r == 1 OR r == 2)
|
||||
END IF
|
||||
RETURN TRUE
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION cust_setup(d)
|
||||
DEFINE d ui.Dialog
|
||||
DEFINE modifying, r SMALLINT
|
||||
LET modifying = cust_modifying()
|
||||
CALL d.setActionActive("dialogtouched", NOT modifying)
|
||||
CALL d.setActionActive("search", NOT modifying)
|
||||
CALL d.setActionActive("undo", modifying)
|
||||
CALL d.setActionActive("save", modifying)
|
||||
LET r = (d.getArrayLength("sr")>0)
|
||||
CALL d.setActionActive("delete", r)
|
||||
CALL d.setFieldActive("cr.cust_name", r)
|
||||
CALL d.setFieldActive("cr.cust_address", r)
|
||||
CALL d.setFieldActive("cr.cust_zipcode", r)
|
||||
CALL d.setFieldActive("cr.cust_state", r)
|
||||
CALL d.setFieldActive("cr.cust_city", r)
|
||||
-- FIXME: Missing CALL d.setFieldActive("cr.*", r)
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION cust_append(d)
|
||||
DEFINE d ui.Dialog
|
||||
DEFINE i INTEGER
|
||||
IF NOT cust_check_changing(d) THEN RETURN END IF
|
||||
IF custarr.getLength() = 1 AND custarr[1].cust_id IS NULL THEN
|
||||
LET i = 1
|
||||
ELSE
|
||||
CALL d.appendRow("sr")
|
||||
LET i = d.getArrayLength("sr")
|
||||
END IF
|
||||
INITIALIZE custrec.* TO NULL
|
||||
LET custarr[i].cust_id = ( custrec.cust_id := 100 + i )
|
||||
LET custarr[i].cust_name = ( custrec.cust_name := "Customer #"||custrec.cust_id )
|
||||
LET custarr[i].cust_ts = ( custrec.cust_ts := CURRENT )
|
||||
CALL d.setCurrentRow("sr",i)
|
||||
LET cust_state = STATE_APPEND
|
||||
CALL cust_setup(d)
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION cust_delete(d)
|
||||
DEFINE d ui.Dialog
|
||||
DEFINE l, r INTEGER
|
||||
IF cust_modifying() THEN
|
||||
CALL cust_setup(d)
|
||||
IF NOT mbox_yn(ctitle,"Delete record and throw away your changes?","question") THEN
|
||||
RETURN
|
||||
END IF
|
||||
ELSE
|
||||
IF NOT mbox_yn(ctitle,"Delete current record?","question") THEN
|
||||
RETURN
|
||||
END IF
|
||||
END IF
|
||||
LET l = d.getArrayLength("sr")
|
||||
IF l == 0 THEN RETURN END IF
|
||||
LET r = d.getCurrentRow("sr")
|
||||
CALL d.deleteRow("sr",r)
|
||||
LET r = d.getCurrentRow("sr")
|
||||
CALL cust_getdetail(d, r)
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION cust_undo(d)
|
||||
DEFINE d ui.Dialog
|
||||
DEFINE r INTEGER
|
||||
-- Undo changes...
|
||||
CASE cust_state
|
||||
WHEN STATE_APPEND
|
||||
CALL d.deleteRow("sr",last_row)
|
||||
LET r = d.getCurrentRow("sr")
|
||||
CALL cust_getdetail(d, r)
|
||||
WHEN STATE_MODIFY
|
||||
LET custrec.* = old_custrec.*
|
||||
LET r = last_row
|
||||
OTHERWISE
|
||||
DISPLAY "Invalid state in cust_undo" EXIT PROGRAM 1
|
||||
END CASE
|
||||
CALL d.setFieldTouched("cr.*", FALSE)
|
||||
CALL cust_clean(d, r)
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION cust_save(d)
|
||||
DEFINE d ui.Dialog
|
||||
IF NOT cust_validate(d) THEN RETURN FALSE END IF
|
||||
CASE cust_state
|
||||
WHEN STATE_APPEND
|
||||
DISPLAY "Execute SQL INSERT statement..."
|
||||
IF SQLCA.SQLCODE < 0 THEN RETURN FALSE END IF
|
||||
WHEN STATE_MODIFY
|
||||
DISPLAY "Execute SQL UPDATE statement..."
|
||||
IF SQLCA.SQLCODE < 0 THEN RETURN FALSE END IF
|
||||
OTHERWISE
|
||||
DISPLAY "Invalid state in cust_save" EXIT PROGRAM 1
|
||||
END CASE
|
||||
CALL d.setFieldTouched("cr.*", FALSE)
|
||||
CALL cust_clean(d, last_row)
|
||||
RETURN TRUE
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION cust_search(d)
|
||||
DEFINE d ui.Dialog
|
||||
DEFINE cond STRING
|
||||
IF NOT cust_check_changing(d) THEN RETURN END IF
|
||||
CONSTRUCT BY NAME cond ON cust_id,
|
||||
cust_name,
|
||||
cust_city,
|
||||
cust_state,
|
||||
cust_zipcode,
|
||||
cust_address
|
||||
ATTRIBUTES(ACCEPT=FALSE,CANCEL=FALSE)
|
||||
ON ACTION search_run
|
||||
ACCEPT CONSTRUCT
|
||||
ON ACTION search_cancel
|
||||
EXIT CONSTRUCT
|
||||
END CONSTRUCT
|
||||
IF int_flag == FALSE THEN
|
||||
MESSAGE cond
|
||||
CALL cust_loadlist(length(cond) / 3)
|
||||
CALL d.setCurrentRow("sr",1)
|
||||
CALL cust_getdetail(d, 1)
|
||||
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 custarr.clear()
|
||||
FOR i=1 TO m
|
||||
CALL custarr.appendElement()
|
||||
LET custarr[i].cust_id = i
|
||||
LET custarr[i].cust_name = "Name " || i
|
||||
LET custarr[i].cust_ts = CURRENT
|
||||
IF i MOD 3 == 0 THEN
|
||||
LET custarr[i].cust_city = "San Francisco"
|
||||
LET custarr[i].cust_state = "CA"
|
||||
LET custarr[i].cust_zipcode = 94102
|
||||
LET custarr[i].cust_address = "24, Blue Lagon Street"
|
||||
ELSE
|
||||
LET custarr[i].cust_city = "Richmond"
|
||||
LET custarr[i].cust_state = "VA"
|
||||
LET custarr[i].cust_zipcode = 23219
|
||||
LET custarr[i].cust_address = "45, Yellow Lake"
|
||||
END IF
|
||||
END FOR
|
||||
END FUNCTION
|
||||
|
||||
-- Dummy function to get customer details (normally, SELECT from database)
|
||||
FUNCTION cust_getdetail(d, r)
|
||||
DEFINE d ui.Dialog
|
||||
DEFINE r INTEGER
|
||||
IF r <= 0 THEN
|
||||
INITIALIZE custrec.* TO NULL
|
||||
INITIALIZE old_custrec.* TO NULL
|
||||
ELSE
|
||||
LET custrec.* = custarr[r].*
|
||||
END IF
|
||||
CALL cust_clean(d, r)
|
||||
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,76 @@
|
||||
# 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 search (TEXT="Query",ACCELERATOR=control-q)
|
||||
ACTION search_run (TEXT="Run query",ACCELERATOR=Enter,ACCELERATOR2=Return)
|
||||
ACTION search_cancel (TEXT="Cancel query",ACCELERATOR=Escape,VALIDATE=NO)
|
||||
ACTION undo (TEXT="Undo",ACCELERATOR=control-u,VALIDATE=NO)
|
||||
ACTION save (TEXT="Save",ACCELERATOR=control-s)
|
||||
ACTION close (DEFAULTVIEW=YES,VALIDATE=NO)
|
||||
ACTION delete (VALIDATE=NO)
|
||||
ACTION modify (TEXT="Modify",DEFAULTVIEW=YES)
|
||||
END
|
||||
|
||||
LAYOUT ( TEXT = "List and Detail" )
|
||||
GRID
|
||||
{
|
||||
<g g1 >
|
||||
<t t1 >
|
||||
Id Name Timestamp City State Zip code Address
|
||||
[c1 |c2 |c3 |c4 |c5 |c6 |c7 ]
|
||||
[c1 |c2 |c3 |c4 |c5 |c6 |c7 ]
|
||||
[c1 |c2 |c3 |c4 |c5 |c6 |c7 ]
|
||||
[c1 |c2 |c3 |c4 |c5 |c6 |c7 ]
|
||||
[c1 |c2 |c3 |c4 |c5 |c6 |c7 ]
|
||||
[c1 |c2 |c3 |c4 |c5 |c6 |c7 ]
|
||||
< >
|
||||
< >
|
||||
<g g2 >
|
||||
[f1 ] [f2 ]
|
||||
City: [f4 ] TS: [f3 ]
|
||||
State: [f5 ]
|
||||
Zipcode: [f6 ]
|
||||
Address: [f7 ]
|
||||
[ ]
|
||||
< >
|
||||
}
|
||||
END
|
||||
END
|
||||
|
||||
ATTRIBUTES
|
||||
TABLE t1:DOUBLECLICK=modify;
|
||||
|
||||
GROUP g1: TEXT = "Customer list";
|
||||
c1 = FORMONLY.c_id TYPE INTEGER;
|
||||
c2 = FORMONLY.c_name TYPE VARCHAR;
|
||||
DATEEDIT c3 = FORMONLY.c_ts TYPE DATETIME YEAR TO SECOND, HIDDEN;
|
||||
c4 = FORMONLY.c_city,SCROLL;
|
||||
c5 = FORMONLY.c_state, HIDDEN=USER;
|
||||
c6 = FORMONLY.c_zipcode, HIDDEN=USER;
|
||||
c7 = FORMONLY.c_address, HIDDEN=USER;
|
||||
|
||||
GROUP g2: TEXT = "Details";
|
||||
f1 = FORMONLY.cust_id TYPE INTEGER, NOENTRY ;
|
||||
f2 = FORMONLY.cust_name TYPE VARCHAR, NOT NULL, REQUIRED;
|
||||
LABEL f3 = FORMONLY.cust_ts TYPE DATETIME YEAR TO SECOND;
|
||||
f4 = FORMONLY.cust_city TYPE CHAR, NOT NULL, REQUIRED;
|
||||
COMBOBOX f5 = FORMONLY.cust_state TYPE VARCHAR, NOT NULL, REQUIRED,
|
||||
QUERYEDITABLE, ITEMS=(("AK","Alaska"),("CA","California"),("VA","Virginia"));
|
||||
f6 = FORMONLY.cust_zipcode TYPE VARCHAR,
|
||||
INCLUDE=(10000 TO 99999),DEFAULT=11111,
|
||||
COMMENT = "Must be a number between 10000 and 99999";
|
||||
f7 = FORMONLY.cust_address TYPE VARCHAR,REQUIRED,SCROLL;
|
||||
|
||||
END
|
||||
|
||||
INSTRUCTIONS
|
||||
SCREEN RECORD sr (FORMONLY.c_id THROUGH FORMONLY.c_address);
|
||||
SCREEN RECORD cr (FORMONLY.cust_id THROUGH FORMONLY.cust_address);
|
||||
END
|
||||
@@ -0,0 +1,276 @@
|
||||
# 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.
|
||||
|
||||
TYPE t_custrec RECORD
|
||||
cust_id INTEGER,
|
||||
cust_name VARCHAR(50),
|
||||
cust_ts DATETIME YEAR TO SECOND,
|
||||
cust_city VARCHAR(30),
|
||||
cust_state CHAR(2),
|
||||
cust_zipcode SMALLINT,
|
||||
cust_address VARCHAR(100)
|
||||
END RECORD
|
||||
|
||||
DEFINE custrec, old_custrec t_custrec
|
||||
DEFINE custarr DYNAMIC ARRAY OF t_custrec
|
||||
|
||||
DEFINE myd ui.Dialog --module dialog variable to avoid passing DIALOG
|
||||
DEFINE isNew INT -- if > 0 then we are in new state
|
||||
DEFINE cust_touched INT
|
||||
|
||||
MAIN
|
||||
DEFINE ret INT
|
||||
DEFINE save_or_cancel STRING
|
||||
|
||||
OPTIONS INPUT WRAP
|
||||
OPEN FORM f1 FROM "CustListDetail2"
|
||||
DISPLAY FORM f1
|
||||
|
||||
CALL cust_loadlist(10)
|
||||
DIALOG ATTRIBUTES(FIELD ORDER FORM, UNBUFFERED)
|
||||
|
||||
DISPLAY ARRAY custarr TO sr.*
|
||||
|
||||
BEFORE ROW
|
||||
CALL cust_getdetail(arr_curr())
|
||||
|
||||
ON ACTION append
|
||||
CALL cust_new()
|
||||
|
||||
ON ACTION search
|
||||
CALL cust_search()
|
||||
|
||||
END DISPLAY
|
||||
|
||||
INPUT custrec.* FROM cr.* ATTRIBUTES(WITHOUT DEFAULTS)
|
||||
BEFORE INPUT
|
||||
CALL set_title()
|
||||
CALL cust_setup(FALSE)
|
||||
|
||||
ON ACTION save
|
||||
LET save_or_cancel="save"
|
||||
ACCEPT DIALOG
|
||||
|
||||
AFTER FIELD cust_address
|
||||
IF NOT custrec.cust_address MATCHES "*,*" THEN
|
||||
ERROR "Address must contain a comma (',')"
|
||||
LET save_or_cancel=NULL; NEXT FIELD CURRENT
|
||||
END IF
|
||||
|
||||
AFTER INPUT
|
||||
--do the checking if the record has to be saved
|
||||
IF cust_touched THEN
|
||||
LET ret=save_or_cancel IS NOT NULL
|
||||
IF NOT ret THEN
|
||||
LET ret = mbox_ync("Customers","Do you want to save your changes?","question")
|
||||
END IF
|
||||
CASE ret
|
||||
WHEN 1 --yes
|
||||
IF NOT cust_save() THEN
|
||||
LET save_or_cancel=NULL; NEXT FIELD CURRENT
|
||||
END IF
|
||||
CALL DIALOG.setFieldTouched("cr.*",0)
|
||||
LET custarr[myd.getCurrentRow("sr")].* = custrec.*
|
||||
WHEN 3 --cancel
|
||||
LET save_or_cancel=NULL; NEXT FIELD CURRENT
|
||||
END CASE
|
||||
END IF
|
||||
LET isNew=0
|
||||
CALL set_title()
|
||||
CALL cust_setup(0)
|
||||
CASE save_or_cancel
|
||||
WHEN "save" --stay in the dialog
|
||||
LET save_or_cancel=NULL; NEXT FIELD CURRENT
|
||||
WHEN "cancel" --exit the dialog
|
||||
EXIT DIALOG
|
||||
END CASE
|
||||
END INPUT
|
||||
|
||||
BEFORE DIALOG
|
||||
LET myd=DIALOG
|
||||
CALL set_title()
|
||||
CALL cust_setup(FALSE)
|
||||
IF custarr.getLength()=0 THEN
|
||||
CALL cust_new() --must be in a valid state if array is empty
|
||||
END IF
|
||||
|
||||
ON ACTION dialogtouched
|
||||
CALL cust_setup(TRUE)
|
||||
|
||||
ON ACTION undo
|
||||
LET custrec.* = old_custrec.*
|
||||
DISPLAY BY NAME custrec.*
|
||||
CALL myd.setFieldTouched("cr.*",FALSE)
|
||||
CALL cust_setup(FALSE)
|
||||
|
||||
ON ACTION close
|
||||
IF NOT cust_touched THEN EXIT DIALOG END IF
|
||||
--some extra code to ask back
|
||||
CASE mbox_ync("Customers","Do you want to save your changes?","question")
|
||||
WHEN 1 --yes
|
||||
LET save_or_cancel="cancel"
|
||||
ACCEPT DIALOG
|
||||
WHEN 2 --no
|
||||
EXIT DIALOG
|
||||
END CASE
|
||||
|
||||
ON ACTION delete
|
||||
IF custarr.getLength()>0 THEN
|
||||
IF mbox_yn("Customers", "Delete current record?", "question") THEN
|
||||
CALL cust_delete(DIALOG.getCurrentRow("sr"))
|
||||
IF custarr.getLength()=0 THEN
|
||||
CALL cust_new()
|
||||
END IF
|
||||
CALL cust_setup(FALSE)
|
||||
END IF
|
||||
END IF
|
||||
|
||||
END DIALOG
|
||||
LET myd=NULL
|
||||
END MAIN
|
||||
|
||||
FUNCTION cust_setup(touched)
|
||||
DEFINE touched INT
|
||||
LET cust_touched=touched
|
||||
CALL myd.setActionActive("dialogtouched", NOT touched)
|
||||
CALL myd.setActionActive("undo", touched)
|
||||
TRY --we might not be in the INPUT context
|
||||
CALL myd.setActionActive("save", touched)
|
||||
END TRY
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION cust_new()
|
||||
LET isNew=1
|
||||
CALL cust_setup(0)
|
||||
--set the dialog in virgin state and reload the defaults
|
||||
INITIALIZE custrec TO NULL
|
||||
CALL myd.setFieldTouched("cr.*",FALSE)
|
||||
--jump to1st field of input
|
||||
CALL myd.nextField("cust_name")
|
||||
CALL myd.appendRow("sr")
|
||||
CALL myd.setCurrentRow("sr",custarr.getLength())
|
||||
CALL set_title()
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION cust_search()
|
||||
DEFINE cond STRING
|
||||
LET int_flag=FALSE
|
||||
CONSTRUCT BY NAME cond ON cust_id,
|
||||
cust_name,
|
||||
cust_city,
|
||||
cust_state,
|
||||
cust_zipcode,
|
||||
cust_address
|
||||
IF int_flag == FALSE THEN
|
||||
MESSAGE cond
|
||||
CALL cust_loadlist(length(cond) / 3)
|
||||
CALL myd.setCurrentRow("sr",1)
|
||||
END IF
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION cust_delete(currRow)
|
||||
DEFINE currRow INTEGER
|
||||
DEFINE len INT
|
||||
LET len=custarr.getLength()
|
||||
CALL myd.deleteRow("sr",currRow)
|
||||
LET isNew=0
|
||||
IF currRow > len-1 THEN
|
||||
LET currRow=len-1
|
||||
CALL myd.setCurrentRow("sr",currRow)
|
||||
END IF
|
||||
CALL cust_getdetail(currRow)
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION cust_save()
|
||||
IF isNew>0 THEN
|
||||
DISPLAY "Execute SQL INSERT statement..."
|
||||
ELSE
|
||||
DISPLAY "Execute SQL UPDATE statement..."
|
||||
END IF
|
||||
IF custrec.cust_name="err" THEN --IF status...
|
||||
--simulate a database error
|
||||
ERROR "SQL ERROR"
|
||||
RETURN FALSE
|
||||
END IF
|
||||
RETURN TRUE
|
||||
END FUNCTION
|
||||
|
||||
-- Dummy function to fill the list of customers (normally, SELECT from database)
|
||||
FUNCTION cust_loadlist(m)
|
||||
DEFINE m,i INTEGER
|
||||
CALL custarr.clear()
|
||||
FOR i=1 TO m
|
||||
CALL custarr.appendElement()
|
||||
LET custarr[i].cust_id = i
|
||||
LET custarr[i].cust_name = "Name " || i
|
||||
LET custarr[i].cust_ts=CURRENT
|
||||
IF i MOD 3 == 0 THEN
|
||||
LET custarr[i].cust_city = "San Francisco"
|
||||
LET custarr[i].cust_state = "CA"
|
||||
LET custarr[i].cust_zipcode = 23219
|
||||
LET custarr[i].cust_address = "24, Blue Lagon Street"
|
||||
ELSE
|
||||
LET custarr[i].cust_city = "Richmond"
|
||||
LET custarr[i].cust_state = "CA"
|
||||
LET custarr[i].cust_zipcode = 12345
|
||||
LET custarr[i].cust_address = "45, Yellow Lake"
|
||||
END IF
|
||||
END FOR
|
||||
END FUNCTION
|
||||
|
||||
-- Dummy function to get customer details (normally, SELECT from database)
|
||||
FUNCTION cust_getdetail(row)
|
||||
DEFINE row INTEGER
|
||||
CALL set_title()
|
||||
CALL cust_setup(0)
|
||||
IF row=0 THEN
|
||||
INITIALIZE old_custrec.* TO NULL
|
||||
CLEAR cr.*
|
||||
RETURN
|
||||
END IF
|
||||
LET custrec.* = custarr[row].*
|
||||
LET old_custrec.* = custarr[row].*
|
||||
DISPLAY BY NAME custrec.*
|
||||
CALL myd.setFieldTouched("cr.*",0)
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION set_title()
|
||||
DEFINE row INT
|
||||
LET row=myd.getCurrentRow("sr")
|
||||
IF myd.getCurrentItem()="sr" THEN
|
||||
CALL fgl_settitle(sfmt("Record %1 of %2",row,custarr.getLength()))
|
||||
ELSE IF isNew>0 THEN
|
||||
CALL fgl_settitle("Input new customer")
|
||||
ELSE
|
||||
CALL fgl_settitle(sfmt("Modify Record %1 of %2",row,custarr.getLength()))
|
||||
END IF
|
||||
END IF
|
||||
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,74 @@
|
||||
# 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,DEFAULTVIEW=yes)
|
||||
ACTION delete (VALIDATE=NO)
|
||||
ACTION cancel (VALIDATE=NO)
|
||||
ACTION append (IMAGE="new")
|
||||
ACTION undo (TEXT="Undo",VALIDATE=NO)
|
||||
ACTION save (TEXT="Save")
|
||||
END
|
||||
|
||||
LAYOUT ( TEXT = "List and Detail" )
|
||||
VBOX
|
||||
GROUP (TEXT="Customer List")
|
||||
TABLE (DOUBLECLICK=modify,HEIGHT=6 LINES)
|
||||
{
|
||||
Id Name Timestamp City State Zip code Address
|
||||
[c1 |c2 |c3 |c4 |c5 |c6 |c7 ]
|
||||
}
|
||||
END
|
||||
END
|
||||
GROUP (TEXT="Details")
|
||||
GRID
|
||||
{
|
||||
Id [f1 ]
|
||||
Name [f2 ]
|
||||
City [f4 ] TS: [f3 ]
|
||||
State [f5 ]
|
||||
Zipcode[f6 ]
|
||||
Address[f7 ]
|
||||
[ :bsearch :bsave : bnew :bdel ]
|
||||
|
||||
}
|
||||
END
|
||||
END
|
||||
END
|
||||
|
||||
ATTRIBUTES
|
||||
|
||||
c1 = FORMONLY.c_id TYPE INTEGER;
|
||||
c2 = FORMONLY.c_name TYPE VARCHAR;
|
||||
DATEEDIT c3 = FORMONLY.c_ts TYPE DATETIME YEAR TO SECOND, HIDDEN;
|
||||
c4 = FORMONLY.c_city,SCROLL;
|
||||
c5 = FORMONLY.c_state, HIDDEN = USER;
|
||||
c6 = FORMONLY.c_zipcode, HIDDEN = USER;
|
||||
c7 = FORMONLY.c_address, HIDDEN = USER;
|
||||
|
||||
f1 = FORMONLY.cust_id TYPE INTEGER, NOENTRY ;
|
||||
f2 = FORMONLY.cust_name TYPE VARCHAR, NOT NULL, REQUIRED,SCROLL;
|
||||
LABEL f3 = FORMONLY.cust_ts TYPE DATETIME YEAR TO SECOND;
|
||||
f4 = FORMONLY.cust_city TYPE CHAR, NOT NULL, REQUIRED;
|
||||
COMBOBOX f5 = FORMONLY.cust_state TYPE VARCHAR, NOT NULL, REQUIRED,
|
||||
QUERYEDITABLE, ITEMS=(("AK","Alaska"),("CA","California"),("VA","Virginia"));
|
||||
f6 = FORMONLY.cust_zipcode TYPE VARCHAR,
|
||||
INCLUDE=(10000 TO 99999),DEFAULT=11111,
|
||||
COMMENT = "Must be a number between 10000 and 99999";
|
||||
f7 = FORMONLY.cust_address TYPE VARCHAR,REQUIRED,SCROLL;
|
||||
BUTTON bsearch:sr.search,TEXT="Search",IMAGE="find";
|
||||
BUTTON bsave:save,TEXT="Save",IMAGE="disk";
|
||||
BUTTON bnew:sr.append,TEXT="New",IMAGE="new";
|
||||
BUTTON bdel:delete,TEXT="Del",IMAGE="delete";
|
||||
END
|
||||
|
||||
INSTRUCTIONS
|
||||
SCREEN RECORD sr (FORMONLY.c_id THROUGH FORMONLY.c_address);
|
||||
SCREEN RECORD cr (FORMONLY.cust_id THROUGH FORMONLY.cust_address);
|
||||
END
|
||||
@@ -0,0 +1,223 @@
|
||||
# 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.
|
||||
|
||||
TYPE t_custrec RECORD
|
||||
cust_id INTEGER,
|
||||
cust_name VARCHAR(50),
|
||||
cust_ts DATETIME YEAR TO SECOND,
|
||||
cust_city VARCHAR(30),
|
||||
cust_state CHAR(2),
|
||||
cust_zipcode VARCHAR(6),
|
||||
cust_address VARCHAR(100)
|
||||
END RECORD
|
||||
|
||||
DEFINE custarr,carr,oldarr DYNAMIC ARRAY OF t_custrec
|
||||
|
||||
DEFINE myd ui.Dialog --module dialog variable to avoid passing DIALOG
|
||||
DEFINE cust_touched INT
|
||||
|
||||
MAIN
|
||||
DEFINE i INT
|
||||
|
||||
OPTIONS INPUT WRAP
|
||||
OPEN FORM f1 FROM "CustListDetail3"
|
||||
DISPLAY FORM f1
|
||||
|
||||
CALL cust_search()
|
||||
FOR i=1 TO custarr.getLength()
|
||||
LET oldarr[i].*=custarr[i].*
|
||||
END FOR
|
||||
DIALOG ATTRIBUTES(UNBUFFERED)
|
||||
|
||||
DISPLAY ARRAY custarr TO sr.*
|
||||
|
||||
BEFORE ROW
|
||||
DISPLAY "BEFORE ROW sr:", sr_arr_curr()
|
||||
IF sr_arr_curr()==0 THEN --input new record
|
||||
NEXT FIELD cr.cust_name
|
||||
ELSE --sync the other array
|
||||
CALL myd.setCurrentRow("cr",sr_arr_curr())
|
||||
CALL fgl_settitle(sfmt("Record %1 of %2",sr_arr_curr(),custarr.getLength()))
|
||||
END IF
|
||||
|
||||
END DISPLAY
|
||||
|
||||
INPUT ARRAY carr FROM cr.* ATTRIBUTES(INSERT ROW=FALSE)
|
||||
|
||||
BEFORE ROW --sync the other array
|
||||
DISPLAY "BEFORE ROW cr:", cr_arr_curr(),cr_arr_count()
|
||||
CALL DIALOG.setCurrentRow("sr",cr_arr_curr())
|
||||
CALL fgl_settitle(sfmt("Modify Record %1 of %2",cr_arr_curr(),custarr.getLength()))
|
||||
|
||||
AFTER FIELD cust_address
|
||||
IF NOT carr[cr_arr_curr()].cust_address MATCHES "*,*" THEN
|
||||
ERROR "Address must contain a comma (',')"
|
||||
NEXT FIELD CURRENT
|
||||
END IF
|
||||
|
||||
AFTER DELETE --delete the row in the other array
|
||||
CALL DIALOG.deleteRow("sr",cr_arr_curr())
|
||||
|
||||
BEFORE INSERT --append the row in the other array
|
||||
DISPLAY "BEFORE INSERT cr_arr_curr:",cr_arr_curr(),cr_arr_count()
|
||||
CALL DIALOG.appendRow("sr")
|
||||
CALL DIALOG.setCurrentRow("sr",cr_arr_curr())
|
||||
CALL fgl_settitle("Input new customer")
|
||||
|
||||
AFTER ROW
|
||||
DISPLAY "AFTER ROW cr:",cr_arr_curr(),cr_arr_count()
|
||||
IF cr_arr_curr()>cr_arr_count() THEN --delete temp row
|
||||
CALL DIALOG.deleteRow("sr",cr_arr_curr())
|
||||
ELSE
|
||||
IF FIELD_TOUCHED(cr.*) THEN --do your row checking here
|
||||
LET cust_touched=1
|
||||
LET custarr[cr_arr_curr()].*=carr[cr_arr_curr()].*
|
||||
END IF
|
||||
END IF
|
||||
|
||||
END INPUT
|
||||
|
||||
BEFORE DIALOG
|
||||
LET cust_touched=0
|
||||
LET myd=DIALOG
|
||||
|
||||
ON ACTION accept
|
||||
ACCEPT DIALOG
|
||||
|
||||
ON ACTION cancel
|
||||
IF (NOT cust_touched) AND (NOT FIELD_TOUCHED(cr.*)) THEN
|
||||
EXIT DIALOG
|
||||
END IF
|
||||
--some extra code to ask back
|
||||
IF mbox_yn("Customers", "Cancel without saving?", "question") THEN
|
||||
EXIT DIALOG
|
||||
END IF
|
||||
|
||||
AFTER DIALOG
|
||||
DISPLAY "AFTER DIALOG"
|
||||
--BEGIN WORK
|
||||
IF (i:=cust_save())<>0 THEN
|
||||
--ROLLBACK WORK
|
||||
--FIXME: where is the function? CALL mbox_ok("Error","Could not update/insert customer","stop")
|
||||
CALL myd.setCurrentRow("cr",i)
|
||||
NEXT FIELD cr.cust_name
|
||||
END IF
|
||||
--COMMIT WORK
|
||||
|
||||
END DIALOG
|
||||
LET myd=NULL
|
||||
END MAIN
|
||||
|
||||
--helpers to prevent writing too much...
|
||||
FUNCTION cr_arr_curr() RETURN myd.getCurrentRow("cr") END FUNCTION
|
||||
|
||||
FUNCTION cr_arr_count() RETURN myd.getArrayLength("cr") END FUNCTION
|
||||
|
||||
FUNCTION sr_arr_curr() RETURN myd.getCurrentRow("sr") END FUNCTION
|
||||
|
||||
FUNCTION cust_search()
|
||||
DEFINE cond STRING
|
||||
LET int_flag=FALSE
|
||||
CALL fgl_settitle("Input search criteria and press <Enter>")
|
||||
CONSTRUCT BY NAME cond ON cust_id,
|
||||
cust_name,
|
||||
cust_city,
|
||||
cust_state,
|
||||
cust_zipcode,
|
||||
cust_address
|
||||
IF int_flag == FALSE THEN
|
||||
MESSAGE cond
|
||||
CALL cust_loadlist(10)
|
||||
END IF
|
||||
END FUNCTION
|
||||
|
||||
--compare custarr with the oldarr and make the necessary updates/inserts/deletes
|
||||
FUNCTION cust_save()
|
||||
DEFINE i,j,oldlen INT
|
||||
DEFINE oldarr2 DYNAMIC ARRAY OF t_custrec --make another copy for multiple trials
|
||||
LET oldlen=oldarr.getLength()
|
||||
FOR i=1 TO oldlen
|
||||
LET oldarr2[i].*=oldarr[i].*
|
||||
END FOR
|
||||
FOR i=1 TO custarr.getLength()
|
||||
IF custarr[i].cust_id>0 THEN --existing customer
|
||||
FOR j=1 TO oldlen
|
||||
IF custarr[i].cust_id=oldarr2[j].cust_id THEN
|
||||
IF custarr[i].* <> oldarr2[j].* THEN --the record has been changed
|
||||
DISPLAY "UPDATE cust_id:",custarr[i].cust_id,",",custarr[i].cust_name
|
||||
--UPDATE customer where... WHERE cust_id=custarr[i].cust_id
|
||||
END IF
|
||||
CALL oldarr2.deleteElement(j)
|
||||
LET oldlen = oldlen - 1
|
||||
EXIT FOR
|
||||
END IF
|
||||
END FOR
|
||||
ELSE
|
||||
DISPLAY "insert customer name:",custarr[i].cust_name
|
||||
--INSERT INTO customer ...
|
||||
END IF
|
||||
IF custarr[i].cust_name="err" THEN --simulate database error
|
||||
RETURN i
|
||||
END IF
|
||||
END FOR
|
||||
--remove remaining oldarr2 items
|
||||
FOR i=1 TO oldlen
|
||||
DISPLAY "DELETE cust_id:",oldarr2[i].cust_id,",",oldarr2[i].cust_name
|
||||
--DELETE FROM customer WHERE cust_id=oldarr[i].cust_id
|
||||
END FOR
|
||||
RETURN 0
|
||||
END FUNCTION
|
||||
|
||||
-- Dummy function to fill the list of customers (normally, SELECT from database)
|
||||
FUNCTION cust_loadlist(m)
|
||||
DEFINE m,i INTEGER
|
||||
CALL custarr.clear()
|
||||
FOR i=1 TO m
|
||||
CALL custarr.appendElement()
|
||||
LET custarr[i].cust_id = i
|
||||
LET custarr[i].cust_name = "Name " || i
|
||||
LET custarr[i].cust_ts=CURRENT
|
||||
IF i MOD 3 == 0 THEN
|
||||
LET custarr[i].cust_city = "San Francisco"
|
||||
LET custarr[i].cust_state = "CA"
|
||||
LET custarr[i].cust_zipcode = "76231"
|
||||
LET custarr[i].cust_address = "24, Blue Lagon Street"
|
||||
ELSE
|
||||
LET custarr[i].cust_city = "Richmond"
|
||||
LET custarr[i].cust_state = "CA"
|
||||
LET custarr[i].cust_zipcode = "71212"
|
||||
LET custarr[i].cust_address = "45, Yellow Lake"
|
||||
END IF
|
||||
END FOR
|
||||
FOR i=1 TO custarr.getLength()
|
||||
LET carr[i].*=custarr[i].*
|
||||
END FOR
|
||||
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,80 @@
|
||||
# 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 append (IMAGE="new")
|
||||
ACTION undo (TEXT="Undo",VALIDATE=NO)
|
||||
END
|
||||
|
||||
TOOLBAR
|
||||
ITEM close
|
||||
END
|
||||
|
||||
LAYOUT ( TEXT = "List and Detail" )
|
||||
GRID
|
||||
{
|
||||
<g g1 >
|
||||
<t t1 >
|
||||
Id Name Timestamp City State Zip code Address
|
||||
[c1 |c2 |c3 |c4 |c5 |c6 |c7 ]
|
||||
[c1 |c2 |c3 |c4 |c5 |c6 |c7 ]
|
||||
[c1 |c2 |c3 |c4 |c5 |c6 |c7 ]
|
||||
[c1 |c2 |c3 |c4 |c5 |c6 |c7 ]
|
||||
[c1 |c2 |c3 |c4 |c5 |c6 |c7 ]
|
||||
[c1 |c2 |c3 |c4 |c5 |c6 |c7 ]
|
||||
< >
|
||||
<g g2 >
|
||||
Id [f1 ]
|
||||
Name [f2 ]
|
||||
City [f4 ] TS: [f3 ]
|
||||
State [f5 ]
|
||||
Zipcode[f6 ]
|
||||
Address[f7 ]
|
||||
[ :bf :bp :bn :bl : bnew :bdel ]
|
||||
< >
|
||||
}
|
||||
END
|
||||
END
|
||||
|
||||
ATTRIBUTES
|
||||
GROUP g1: TEXT = "Customer list";
|
||||
c1 = FORMONLY.c_id TYPE INTEGER;
|
||||
c2 = FORMONLY.c_name TYPE VARCHAR;
|
||||
DATEEDIT c3 = FORMONLY.c_ts TYPE DATETIME YEAR TO SECOND, HIDDEN;
|
||||
c4 = FORMONLY.c_city,SCROLL;
|
||||
c5 = FORMONLY.c_state;
|
||||
c6 = FORMONLY.c_zipcode;
|
||||
c7 = FORMONLY.c_address;
|
||||
|
||||
GROUP g2: TEXT = "Details";
|
||||
f1 = FORMONLY.cust_id TYPE INTEGER, NOENTRY,DEFAULT=0 ;
|
||||
f2 = FORMONLY.cust_name TYPE VARCHAR, NOT NULL, REQUIRED,SCROLL;
|
||||
LABEL f3 = FORMONLY.cust_ts TYPE DATETIME YEAR TO SECOND;
|
||||
f4 = FORMONLY.cust_city TYPE CHAR, NOT NULL, REQUIRED;
|
||||
COMBOBOX f5 = FORMONLY.cust_state TYPE VARCHAR, NOT NULL, REQUIRED,
|
||||
QUERYEDITABLE, ITEMS=(("AK","Alaska"),("CA","California"),("VA","Virginia"));
|
||||
f6 = FORMONLY.cust_zipcode TYPE VARCHAR,
|
||||
INCLUDE=(10000 TO 99999),DEFAULT=11111,
|
||||
COMMENT = "Must be a number between 10000 and 99999";
|
||||
f7 = FORMONLY.cust_address TYPE VARCHAR,REQUIRED,SCROLL;
|
||||
|
||||
BUTTON bf:firstrow;
|
||||
BUTTON bp:prevrow;
|
||||
BUTTON bn:nextrow;
|
||||
BUTTON bl:lastrow;
|
||||
BUTTON bnew:cr.append,TEXT="New",IMAGE="new";
|
||||
BUTTON bdel:cr.delete,TEXT="Del",IMAGE="delete";
|
||||
END
|
||||
|
||||
INSTRUCTIONS
|
||||
SCREEN RECORD sr (FORMONLY.c_id THROUGH FORMONLY.c_address);
|
||||
SCREEN RECORD cr (FORMONLY.cust_id THROUGH FORMONLY.cust_address);
|
||||
END
|
||||
@@ -0,0 +1,175 @@
|
||||
# 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.
|
||||
|
||||
TYPE t_custrec RECORD
|
||||
cust_id INTEGER,
|
||||
cust_name VARCHAR(50),
|
||||
cust_ts DATETIME YEAR TO SECOND,
|
||||
cust_city VARCHAR(30),
|
||||
cust_state CHAR(2),
|
||||
cust_zipcode VARCHAR(6),
|
||||
cust_address VARCHAR(100)
|
||||
END RECORD
|
||||
|
||||
--normally this is a DEFINE custarr DYNAMIC ARRAY OF RECORD LIKE customer.*
|
||||
DEFINE custarr DYNAMIC ARRAY OF t_custrec
|
||||
DEFINE myd ui.DIALOG
|
||||
|
||||
MAIN
|
||||
DEFINE currRow INTEGER
|
||||
|
||||
OPEN FORM f1 FROM "CustListDetail"
|
||||
DISPLAY FORM f1
|
||||
|
||||
CALL cust_loadlist(10)
|
||||
DISPLAY ARRAY custarr TO sr.* ATTRIBUTES(UNBUFFERED,ACCEPT=FALSE)
|
||||
|
||||
BEFORE DISPLAY
|
||||
LET myd=DIALOG
|
||||
|
||||
BEFORE ROW
|
||||
CALL cust_getdetail(arr_curr())
|
||||
|
||||
ON ACTION search
|
||||
CALL cust_search()
|
||||
|
||||
ON ACTION modify
|
||||
CALL cust_modify(0,arr_curr()) RETURNING currRow
|
||||
|
||||
ON ACTION append
|
||||
CALL fgl_set_arr_curr(cust_modify(1,arr_curr()))
|
||||
|
||||
ON ACTION delete
|
||||
IF custarr.getLength()>0 THEN
|
||||
IF mbox_yn("Customers", "Do you want to delete the current customer?", "question") THEN
|
||||
CALL cust_delete(arr_curr())
|
||||
END IF
|
||||
END IF
|
||||
|
||||
END DISPLAY
|
||||
LET myd=NULL
|
||||
END MAIN
|
||||
|
||||
FUNCTION cust_modify(new,i)
|
||||
DEFINE new INT
|
||||
DEFINE i INT
|
||||
DEFINE myrec t_custrec
|
||||
IF new THEN
|
||||
CALL fgl_settitle("Input New Customer")
|
||||
ELSE
|
||||
CALL fgl_settitle("Modify Customer")
|
||||
LET myrec.*=custarr[i].*
|
||||
END IF
|
||||
LET int_flag=FALSE
|
||||
INPUT BY NAME myrec.* ATTRIBUTES(UNBUFFERED,WITHOUT DEFAULTS=NOT new)
|
||||
AFTER FIELD cust_address
|
||||
IF NOT myrec.cust_address MATCHES "*,*" THEN
|
||||
ERROR "Address must contain a comma (',')"
|
||||
NEXT FIELD CURRENT
|
||||
END IF
|
||||
END INPUT
|
||||
IF int_flag=FALSE THEN
|
||||
IF new THEN
|
||||
LET i=custarr.getLength()+1
|
||||
LET myrec.cust_id=100+i
|
||||
DISPLAY "INSERT INTO database: id:",myrec.cust_id,",name:",myrec.cust_name
|
||||
ELSE
|
||||
DISPLAY "UPDATE DATABASE: id:",myrec.cust_id,",name:",myrec.cust_name
|
||||
END IF
|
||||
LET custarr[i].* = myrec.*
|
||||
DISPLAY BY NAME custarr[i].*
|
||||
END IF
|
||||
CALL cust_getdetail(i)
|
||||
RETURN i
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION cust_delete(currRow)
|
||||
DEFINE currRow INTEGER
|
||||
DEFINE len INT
|
||||
LET len=custarr.getLength()
|
||||
CALL custarr.deleteElement(currRow)
|
||||
IF currRow > len-1 THEN
|
||||
LET currRow=len-1
|
||||
END IF
|
||||
CALL cust_getdetail(currRow)
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION cust_search()
|
||||
DEFINE cond STRING
|
||||
LET int_flag=FALSE
|
||||
CALL fgl_settitle("Search Customer")
|
||||
CONSTRUCT BY NAME cond ON cust_id,
|
||||
cust_name,
|
||||
cust_city,
|
||||
cust_state,
|
||||
cust_zipcode,
|
||||
cust_address
|
||||
IF int_flag == FALSE THEN
|
||||
MESSAGE cond
|
||||
CALL cust_loadlist(length(cond) / 3)
|
||||
CALL cust_getdetail(arr_curr())
|
||||
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 custarr.clear()
|
||||
FOR i=1 TO m
|
||||
CALL custarr.appendElement()
|
||||
LET custarr[i].cust_id = i
|
||||
LET custarr[i].cust_name = "Name " || i
|
||||
LET custarr[i].cust_ts = CURRENT
|
||||
IF i MOD 3 == 0 THEN
|
||||
LET custarr[i].cust_city = "San Francisco"
|
||||
LET custarr[i].cust_state = "CA"
|
||||
LET custarr[i].cust_zipcode = 94102
|
||||
LET custarr[i].cust_address = "24, Blue Lagon Street"
|
||||
ELSE
|
||||
LET custarr[i].cust_city = "Richmond"
|
||||
LET custarr[i].cust_state = "VA"
|
||||
LET custarr[i].cust_zipcode = 23219
|
||||
LET custarr[i].cust_address = "45, Yellow Lake"
|
||||
END IF
|
||||
END FOR
|
||||
END FUNCTION
|
||||
|
||||
-- Dummy function to get customer details (normally, SELECT from database)
|
||||
FUNCTION cust_getdetail(row)
|
||||
DEFINE row INTEGER
|
||||
CALL fgl_settitle(sfmt("Record %1 of %2",row,custarr.getLength()))
|
||||
CALL myd.setActionActive("delete",custarr.getLength()>0)
|
||||
CALL myd.setActionActive("modify",custarr.getLength()>0)
|
||||
IF row <= 0 THEN
|
||||
CLEAR cr.*
|
||||
RETURN
|
||||
END IF
|
||||
DISPLAY custarr[row].* TO cr.*
|
||||
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,19 @@
|
||||
# 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 = \
|
||||
CustListDetail.42m\
|
||||
CustListDetail_old.42m\
|
||||
CustListDetail2.42m\
|
||||
CustListDetail3.42m
|
||||
FORMS = CustListDetail.42f\
|
||||
CustListDetail_old.42m\
|
||||
CustListDetail2.42f\
|
||||
CustListDetail3.42f
|
||||
include ../../Makeincl
|
||||
@@ -0,0 +1,107 @@
|
||||
Editable Customer List
|
||||
-----------------------
|
||||
|
||||
This directory contains some trials to solve the following task:
|
||||
A database table should be displayed and it should be possible to simultaneously edit the highlighted table record in a detail input.
|
||||
It should be possible to edit/delete records , append new records and limit the displayed amount of data by calling a filter dialog.
|
||||
|
||||
This sounds like an easy game for multiple dialogs, but it turned out to be a bit harder than expected.
|
||||
First lets inspect a sample implementing this task without multiple dialogs.
|
||||
|
||||
CustListDetail_old.4gl:
|
||||
-----------------------
|
||||
Browses the table in a DISPLAY ARRAY and shows simultaneously the details in another screen record, but to edit the record one has to double-click the highlighted line or to press the "Modify" button. To append a new record in the database, one has to press the "Append" button. The title of the window is set to assist the end user in which mode the program is. The code to do that is pretty short compared to the multiple dialogs samples.
|
||||
|
||||
Improved with multiple dialogs?
|
||||
------------------------------
|
||||
Now when starting CustListDetail or CustListDetail2 they both show nearly the same behavior:
|
||||
It's possible to click directly into the record INPUT for the selected line in the DISPLAY ARRAY.
|
||||
In the moment if we start to edit and change the current record we immediately run into a serious validation problem: some of our actions require the validation of the current record before we can continue with the intended action.
|
||||
|
||||
Example:
|
||||
Change something in the current Record and pressing the 'Append' button:
|
||||
In this moment you will be asked if you want to save your changes (CustListDetail) and after the confirmation the INPUT is cleared and you can enter new data.
|
||||
|
||||
The code to do that isn't that hard but you will have to perform the same steps which would have been done automatically for you if you would have left the dialog with ACCEPT DIALOG:
|
||||
|
||||
v1: Verify if the field constraints are met (by calling the new DIALOG.validate() method).
|
||||
|
||||
v2: Call the code which is called in the AFTER FIELD trigger of the field you are currently inside.
|
||||
|
||||
v3: Call the code which is called in the AFTER INPUT.
|
||||
|
||||
Especially v2 is hard to solve if you have a lot of AFTER FIELD clauses, then you will either have to check with
|
||||
IF INFIELD(a) THEN CALL after_field(a) END IF
|
||||
or you check anyway all your AFTER FIELD constraints in each action which is type of ACCEPT DIALOG.
|
||||
In the current sample it is sufficient to call the check code for AFTER FIELD address (done in CustListDetail.4gl in 'cust_validate_address' and in CustListDetail2 directly in AFTER FIELD address)
|
||||
|
||||
The validation steps have to be invoked for each action in the multiple dialog which require it: 'Append', 'Query' (we want to have saved correctly before we refill the list with data) and 'Save'.
|
||||
The 'Save' button is optional, normally we don't need it because we ask in each possible step for confirmation, but it might help to assist end users which miss the 'Ok' button.
|
||||
|
||||
If we click into the DISPLAY ARRAY, MD helps us by automatically invoking the AFTER FIELD and AFTER INPUT of the INPUT sub dialog.
|
||||
|
||||
CustListDetail.4gl :
|
||||
--------------------
|
||||
The 'hard' way:
|
||||
Solves the validation problems described above by calling a validation procedure ( cust_validate() ) which makes all the necessary checks. If an AFTER FIELD constraint is added later to a particular field, it must be called also in this validation procedure. The code to handle the actions 'append' and 'search' need to call the validation explicitly before performing the indended action code. The code for 'save' is similar: it must als check the validation before.
|
||||
|
||||
The sample shows also how to use the new 'dialogtouched' action, however this action is not limited to Multiple Dialogs, it is also possible to use it in traditional 4GL dialogs.
|
||||
The wish to use 'dialogtouched' is driven to enable/disable the 'Save' action depending on changes in the current record: Because there is no clear Ok/Cancel dialog, it's adds more visibility to the current input state.
|
||||
Note 'dialogtouched' in a traditional INPUT with Ok/Cancel could be used to enable the 'Ok' button only if something has been changed.
|
||||
|
||||
CustListdetail2.4gl
|
||||
-------------------
|
||||
The sub dialog action way:
|
||||
Uses actions dedicated to the sub dialog to surround the need for multiple validation checkings.
|
||||
The 'append' and the 'search' actions are inside the DISPLAY ARRAY statement.
|
||||
This means normally that this actions are only shown in the Action Panel if the focus is inside the DISPLAY ARRAY . If the focus is inside the DISPLAY ARRAY we went already thru the validation in the AFTER INPUT and hence have no need to check again: all validations for the INPUT sub dialog have been done already.
|
||||
However there is the possility to make those actions always visible by fully qualifying them as BUTTON's inside the .per file, the end user does not see that this action is a sub dialog action:
|
||||
|
||||
BUTTON bnew:sr.append,TEXT="New",IMAGE="new";
|
||||
|
||||
the "sr.append" name means:display the append action for the sub dialog which handles the "sr" screen record (which is the DISPLAY ARRAY). This fully qualified action name instructs the run time to make the action always visible and do the following if one clicks on it:
|
||||
If the focus is not inside the DISPLAY ARRAY,move the focus to the display array and perform the validation of the current sub dialog (the INPUT in this case).
|
||||
Then the action code is performed.
|
||||
This is exactly the case we need: our 'append'/'search' action code should be only performed if the INPUT is valid. By using this technique we achieve that.
|
||||
If the focus is already inside the DISPLAY, the code for the action is immediately executed:no need to validate the INPUT, ist must have been validated earlier in the AFTER INPUT.
|
||||
|
||||
CustListDetail3.4gl
|
||||
-------------------
|
||||
The 3rd sample works like a traditional INPUT ARRAY, it has an OK/Cancel button and all the database work is assumed to take place after Ok has been clicked.
|
||||
It also uses a single row INPUT ARRAY in place of the INPUT of sample1/sample2 showing that one can again save code if he uses the INPUT ARRAY.
|
||||
|
||||
Advantage:
|
||||
-no confirmations inbetween the rows.
|
||||
-Database stuff in 1 transaction possible
|
||||
Disadvantages:
|
||||
-some more logic at the end needed to delete/insert/update rows from the database
|
||||
-The sample also omit's the 'search' action because a search must be done then before calling this dialog.
|
||||
|
||||
Further caveeat
|
||||
---------------
|
||||
Both multiple dialogs using INPUT samples have another caveeat:
|
||||
The DIALOG is not ready yet to toggle sub dialogs between the 2 modes WITHOUT DEFAULTS and without WITHOUT DEFAULTS dynamically.
|
||||
Typically in the CustListDetail_old sample the INPUT is called with
|
||||
INPUT ATTRIBUTES(WITHOUT DEFAULTS=NOT new)
|
||||
and in the new mode (WITHOUT DEFAULTS=FALSE) the DEFAULT values are load from the .per definition and the REQUIRED attribute is taken into account.
|
||||
In the multiple dialog sample we can only use WITHOUT DEFAULTS=TRUE because we 'stay' in the INPUT if the record is changed and have yet no dynamic method available to put the INPUT into 'virgin' state if we press the 'Append' Button.
|
||||
(Writing WITHOUT DEFAULTS=FALSE works but of cause only once for the first record displayed in the program and will be immediately overwritten by the first BEFORE ROW code of the DISPLAY ARRAY)
|
||||
That's why the REQUIRED and DEFAULT attributes are not usable in this case, which raises again problems:
|
||||
REQUIRED is used to force a user to visit a field in WITHOUT DEFAULTS=FALSE mode to ensure that an AFTER FIELD trigger is called.
|
||||
This mechanism is not usable with WITHOUT DEFAULTS=TRUE, instead one has to set the NOT NULL constraint together with a NULL value as default to force an end user to visit the field initially.
|
||||
Note:
|
||||
This only arises in the situation that we use the multiple dialog for inputting several records in one INPUT , as long as you use the multiple dialog as a typical Ok/Cancel dialog like shown in other samples (../Folder) the WITHOUT DEFAULTS mechanism works like expected.
|
||||
|
||||
Conclusion
|
||||
----------
|
||||
To make the wanted visual behavior happen you have a lot of possibilities
|
||||
|
||||
1. Calling all validations 'manually' for each action needing the validation for the current sub dialog(CustListDetail.4gl). This forces you to make extra functions for *each* AFTER FIELD check in your INPUT!
|
||||
As already said, this is the 'hard' way and is also not looking intuitive, we cannot recommend it. Use one of the next techniques to make your life easier.
|
||||
|
||||
2. using sub dialog actions local to the DISPLAY ARRAY to let the DIALOG doing the validation for you . You need to get a bit experience how to fully qualify the sub dialog local action in the .per to let this action be active in each place of the DIALOG.
|
||||
(CustListDetail2.4gl)
|
||||
|
||||
3. Synchronizing an INPUT ARRAY with a DISPLAY ARRAY to save code lines and get 'append'/'delete' for free
|
||||
|
||||
In case 1.) case you will have to write a lot more code than in the original sample ,in case 2. and 3.) you must know about the possibilies in using sub dialog local actions for achieving the necessary validations.
|
||||
@@ -0,0 +1,762 @@
|
||||
# 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 os
|
||||
IMPORT util
|
||||
IMPORT FGL MsgBox
|
||||
|
||||
DEFINE charset STRING
|
||||
DEFINE boundary STRING
|
||||
DEFINE rv INTEGER
|
||||
DEFINE smtplog base.StringBuffer
|
||||
DEFINE logtext STRING
|
||||
DEFINE tz STRING
|
||||
|
||||
DEFINE ext2mime DYNAMIC ARRAY OF RECORD
|
||||
ext STRING,
|
||||
mime STRING,
|
||||
encoding STRING
|
||||
END RECORD
|
||||
|
||||
&define EXT2MIME(a, b, c) \
|
||||
LET ext2mime[nmime].ext = a \
|
||||
LET ext2mime[nmime].mime = b \
|
||||
LET ext2mime[nmime].encoding = c \
|
||||
LET nmime = nmime + 1
|
||||
|
||||
TYPE cnf_t RECORD
|
||||
hostname STRING,
|
||||
smtphost STRING,
|
||||
smtpport INTEGER,
|
||||
senderemail STRING,
|
||||
cnxtimeout INTEGER
|
||||
END RECORD
|
||||
DEFINE cnf cnf_t
|
||||
|
||||
MAIN
|
||||
DEFINE subject STRING
|
||||
DEFINE recipients DYNAMIC ARRAY OF RECORD mode STRING, recipient STRING END RECORD
|
||||
DEFINE mailtext STRING
|
||||
DEFINE mailbody DYNAMIC ARRAY OF STRING
|
||||
DEFINE attachments DYNAMIC ARRAY OF RECORD fname, sname STRING END RECORD
|
||||
DEFINE rcpt DYNAMIC ARRAY OF STRING
|
||||
DEFINE nmime INTEGER
|
||||
DEFINE filename STRING
|
||||
|
||||
OPTIONS INPUT WRAP
|
||||
|
||||
# TZ checks
|
||||
IF NUM_ARGS() == 1 THEN
|
||||
IF ARG_VAL(1) == "TZGMT" THEN
|
||||
CALL fgl_setenv("TZ", "GMT")
|
||||
CALL showTZ()
|
||||
END IF
|
||||
IF ARG_VAL(1) == "TZLOCAL" THEN
|
||||
CALL showTZ()
|
||||
END IF
|
||||
IF ARG_VAL(1) == "DTSHOW" THEN
|
||||
DISPLAY CURRENT YEAR TO MINUTE
|
||||
END IF
|
||||
EXIT PROGRAM 0
|
||||
END IF
|
||||
|
||||
OPEN FORM maildemo FROM "Mailer"
|
||||
DISPLAY FORM maildemo
|
||||
|
||||
LET charset = getCharset()
|
||||
|
||||
# Mime types initialization - should be left configurable by the client
|
||||
LET nmime = 1
|
||||
EXT2MIME("bin", "application/octet-stream", "base64")
|
||||
EXT2MIME("pdf", "application/x-pdf", "base64")
|
||||
EXT2MIME("gz", "application/x-gzip", "base64")
|
||||
EXT2MIME("tgz", "application/x-compressed-tar", "base64")
|
||||
EXT2MIME("gif", "image/gif", "base64")
|
||||
EXT2MIME("jpg", "image/jpg", "base64")
|
||||
EXT2MIME("png", "image/png", "base64")
|
||||
EXT2MIME("txt", "text/plain", "7bit")
|
||||
EXT2MIME("per", "text/plain", "7bit")
|
||||
EXT2MIME("4gl", "text/plain", "7bit")
|
||||
EXT2MIME("42m", "application/octet-stream", "base64")
|
||||
|
||||
CALL load_maildemo_cnf()
|
||||
LET tz = detect_timezone()
|
||||
LET boundary='--------------' || util.Math.rand(1000) || util.Math.rand(1000) || util.Math.rand(1000) || util.Math.rand(1000)
|
||||
|
||||
# Mail composition dialog
|
||||
DIALOG ATTRIBUTE(UNBUFFERED)
|
||||
|
||||
INPUT BY NAME cnf.* ATTRIBUTES(WITHOUT DEFAULTS)
|
||||
END INPUT
|
||||
|
||||
INPUT ARRAY recipients FROM srrecipients.* ATTRIBUTES(WITHOUT DEFAULTS)
|
||||
BEFORE INSERT
|
||||
LET recipients[dialog.getCurrentRow("srrecipients")].mode = "To"
|
||||
AFTER ROW
|
||||
IF LENGTH(recipients[dialog.getCurrentRow("srrecipients")].recipient) == 0 THEN
|
||||
CALL recipients.deleteElement(dialog.getCurrentRow("srrecipients"))
|
||||
END IF
|
||||
END INPUT
|
||||
|
||||
INPUT BY NAME subject, mailtext
|
||||
END INPUT
|
||||
|
||||
DISPLAY ARRAY attachments TO srattachments.*
|
||||
END DISPLAY
|
||||
|
||||
INPUT BY NAME logtext
|
||||
END INPUT
|
||||
|
||||
BEFORE DIALOG
|
||||
IF LENGTH(cnf.hostname) == 0 OR LENGTH(cnf.smtpHost) == 0 OR LENGTH(cnf.senderEmail) == 0 THEN
|
||||
NEXT FIELD hostname
|
||||
END IF
|
||||
NEXT FIELD recipient
|
||||
ON ACTION attachfile
|
||||
CALL ui.Interface.frontCall("standard", "openfile", ["C:\\", "*", "", "Attach file"], [filename])
|
||||
IF filename IS NOT NULL THEN
|
||||
LET attachments[attachments.getLength()+1].fname = filename
|
||||
LET attachments[attachments.getLength()].sname = "attachment" || (attachments.getLength() USING "<<<<<<<")
|
||||
END IF
|
||||
ON ACTION delattachment
|
||||
CALL attachments.deleteElement(dialog.getCurrentRow("srattachments"))
|
||||
ON ACTION viewattachment
|
||||
CALL mbox_ok("View attachment", "View : " || attachments[dialog.getCurrentRow("srattachments")].sname, "information")
|
||||
ON ACTION close
|
||||
EXIT DIALOG
|
||||
ON ACTION send
|
||||
IF INFIELD(recipient) THEN
|
||||
# We are currently editing the recipient column
|
||||
IF LENGTH(recipients[dialog.getCurrentRow("srrecipients")].recipient) == 0 THEN
|
||||
# Empty, remove it
|
||||
# AFTER FIELD has not been executed, do it here
|
||||
CALL recipients.deleteElement(dialog.getCurrentRow("srrecipients"))
|
||||
END IF
|
||||
END IF
|
||||
IF recipients.getLength() == 0 THEN
|
||||
CALL mbox_ok("Error", "The recipient list is empty.", "stop")
|
||||
CONTINUE DIALOG
|
||||
END IF
|
||||
IF LENGTH(subject) == 0 THEN
|
||||
# Note: click on the X button returns NULL or "", I would expect the default specified, i.e. "No" here.
|
||||
IF NOT mbox_yn("Warning", "The subject line is empty.\nDo you really want to send this mail ?", "exclamation") THEN
|
||||
NEXT FIELD subject
|
||||
END IF
|
||||
END IF
|
||||
|
||||
IF NOT retrieve_attachments(attachments) THEN
|
||||
CONTINUE DIALOG
|
||||
END IF
|
||||
|
||||
CALL genmail(cnf.senderemail, recipients, subject, mailtext, attachments, mailbody, rcpt)
|
||||
|
||||
IF sendmail(rcpt, mailbody) THEN
|
||||
CALL clear_attachments(attachments)
|
||||
CALL mbox_ok("Send mail", "Mail successfully sent.", "information")
|
||||
CALL rcpt.clear()
|
||||
CALL recipients.clear()
|
||||
CALL attachments.clear()
|
||||
CALL mailbody.clear()
|
||||
LET subject = ""
|
||||
LET mailtext = ""
|
||||
NEXT FIELD recipient
|
||||
END IF
|
||||
CALL clear_attachments(attachments)
|
||||
ON ACTION savecnf
|
||||
CALL save_maildemo_cnf()
|
||||
ON ACTION chkcnf
|
||||
CALL check_maildemo_cnf()
|
||||
END DIALOG
|
||||
END MAIN
|
||||
|
||||
# Retrieve the current charset
|
||||
FUNCTION getCharset()
|
||||
DEFINE l STRING
|
||||
DEFINE c base.Channel
|
||||
|
||||
LET c = base.Channel.create()
|
||||
CALL c.openPipe("fglrun -i mbcs 2>&1", "r")
|
||||
LET l = c.readLine()
|
||||
WHILE l IS NOT NULL
|
||||
IF l MATCHES "Charmap : *" THEN
|
||||
RETURN l.subString(16,length(l))
|
||||
EXIT WHILE
|
||||
END IF
|
||||
LET l = c.readLine()
|
||||
END WHILE
|
||||
END FUNCTION
|
||||
|
||||
&define MB(array, idx, line) \
|
||||
LET array[idx] = line \
|
||||
LET idx = idx + 1
|
||||
|
||||
# Generate the mail content
|
||||
FUNCTION genmail(senderemail, recipients, subject, mailtext, attachments, mailbody, rcpt)
|
||||
DEFINE senderemail STRING
|
||||
DEFINE subject STRING
|
||||
DEFINE recipients DYNAMIC ARRAY OF RECORD mode STRING, recipient STRING END RECORD
|
||||
DEFINE mailtext STRING
|
||||
DEFINE sbmt base.StringBuffer
|
||||
DEFINE i, found INTEGER
|
||||
DEFINE rcpt DYNAMIC ARRAY OF STRING
|
||||
DEFINE attachments DYNAMIC ARRAY OF RECORD fname, sname STRING END RECORD
|
||||
DEFINE nl INTEGER
|
||||
DEFINE nrcpt INTEGER
|
||||
DEFINE nr INTEGER
|
||||
DEFINE extension STRING
|
||||
DEFINE mimeid INTEGER
|
||||
|
||||
DEFINE mailbody DYNAMIC ARRAY OF STRING
|
||||
|
||||
LET nl = 1
|
||||
LET nr = 1
|
||||
LET nrcpt = 0
|
||||
|
||||
# Add mail date
|
||||
MB(mailbody, nl, SFMT("Date: %1 %2 %3\r", TODAY USING "ddd, dd mmm yyyy", TIME(CURRENT), tz))
|
||||
|
||||
# First add headers
|
||||
MB(mailbody, nl, SFMT("From: %1\r", senderemail))
|
||||
|
||||
# Add user agent
|
||||
MB(mailbody, nl, "User-Agent: maildemo sample program.\r")
|
||||
|
||||
# Add MIME version
|
||||
MB(mailbody, nl, "MIME-Version: 1.0\r")
|
||||
|
||||
# Add To: recipients
|
||||
LET found = 0
|
||||
FOR i = 1 TO recipients.getLength()
|
||||
IF recipients[i].mode == "To" THEN
|
||||
IF found THEN
|
||||
LET mailbody[nl] = mailbody[nl].append(SFMT(", %1", recipients[i].recipient))
|
||||
ELSE
|
||||
LET mailbody[nl]=SFMT("To: %1", recipients[i].recipient)
|
||||
LET found = 1
|
||||
END IF
|
||||
MB(rcpt, nr, SFMT("RCPT TO: %1", extract_email(recipients[i].recipient)))
|
||||
END IF
|
||||
END FOR
|
||||
IF found == 1 THEN
|
||||
LET mailbody[nl] = mailbody[nl].append("\r")
|
||||
LET nl = nl + 1
|
||||
END IF
|
||||
|
||||
# Add CC: recipients
|
||||
LET found = 0
|
||||
FOR i = 1 TO recipients.getLength()
|
||||
IF recipients[i].mode == "CC" THEN
|
||||
IF found THEN
|
||||
LET mailbody[nl] = mailbody[nl].append(SFMT(", %1", recipients[i].recipient))
|
||||
ELSE
|
||||
LET mailbody[nl]=SFMT("CC: %1", recipients[i].recipient)
|
||||
LET found = 1
|
||||
END IF
|
||||
MB(rcpt, nr, SFMT("RCPT TO: %1", extract_email(recipients[i].recipient)))
|
||||
END IF
|
||||
END FOR
|
||||
IF found == 1 THEN
|
||||
LET mailbody[nl] = mailbody[nl].append("\r")
|
||||
LET nl = nl + 1
|
||||
END IF
|
||||
|
||||
# Add CC: recipients
|
||||
LET found = 0
|
||||
FOR i = 1 TO recipients.getLength()
|
||||
IF recipients[i].mode == "BCC" THEN
|
||||
IF found THEN
|
||||
LET mailbody[nl] = mailbody[nl].append(SFMT(", %1", recipients[i].recipient))
|
||||
ELSE
|
||||
LET mailbody[nl]=SFMT("BCC: %1", recipients[i].recipient)
|
||||
LET found = 1
|
||||
END IF
|
||||
MB(rcpt, nr, SFMT("RCPT TO: %1", extract_email(recipients[i].recipient)))
|
||||
END IF
|
||||
END FOR
|
||||
IF found == 1 THEN
|
||||
LET mailbody[nl] = mailbody[nl].append("\r")
|
||||
LET nl = nl + 1
|
||||
END IF
|
||||
|
||||
MB(mailbody, nl, SFMT("Subject: %1\r", subject))
|
||||
|
||||
# Now add the message content
|
||||
# Multipart message not handled
|
||||
# Should check the body is really 7bit and not line starts with ., ...
|
||||
# To be conformant with 7bit encoding. See RFC for details.
|
||||
|
||||
# \n.\n is the DATA section end mark.
|
||||
# Should be replaced by \n..\n
|
||||
LET sbmt = base.StringBuffer.create()
|
||||
CALL sbmt.append(mailtext)
|
||||
|
||||
# Need to do it twice to properly handle following case: \n.\n.\n.\n.\n.\n
|
||||
CALL sbmt.replace("\n.\n", "\n..\n", 0)
|
||||
CALL sbmt.replace("\n.\n", "\n..\n", 0)
|
||||
LET mailtext=sbmt.toString()
|
||||
IF attachments.getLength() == 0 THEN
|
||||
MB(mailbody, nl, SFMT("Content-Type: text/plain; charset=%1\r", "ISO-8859-1"))
|
||||
MB(mailbody, nl, SFMT("Content-Transfer-Encoding: %1\r", "7bit"))
|
||||
|
||||
MB(mailbody, nl, "\r")
|
||||
MB(mailbody, nl, mailtext)
|
||||
MB(mailbody, nl, "\r")
|
||||
ELSE
|
||||
MB(mailbody, nl, "Content-Type: multipart/mixed;\r")
|
||||
MB(mailbody, nl, ' boundary="' || boundary || '"\r')
|
||||
MB(mailbody, nl, "\r")
|
||||
MB(mailbody, nl, "This is a multi-part message in MIME format.\r")
|
||||
|
||||
# Add message text
|
||||
MB(mailbody, nl, "--" || boundary || "\r")
|
||||
MB(mailbody, nl, SFMT("Content-Type: text/plain; charset=%1; format=flowed\r", "ISO-8859-1"))
|
||||
MB(mailbody, nl, SFMT("Content-Transfer-Encoding: %1\r", "7bit"))
|
||||
MB(mailbody, nl, "\r")
|
||||
MB(mailbody, nl, mailtext)
|
||||
MB(mailbody, nl, '\r')
|
||||
|
||||
FOR i = 1 TO attachments.getLength()
|
||||
MB(mailbody, nl, "--" || boundary || "\r")
|
||||
|
||||
LET extension = os.Path.extension(attachments[i].fname)
|
||||
LET mimeid = getmime(extension)
|
||||
IF mimeid == -1 THEN
|
||||
# Unknown extension.
|
||||
# Default to application/octet-stream, base64 encoded
|
||||
LET mimeid = 1
|
||||
END IF
|
||||
CASE ext2mime[mimeid].encoding
|
||||
WHEN "base64"
|
||||
MB(mailbody, nl, SFMT("Content-Type: %1;\r", ext2mime[mimeid].mime))
|
||||
MB(mailbody, nl, ' name="'|| attachments[i].fname || '"\r')
|
||||
MB(mailbody, nl, 'Content-Transfer-Encoding: base64\r')
|
||||
MB(mailbody, nl, 'Content-Disposition: inline;\r')
|
||||
MB(mailbody, nl, ' filename="'|| os.Path.basename(attachments[i].fname) || '"\r')
|
||||
MB(mailbody, nl, '\r')
|
||||
|
||||
LET nl = file_to_base64(attachments[i].sname, mailbody, nl)
|
||||
WHEN "7bit"
|
||||
MB(mailbody, nl, SFMT("Content-Type: %1;\r", ext2mime[mimeid].mime))
|
||||
MB(mailbody, nl, ' name="'|| attachments[i].fname || '"\r')
|
||||
MB(mailbody, nl, 'Content-Transfer-Encoding: 7bit\r')
|
||||
MB(mailbody, nl, 'Content-Disposition: inline;\r')
|
||||
MB(mailbody, nl, ' filename="'|| os.Path.basename(attachments[i].fname) || '"\r')
|
||||
MB(mailbody, nl, '\r')
|
||||
|
||||
LET nl = file_to_7bit(attachments[i].sname, mailbody, nl)
|
||||
OTHERWISE
|
||||
CALL mbox_ok("Attachment encoding", SFMT("Unsupported encoding %1.", ext2mime[mimeid].encoding), "stop")
|
||||
EXIT PROGRAM 1
|
||||
END CASE
|
||||
END FOR
|
||||
MB(mailbody, nl, "--" || boundary || "--\r")
|
||||
END IF
|
||||
|
||||
# Add end of message marker
|
||||
MB(mailbody, nl, ".\r")
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION smtpSend(ch, command)
|
||||
DEFINE ch base.Channel
|
||||
DEFINE command STRING
|
||||
|
||||
DEFINE smtpStatus INTEGER
|
||||
DEFINE smtpMessage STRING
|
||||
|
||||
CALL clientWrite(ch, command)
|
||||
CALL readSmtpAnswer(ch) RETURNING smtpStatus, smtpMessage
|
||||
|
||||
RETURN smtpStatus, smtpMessage
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION readSmtpAnswer(ch)
|
||||
DEFINE ch base.Channel
|
||||
DEFINE line STRING
|
||||
|
||||
DEFINE smtpStatus INTEGER
|
||||
DEFINE smtpMessage STRING
|
||||
|
||||
LET smtpMessage = ""
|
||||
WHILE 1
|
||||
LET line = serverRead(ch)
|
||||
IF line IS NULL THEN
|
||||
RETURN -1, "COULD NOT READ SMTP ANSWER"
|
||||
END IF
|
||||
IF line MATCHES "[0-9][0-9][0-9] *" THEN
|
||||
IF smtpMessage.getLength() != 0 THEN
|
||||
LET smtpMessage=smtpMessage || "\n"
|
||||
END IF
|
||||
LET smtpMessage=smtpMessage.append(line.subString(4, line.getLength()))
|
||||
LET smtpStatus = line.subString(1,3)
|
||||
RETURN smtpStatus, smtpMessage
|
||||
END IF
|
||||
IF line MATCHES "[0-9][0-9][0-9]-*" THEN
|
||||
IF smtpMessage.getLength() != 0 THEN
|
||||
LET smtpMessage=smtpMessage || "\n"
|
||||
END IF
|
||||
LET smtpMessage=smtpMessage.append(line.subString(4, line.getLength()))
|
||||
END IF
|
||||
END WHILE
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION sendmail(rcpt, mailbody)
|
||||
DEFINE mailbody DYNAMIC ARRAY OF STRING
|
||||
DEFINE rcpt DYNAMIC ARRAY OF STRING
|
||||
|
||||
DEFINE smtpStatus INTEGER
|
||||
DEFINE smtpMessage STRING
|
||||
|
||||
DEFINE mc base.Channel
|
||||
DEFINE i INTEGER
|
||||
|
||||
LET smtplog = base.StringBuffer.create()
|
||||
LET mc = base.Channel.create()
|
||||
TRY
|
||||
CALL mc.openClientSocket(cnf.smtphost, cnf.smtpport, "ub", cnf.cnxtimeout)
|
||||
CATCH
|
||||
CALL mbox_ok("Socket error", "The connection to the SMTP Server failed.\n", "stop")
|
||||
RETURN FALSE
|
||||
END TRY
|
||||
CALL readSmtpAnswer(mc) RETURNING smtpStatus, smtpMessage
|
||||
|
||||
CALL smtpSend(mc, "HELO " || cnf.hostname || "\r") RETURNING smtpStatus, smtpMessage
|
||||
IF smtpStatus >= 400 THEN
|
||||
CALL smtpError(mc, smtpMessage)
|
||||
RETURN FALSE
|
||||
END IF
|
||||
CALL smtpSend(mc, SFMT("MAIL FROM: %1\r", extract_email(cnf.senderemail))) RETURNING smtpStatus, smtpMessage
|
||||
IF smtpStatus >= 400 THEN
|
||||
CALL smtpError(mc, smtpMessage)
|
||||
RETURN FALSE
|
||||
END IF
|
||||
FOR i = 1 TO rcpt.getLength()
|
||||
CALL smtpSend(mc, rcpt[i]) RETURNING smtpStatus, smtpMessage
|
||||
IF smtpStatus >= 400 THEN
|
||||
CALL smtpError(mc, smtpMessage)
|
||||
RETURN FALSE
|
||||
END IF
|
||||
END FOR
|
||||
CALL smtpSend(mc, "DATA\r") RETURNING smtpStatus, smtpMessage
|
||||
IF smtpStatus >= 400 THEN
|
||||
CALL smtpError(mc, smtpMessage)
|
||||
RETURN FALSE
|
||||
END IF
|
||||
FOR i = 1 TO mailbody.getLength()
|
||||
CALL clientWrite(mc, mailbody[i])
|
||||
END FOR
|
||||
CALL readSmtpAnswer(mc) RETURNING smtpStatus, smtpMessage
|
||||
IF smtpStatus >= 400 THEN
|
||||
CALL smtpError(mc, smtpMessage)
|
||||
RETURN FALSE
|
||||
END IF
|
||||
CALL smtpSend(mc, "QUIT\r") RETURNING smtpStatus, smtpMessage
|
||||
IF smtpStatus >= 400 THEN
|
||||
CALL smtpError(mc, smtpMessage)
|
||||
RETURN FALSE
|
||||
END IF
|
||||
CALL mc.close()
|
||||
RETURN TRUE
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION clientWrite(mc, line)
|
||||
DEFINE mc base.Channel
|
||||
DEFINE line STRING
|
||||
CALL smtplog.append('<FONT COLOR="#339933"><B>C> </B>')
|
||||
CALL smtplog.append(line)
|
||||
CALL smtplog.append("<BR></FONT>")
|
||||
LET logtext=smtplog.toString()
|
||||
CALL mc.writeLine(line)
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION serverRead(mc)
|
||||
DEFINE line STRING
|
||||
DEFINE mc base.Channel
|
||||
LET line = mc.readLine()
|
||||
CALL smtplog.append("<FONT COLOR=red><B>S> </B>")
|
||||
CALL smtplog.append(line)
|
||||
CALL smtplog.append("<BR></FONT>")
|
||||
LET logtext=smtplog.toString()
|
||||
RETURN line
|
||||
END FUNCTION
|
||||
|
||||
# Returns the mime entry index for the extension `ext'
|
||||
# -1 if not entry matching the extension is found.
|
||||
FUNCTION getmime(ext)
|
||||
DEFINE ext STRING
|
||||
DEFINE j INTEGER
|
||||
FOR j = 1 TO ext2mime.getLength()
|
||||
IF ext2mime[j].ext == ext THEN
|
||||
RETURN j
|
||||
END IF
|
||||
END FOR
|
||||
RETURN -1
|
||||
END FUNCTION
|
||||
|
||||
# Save configuration
|
||||
FUNCTION save_maildemo_cnf()
|
||||
DEFINE pch base.Channel
|
||||
DEFINE path STRING
|
||||
LET path = os.Path.join(os.Path.homedir(), "maildemo.cnf")
|
||||
IF os.Path.exists(path) THEN
|
||||
IF NOT os.Path.writable(path) THEN
|
||||
CALL mbox_ok("Write configuration", SFMT("The file %1 can not be written", path), "stop")
|
||||
RETURN
|
||||
END IF
|
||||
END IF
|
||||
|
||||
TRY
|
||||
LET pch=base.channel.create()
|
||||
CALL pch.openFile(path, "w")
|
||||
CALL pch.write([cnf.*])
|
||||
CALL pch.close()
|
||||
CATCH
|
||||
CALL mbox_ok("Write configuration", SFMT("Failed to save the configuration in the file %1", path), "stop")
|
||||
RETURN
|
||||
END TRY
|
||||
CALL mbox_ok("Save configuration", SFMT("The configuration was successfully saved in %1.", path), "information")
|
||||
END FUNCTION
|
||||
|
||||
# Load configuration
|
||||
FUNCTION load_maildemo_cnf()
|
||||
DEFINE pch base.Channel
|
||||
DEFINE path STRING
|
||||
|
||||
LET path = os.Path.join(os.Path.homedir(), "maildemo.cnf")
|
||||
IF NOT os.Path.exists(path) THEN
|
||||
CALL default_maildemo_cnf()
|
||||
RETURN
|
||||
END IF
|
||||
IF NOT os.Path.readable(path) THEN
|
||||
CALL default_maildemo_cnf()
|
||||
RETURN
|
||||
END IF
|
||||
|
||||
# Read save parameters
|
||||
LET pch = base.channel.create()
|
||||
CALL pch.openFile(path,"r")
|
||||
LET rv = pch.read([cnf.*])
|
||||
CALL pch.close()
|
||||
IF rv THEN
|
||||
RETURN
|
||||
END IF
|
||||
CALL default_maildemo_cnf()
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION default_maildemo_cnf()
|
||||
LET cnf.smtpPort = 25
|
||||
LET cnf.smtpHost = ""
|
||||
LET cnf.senderEmail = ""
|
||||
LET cnf.hostname = ""
|
||||
LET cnf.cnxtimeout = 10
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION file_to_base64(fname, mailbody, nl)
|
||||
DEFINE mailbody DYNAMIC ARRAY OF STRING
|
||||
DEFINE fname STRING
|
||||
DEFINE nl INTEGER
|
||||
DEFINE l BYTE
|
||||
DEFINE lfn STRING
|
||||
DEFINE doc om.DomDocument
|
||||
DEFINE node om.DomNode
|
||||
DEFINE lnode om.NodeList
|
||||
|
||||
LET lfn = fname || ".xml"
|
||||
|
||||
LOCATE l IN FILE fname
|
||||
CALL fgl_report_set_document_handler(om.XmlWriter.createFileWriter(lfn))
|
||||
START REPORT gen_base64 TO FILE "dummy_report_file"
|
||||
OUTPUT TO REPORT gen_base64(l)
|
||||
FINISH REPORT gen_base64
|
||||
|
||||
# Load the generated XML file
|
||||
LET doc = om.DomDocument.createFromXmlFile(lfn)
|
||||
LET node = doc.getDocumentElement()
|
||||
LET lnode = node.selectByTagName("Item")
|
||||
LET node = lnode.item(1)
|
||||
MB(mailbody, nl, node.getAttribute("value"))
|
||||
IF NOT os.Path.delete(lfn) THEN
|
||||
CALL mbox_ok("Warning", SFMT("The temporary file %1 could not be removed.", lfn), "stop")
|
||||
END IF
|
||||
RETURN nl
|
||||
END FUNCTION
|
||||
|
||||
# Convert a file to a base64 string
|
||||
REPORT gen_base64(l)
|
||||
DEFINE l BYTE
|
||||
OUTPUT
|
||||
PAGE LENGTH 1
|
||||
TOP MARGIN 0
|
||||
BOTTOM MARGIN 0
|
||||
LEFT MARGIN 0
|
||||
FORMAT
|
||||
ON EVERY ROW
|
||||
PRINTX name=base64_l l
|
||||
END REPORT
|
||||
|
||||
# Convert a file to 7bit encoding (fake)
|
||||
FUNCTION file_to_7bit(fname, mailbody, nl)
|
||||
DEFINE fname STRING
|
||||
DEFINE mailbody DYNAMIC ARRAY OF STRING
|
||||
DEFINE ac base.Channel
|
||||
DEFINE line STRING
|
||||
DEFINE nl INTEGER
|
||||
|
||||
LET ac = base.Channel.create()
|
||||
CALL ac.openFile(fname, "rb")
|
||||
WHILE NOT ac.isEof()
|
||||
LET line = ac.readLine()
|
||||
MB(mailbody, nl, line)
|
||||
END WHILE
|
||||
CALL ac.close()
|
||||
RETURN nl
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION check_maildemo_cnf()
|
||||
DEFINE smtpStatus INTEGER
|
||||
DEFINE smtpMessage STRING
|
||||
DEFINE mc base.Channel
|
||||
|
||||
LET smtplog = base.StringBuffer.create()
|
||||
LET mc = base.Channel.create()
|
||||
TRY
|
||||
CALL mc.openClientSocket(cnf.smtphost, cnf.smtpport, "ub", cnf.cnxtimeout)
|
||||
CATCH
|
||||
CALL mbox_ok("Socket error", "The connection to the SMTP Server failed.\n", "stop")
|
||||
RETURN
|
||||
END TRY
|
||||
CALL readSmtpAnswer(mc) RETURNING smtpStatus, smtpMessage
|
||||
|
||||
CALL smtpSend(mc, "HELO " || cnf.hostname || "\r") RETURNING smtpStatus, smtpMessage
|
||||
IF smtpStatus >= 400 THEN
|
||||
CALL smtpError(mc, smtpMessage)
|
||||
RETURN
|
||||
END IF
|
||||
CALL smtpSend(mc, "QUIT\r") RETURNING smtpStatus, smtpMessage
|
||||
IF smtpStatus >= 400 THEN
|
||||
CALL smtpError(mc, smtpMessage)
|
||||
RETURN
|
||||
END IF
|
||||
CALL mc.close()
|
||||
CALL mbox_ok("SMTP Connection", "The SMTP parameters are correct", "information")
|
||||
RETURN
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION smtpError(mc, msg)
|
||||
DEFINE msg STRING
|
||||
DEFINE mc base.Channel
|
||||
DEFINE smtpStatus INTEGER
|
||||
DEFINE smtpMessage STRING
|
||||
|
||||
CALL mbox_ok("SMTP Error", msg, "stop")
|
||||
CALL smtpSend(mc, "QUIT\r") RETURNING smtpStatus, smtpMessage
|
||||
CALL mc.close()
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION getProgramCmd()
|
||||
DEFINE cmd STRING
|
||||
LET cmd = os.Path.join(fgl_getenv("FGLDIR"), "demo")
|
||||
LET cmd = os.Path.join(cmd, "MultipleDialogs")
|
||||
LET cmd = os.Path.join(cmd, "Mailer.42m")
|
||||
IF NOT os.Path.exists(cmd) THEN
|
||||
LET cmd = os.Path.join(".", "Mailer.42m")
|
||||
IF NOT os.Path.exists(cmd) THEN
|
||||
DISPLAY "ERROR: Could not find Mailer.42m program file."
|
||||
EXIT PROGRAM 1
|
||||
END IF
|
||||
END IF
|
||||
IF fgl_getenv("WINDIR") THEN
|
||||
LET cmd = "\"" || cmd || "\""
|
||||
END IF
|
||||
LET cmd = SFMT("fglrun %1 ", cmd)
|
||||
RETURN cmd
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION showTZ()
|
||||
CALL FGL_SETENV("FGLGUI","0")
|
||||
RUN getProgramCmd() || "DTSHOW"
|
||||
CALL FGL_SETENV("FGLGUI","1")
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION detect_timezone()
|
||||
DEFINE ch base.Channel
|
||||
DEFINE dnow, dgmt DATETIME YEAR TO MINUTE
|
||||
DEFINE dh INTERVAL MINUTE(5) TO MINUTE
|
||||
DEFINE sminutes STRING
|
||||
DEFINE iminutes INTEGER
|
||||
DEFINE ihours INTEGER
|
||||
|
||||
CALL FGL_SETENV("FGLGUI","0")
|
||||
|
||||
LET ch = base.Channel.create()
|
||||
CALL ch.openPipe(getProgramCmd()||" TZLOCAL", "r")
|
||||
LET rv=ch.read(dnow)
|
||||
CALL ch.close()
|
||||
|
||||
CALL ch.openPipe(getProgramCmd()||" TZGMT", "r")
|
||||
LET rv=ch.read(dgmt)
|
||||
CALL ch.close()
|
||||
|
||||
LET dh = dnow - dgmt
|
||||
LET sminutes = dh
|
||||
LET iminutes = sminutes
|
||||
IF iminutes MOD 2 == 1 THEN
|
||||
LET iminutes = iminutes + 1
|
||||
END IF
|
||||
LET ihours = iminutes / 60
|
||||
LET iminutes = iminutes - ihours * 60
|
||||
|
||||
CALL FGL_SETENV("FGLGUI","1")
|
||||
|
||||
RETURN SFMT("%1%2", ihours USING "+&&", iminutes USING "&&")
|
||||
END FUNCTION
|
||||
|
||||
{
|
||||
Retrieves attachments from the client.
|
||||
If many attachments should be retrieved, maybe a progress bar could be displayed
|
||||
}
|
||||
FUNCTION retrieve_attachments(attachments)
|
||||
DEFINE attachments DYNAMIC ARRAY OF RECORD fname, sname STRING END RECORD
|
||||
DEFINE i, j INTEGER
|
||||
|
||||
FOR i = 1 TO attachments.getLength()
|
||||
DISPLAY "Retrieving attachment : ", attachments[i].fname, " TO ", attachments[i].sname
|
||||
TRY
|
||||
CALL fgl_getfile(attachments[i].fname, attachments[i].sname)
|
||||
CATCH
|
||||
CALL mbox_ok("Attachment download", SFMT("Could not retrieve attachment %1", attachments[i].fname), "stop")
|
||||
FOR j = 1 TO i
|
||||
LET rv = os.Path.delete(attachments[j].sname)
|
||||
END FOR
|
||||
RETURN FALSE
|
||||
END TRY
|
||||
END FOR
|
||||
RETURN TRUE
|
||||
END FUNCTION
|
||||
|
||||
# Deletes temporary attachment files
|
||||
FUNCTION clear_attachments(attachments)
|
||||
DEFINE attachments DYNAMIC ARRAY OF RECORD fname, sname STRING END RECORD
|
||||
DEFINE i INTEGER
|
||||
|
||||
FOR i = 1 TO attachments.getLength()
|
||||
LET rv = os.Path.delete(attachments[i].sname)
|
||||
END FOR
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION extract_email(email)
|
||||
DEFINE email STRING
|
||||
DEFINE s, e INTEGER
|
||||
|
||||
LET s = email.getIndexOf("<", 1)
|
||||
LET e = email.getIndexOf(">", 1)
|
||||
IF s == 0 OR e == 0 OR s >= e THEN
|
||||
RETURN email
|
||||
END IF
|
||||
RETURN email.subString(s + 1, e - 1)
|
||||
END FUNCTION
|
||||
@@ -0,0 +1,124 @@
|
||||
# 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 send (TEXT="Send", IMAGE="mail", ACCELERATOR=CONTROL-RETURN)
|
||||
ACTION attachfile (TEXT="Attach file", IMAGE="plus", ACCELERATOR=CONTROL-J)
|
||||
ACTION close (IMAGE="quit", VALIDATE = NO, DEFAULTVIEW=YES)
|
||||
ACTION viewattachment (DEFAULTVIEW=NO)
|
||||
END
|
||||
|
||||
LAYOUT(TEXT="Send EMAIL")
|
||||
FOLDER
|
||||
PAGE p1(TEXT="Account settings")
|
||||
GRID
|
||||
{
|
||||
[lhostname ][hostname ]
|
||||
[lsmtphost ][smtphost ]
|
||||
[lsmtpport ][smtpport]
|
||||
[lsenderem ][senderemail ]
|
||||
[lcnxtout ][cnxtimeout] (-1 = INFINITE TIMEOUT)
|
||||
[chkcnf ]
|
||||
[savecnf ]
|
||||
}
|
||||
END
|
||||
END
|
||||
PAGE p2(TEXT="Mail compose")
|
||||
VBOX(splitter)
|
||||
HBOX(splitter)
|
||||
GROUP(TEXT="Recipients")
|
||||
GRID
|
||||
{
|
||||
<T recipients >[a]
|
||||
[mode|recipient ][i]
|
||||
[mode|recipient ][d]
|
||||
[mode|recipient ]
|
||||
[mode|recipient ]
|
||||
}
|
||||
END
|
||||
END
|
||||
GROUP(TEXT="Attachments")
|
||||
GRID
|
||||
{
|
||||
<T attachments >
|
||||
[sname |fname ]
|
||||
[ :add:rm]
|
||||
}
|
||||
END
|
||||
END
|
||||
END
|
||||
GROUP mc(TEXT="Compose mail")
|
||||
GRID
|
||||
{
|
||||
[s|subject ]
|
||||
[mailtext ]
|
||||
[ ]
|
||||
[ ]
|
||||
[ ]
|
||||
[send]
|
||||
}
|
||||
END
|
||||
END
|
||||
END
|
||||
END
|
||||
PAGE p3(TEXT="SMTP Dialog log")
|
||||
GRID
|
||||
{
|
||||
[smtplog ]
|
||||
[ ]
|
||||
[ ]
|
||||
[ ]
|
||||
}
|
||||
END
|
||||
END
|
||||
END
|
||||
|
||||
ATTRIBUTES
|
||||
# Configuration
|
||||
# smtphost should be a simple EDIT field, but EDIT does not support STRETCH attribute.
|
||||
TEXTEDIT hostname=formonly.hostname, STRETCH=X, SCROLLBARS=NONE, WANTNORETURNS;
|
||||
TEXTEDIT smtphost=formonly.smtphost, STRETCH=X, SCROLLBARS=NONE, WANTNORETURNS;
|
||||
TABLE recipients: UNHIDABLECOLUMNS, UNMOVABLECOLUMNS;
|
||||
EDIT smtpport=formonly.smtpport;
|
||||
EDIT senderemail=formonly.senderemail, SCROLL;
|
||||
EDIT cnxtimeout=formonly.cnxtimeout;
|
||||
BUTTON savecnf:savecnf, TEXT="Save", IMAGE="disk";
|
||||
BUTTON chkcnf:chkcnf, TEXT="Check", IMAGE="refresh";
|
||||
BUTTON a: srrecipients.append, IMAGE = "listadd", TEXT="";
|
||||
BUTTON i: srrecipients.insert, IMAGE = "listins", TEXT="";
|
||||
BUTTON d: srrecipients.delete, IMAGE = "listdel", TEXT="";
|
||||
LABEL lhostname:TEXT="Hostname";
|
||||
LABEL lsmtphost:TEXT="Smtp server";
|
||||
LABEL lsmtpport:TEXT="Smtp port";
|
||||
LABEL lsenderem:TEXT="Email";
|
||||
LABEL lcnxtout:TEXT="Connection timeout";
|
||||
|
||||
# To
|
||||
COMBOBOX mode=formonly.mode, ITEMS=("To", "CC", "BCC"), TITLE="To";
|
||||
EDIT recipient=formonly.recipient, TITLE="Email";
|
||||
|
||||
# Subject and mail body
|
||||
LABEL s: TEXT="Subject:";
|
||||
EDIT subject=formonly.subject, SCROLL;
|
||||
TEXTEDIT mailtext=formonly.mailtext, STRETCH=BOTH;
|
||||
BUTTON send:send, TEXT="Send", IMAGE="mail";
|
||||
|
||||
# Attachments
|
||||
TABLE attachments: DOUBLECLICK=viewattachment;
|
||||
EDIT fname=formonly.fname, TITLE="Filename";
|
||||
EDIT sname=formonly.sname, HIDDEN=USER, TITLE="Local Filename";
|
||||
BUTTON rm: delattachment, TEXT="Remove", IMAGE="eraser";
|
||||
BUTTON add: attachfile, TEXT="Attach", IMAGE="plus";
|
||||
|
||||
# SMTP dialog log
|
||||
TEXTEDIT smtplog=formonly.logtext, STRETCH=BOTH;
|
||||
|
||||
INSTRUCTIONS
|
||||
SCREEN RECORD srrecipients(formonly.mode, formonly.recipient);
|
||||
SCREEN RECORD srattachments(formonly.fname, formonly.sname);
|
||||
@@ -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.
|
||||
|
||||
|
||||
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,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.
|
||||
@@ -0,0 +1,113 @@
|
||||
# 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 Styles
|
||||
IMPORT FGL CityList
|
||||
|
||||
DEFINE custarr DYNAMIC ARRAY OF RECORD
|
||||
c_id INTEGER,
|
||||
c_name VARCHAR(50),
|
||||
c_ts DATETIME YEAR TO SECOND
|
||||
END RECORD
|
||||
|
||||
DEFINE custcrit RECORD
|
||||
cust_id STRING,
|
||||
cust_name STRING,
|
||||
cust_state STRING,
|
||||
cust_city STRING,
|
||||
cust_zipcode STRING
|
||||
END RECORD
|
||||
|
||||
MAIN
|
||||
DEFINE s INTEGER
|
||||
DEFINE where_clause, name, state STRING
|
||||
DEFINE first_rows SMALLINT
|
||||
DEFINE first_rows_count INTEGER
|
||||
|
||||
OPTIONS INPUT WRAP
|
||||
|
||||
OPEN FORM f1 FROM "QueryCustomers"
|
||||
DISPLAY FORM f1
|
||||
|
||||
CALL addCommonStyles()
|
||||
CALL addStyleList1()
|
||||
|
||||
DIALOG ATTRIBUTES(FIELD ORDER FORM, UNBUFFERED)
|
||||
|
||||
INPUT BY NAME first_rows, first_rows_count
|
||||
ON CHANGE first_rows
|
||||
CALL DIALOG.setFieldActive("first_rows_count",first_rows)
|
||||
END INPUT
|
||||
|
||||
CONSTRUCT BY NAME where_clause
|
||||
ON cust_id,
|
||||
cust_name,
|
||||
cust_state,
|
||||
cust_city,
|
||||
cust_zipcode
|
||||
AFTER FIELD cust_id,
|
||||
cust_name,
|
||||
cust_state,
|
||||
cust_city,
|
||||
cust_zipcode
|
||||
DISPLAY BY NAME where_clause
|
||||
END CONSTRUCT
|
||||
|
||||
DISPLAY ARRAY custarr TO sr.*
|
||||
BEFORE ROW
|
||||
MESSAGE SFMT("Row: %1/%2", DIALOG.getCurrentRow("sr"), DIALOG.getArrayLength("sr"))
|
||||
END DISPLAY
|
||||
|
||||
BEFORE DIALOG
|
||||
LET first_rows = FALSE
|
||||
CALL DIALOG.setFieldActive("first_rows_count",FALSE)
|
||||
|
||||
ON ACTION zoom_city
|
||||
CALL select_city(DIALOG.getFieldBuffer("cust_city")) RETURNING s, name, state
|
||||
IF s==0 THEN
|
||||
DISPLAY name, state TO cust_city, cust_state
|
||||
END IF
|
||||
|
||||
-- Save/Load criterias: would be nice to have DIALOG.setFieldBuffer
|
||||
-- to make it generic.
|
||||
ON ACTION clear_criterias
|
||||
CLEAR cust_id, cust_name, cust_state, cust_city, cust_zipcode
|
||||
LET where_clause = NULL
|
||||
DISPLAY BY NAME where_clause
|
||||
ON ACTION load_criterias
|
||||
DISPLAY BY NAME custcrit.*
|
||||
ON ACTION save_criterias
|
||||
CALL get_fldbuf( cust_id, cust_name, cust_state, cust_city, cust_zipcode )
|
||||
RETURNING custcrit.*
|
||||
|
||||
ON ACTION fetch
|
||||
DISPLAY BY NAME where_clause
|
||||
CALL cust_loadlist(300 / LENGTH(where_clause))
|
||||
|
||||
ON ACTION clear_list
|
||||
CALL cust_loadlist(0)
|
||||
|
||||
ON ACTION close
|
||||
EXIT DIALOG
|
||||
|
||||
END DIALOG
|
||||
|
||||
END MAIN
|
||||
|
||||
-- Dummy function to fill the list of customers (normally, SELECT from database)
|
||||
FUNCTION cust_loadlist(m)
|
||||
DEFINE m,i INTEGER
|
||||
CALL custarr.clear()
|
||||
FOR i=1 TO m
|
||||
CALL custarr.appendElement()
|
||||
LET custarr[i].c_id = i
|
||||
LET custarr[i].c_name = "Name " || i
|
||||
LET custarr[i].c_ts = CURRENT
|
||||
END FOR
|
||||
END FUNCTION
|
||||
@@ -0,0 +1,83 @@
|
||||
# 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 clear_criterias ( VALIDATE = NO )
|
||||
ACTION clear_list ( TEXT = "Clear list" )
|
||||
ACTION close ( VALIDATE = NO )
|
||||
END
|
||||
|
||||
LAYOUT ( TEXT = "Query customers" )
|
||||
GRID
|
||||
{
|
||||
<g g1 >
|
||||
[fr ] Count: [frc ]
|
||||
< >
|
||||
<g g2 >
|
||||
Id: [f1 ] Name: [f2 ]
|
||||
State: [f3 ]
|
||||
City: [f4 ]
|
||||
Zipcode: [f5 ]
|
||||
[cr :ld :sv :sr : ]
|
||||
SQL Condition:
|
||||
[sql ]
|
||||
[ ]
|
||||
< >
|
||||
<g g3 >
|
||||
<t t1 >
|
||||
Id Name Timestamp
|
||||
[c1 |c2 |c3 ]
|
||||
[c1 |c2 |c3 ]
|
||||
[c1 |c2 |c3 ]
|
||||
[c1 |c2 |c3 ]
|
||||
[c1 |c2 |c3 ]
|
||||
[c1 |c2 |c3 ]
|
||||
[c1 |c2 |c3 ]
|
||||
[c1 |c2 |c3 ]
|
||||
< >
|
||||
< >
|
||||
[el : :cw ]
|
||||
}
|
||||
END
|
||||
END
|
||||
|
||||
ATTRIBUTES
|
||||
|
||||
GROUP g1: TEXT = "Search options";
|
||||
CHECKBOX fr = FORMONLY.first_rows TYPE SMALLINT, NOT NULL, TEXT="Select first N rows";
|
||||
EDIT frc= FORMONLY.first_rows_count, NOT NULL;
|
||||
|
||||
GROUP g2: TEXT = "Search criterias";
|
||||
EDIT f1 = FORMONLY.cust_id TYPE INTEGER;
|
||||
EDIT f2 = FORMONLY.cust_name TYPE VARCHAR;
|
||||
COMBOBOX f3 = FORMONLY.cust_state TYPE VARCHAR,
|
||||
QUERYEDITABLE, DEFAULT="CA", INITIALIZER=combo_fill_states;
|
||||
BUTTONEDIT f4 = FORMONLY.cust_city TYPE CHAR, ACTION=zoom_city, IMAGE="find";
|
||||
EDIT f5 = FORMONLY.cust_zipcode TYPE VARCHAR;
|
||||
|
||||
GROUP g3: TEXT = "Customer list";
|
||||
EDIT c1 = FORMONLY.c_id TYPE INTEGER;
|
||||
EDIT c2 = FORMONLY.c_name TYPE VARCHAR;
|
||||
DATEEDIT c3 = FORMONLY.c_ts TYPE DATETIME YEAR TO SECOND;
|
||||
|
||||
BUTTON cr : clear_criterias, TEXT="Clear";
|
||||
BUTTON ld : load_criterias, TEXT="Load";
|
||||
BUTTON sv : save_criterias, TEXT="Save";
|
||||
BUTTON sr : fetch, TEXT="Fetch";
|
||||
|
||||
TEXTEDIT sql = FORMONLY.where_clause, STRETCH=X;
|
||||
|
||||
BUTTON el : clear_list;
|
||||
BUTTON cw : close;
|
||||
|
||||
END
|
||||
|
||||
INSTRUCTIONS
|
||||
SCREEN RECORD sr (FORMONLY.c_id THROUGH FORMONLY.c_ts);
|
||||
END
|
||||
@@ -0,0 +1,8 @@
|
||||
Multiple Dialogs Demos
|
||||
----------------------
|
||||
|
||||
Multiple Dialogs is the main feature of version 2.10.
|
||||
The DIALOG instruction acts as a collection of singular dialogs which work in parallel.
|
||||
|
||||
You will find here some small demos showing how Multiple Dialogs work, mixing singular dialogs.
|
||||
The Simple Mailer demo is a more complete application using Multiple Dialogs.
|
||||
@@ -0,0 +1,50 @@
|
||||
# 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 addCommonStyles()
|
||||
DEFINE r, sl, s, a om.DomNode
|
||||
DEFINE nl om.NodeList
|
||||
LET r = ui.Interface.getRootNode()
|
||||
LET nl = r.selectByTagName("StyleList")
|
||||
LET sl = nl.item(1)
|
||||
LET s = sl.createChild("Style")
|
||||
CALL s.setAttribute("name", ":query")
|
||||
CALL sl.appendChild(s)
|
||||
LET a = s.createChild("StyleAttribute")
|
||||
CALL s.appendChild(a)
|
||||
CALL a.setAttribute("name", "backgroundColor")
|
||||
CALL a.setAttribute("value", "yellow")
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION addStyleList1()
|
||||
DEFINE r, sl, s, a om.DomNode
|
||||
DEFINE nl om.NodeList
|
||||
LET r = ui.Interface.getRootNode()
|
||||
LET nl = r.selectByTagName("StyleList")
|
||||
LET sl = nl.item(1)
|
||||
|
||||
LET s = sl.createChild("Style")
|
||||
CALL s.setAttribute("name", ":odd")
|
||||
CALL sl.appendChild(s)
|
||||
LET a = s.createChild("StyleAttribute")
|
||||
CALL s.appendChild(a)
|
||||
CALL a.setAttribute("name", "backgroundColor")
|
||||
CALL a.setAttribute("value", "#FFFFDD")
|
||||
|
||||
LET s = sl.createChild("Style")
|
||||
CALL s.setAttribute("name", ":even")
|
||||
CALL sl.appendChild(s)
|
||||
LET a = s.createChild("StyleAttribute")
|
||||
CALL s.appendChild(a)
|
||||
CALL a.setAttribute("name", "backgroundColor")
|
||||
CALL a.setAttribute("value", "#EEFFFF")
|
||||
|
||||
END FUNCTION
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
# 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.
|
||||
|
||||
MAIN
|
||||
DEFINE a1 DYNAMIC ARRAY OF RECORD
|
||||
col11 INTEGER,
|
||||
col12 CHAR(10)
|
||||
END RECORD
|
||||
DEFINE a2 DYNAMIC ARRAY OF RECORD
|
||||
col21 INTEGER,
|
||||
col22 CHAR(10)
|
||||
END RECORD
|
||||
DEFINE field1 INTEGER
|
||||
DEFINE field2, field3, field4 CHAR(50)
|
||||
|
||||
OPTIONS INPUT WRAP
|
||||
|
||||
OPEN FORM f FROM "SubBlockActions"
|
||||
DISPLAY FORM f
|
||||
|
||||
LET field1 = 123
|
||||
LET field2 = "aaaa"
|
||||
LET field4 = "xxxx"
|
||||
|
||||
LET a1[1].col11 = 1
|
||||
LET a1[2].col11 = 2
|
||||
LET a1[3].col11 = 3
|
||||
|
||||
LET a2[1].col21 = 1
|
||||
LET a2[2].col21 = 2
|
||||
|
||||
DIALOG ATTRIBUTES(UNBUFFERED)
|
||||
|
||||
INPUT BY NAME field1,field2,field3 ATTRIBUTES(NAME="input1", WITHOUT DEFAULTS)
|
||||
ON ACTION foo
|
||||
ERROR "Action foo of INPUT input1 sub-block"
|
||||
ON ACTION bar
|
||||
ERROR "Action bar of INPUT input1 sub-block"
|
||||
END INPUT
|
||||
|
||||
INPUT ARRAY a1 FROM sr1.* ATTRIBUTES (WITHOUT DEFAULTS)
|
||||
ON ACTION foo
|
||||
ERROR "Action foo of INPUT ARRAY sr1 sub-block"
|
||||
END INPUT
|
||||
|
||||
INPUT ARRAY a2 FROM sr2.* ATTRIBUTES (WITHOUT DEFAULTS)
|
||||
ON ACTION foo
|
||||
ERROR "Action foo of INPUT ARRAY sr2 sub-block"
|
||||
END INPUT
|
||||
|
||||
INPUT BY NAME field4 ATTRIBUTES(NAME="input2", WITHOUT DEFAULTS)
|
||||
END INPUT
|
||||
|
||||
ON ACTION disable_input1_foo
|
||||
CALL dialog.setActionActive("input1.foo",FALSE)
|
||||
ON ACTION enable_input1_foo
|
||||
CALL dialog.setActionActive("input1.foo",TRUE)
|
||||
ON ACTION disable_input1_bar
|
||||
CALL dialog.setActionActive("input1.bar",FALSE)
|
||||
ON ACTION enable_input1_bar
|
||||
CALL dialog.setActionActive("input1.bar",TRUE)
|
||||
|
||||
ON ACTION disable_actions_sr1
|
||||
CALL list_actions(DIALOG,"sr1", FALSE)
|
||||
ON ACTION enable_actions_sr1
|
||||
CALL list_actions(DIALOG,"sr1", TRUE)
|
||||
|
||||
ON ACTION disable_actions_sr2
|
||||
CALL list_actions(DIALOG,"sr2", FALSE)
|
||||
ON ACTION enable_actions_sr2
|
||||
CALL list_actions(DIALOG,"sr2", TRUE)
|
||||
|
||||
ON ACTION close EXIT DIALOG
|
||||
|
||||
END DIALOG
|
||||
|
||||
END MAIN
|
||||
|
||||
FUNCTION list_actions(d, name, activate)
|
||||
DEFINE d ui.Dialog, name STRING, activate INTEGER
|
||||
CALL d.setActionActive(name||".insert",activate)
|
||||
CALL d.setActionActive(name||".append",activate)
|
||||
CALL d.setActionActive(name||".delete",activate)
|
||||
CALL d.setActionActive(name||".foo",activate)
|
||||
END FUNCTION
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
# 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", TEXT="Ins" )
|
||||
ACTION append ( IMAGE="listadd", TEXT="App" )
|
||||
ACTION delete ( IMAGE="listdel", TEXT="Del" )
|
||||
ACTION foo ( IMAGE="smiley", TEXT="Foo" )
|
||||
ACTION bar ( IMAGE="ssmiley", TEXT="Bar" )
|
||||
ACTION close ( VALIDATE = NO, DEFAULTVIEW=YES )
|
||||
end
|
||||
|
||||
TOPMENU
|
||||
GROUP global (TEXT="global")
|
||||
COMMAND foo (COMMENT="Bound with the name 'foo'")
|
||||
COMMAND bar (COMMENT="Bound with the name 'bar'")
|
||||
END
|
||||
GROUP input1 (TEXT="Input1")
|
||||
COMMAND input1.foo (TEXT="input1.foo", COMMENT="Bound with the name 'input1.foo'")
|
||||
COMMAND input1.bar (TEXT="input1.bar", COMMENT="Bound with the name 'input1.bar'")
|
||||
END
|
||||
GROUP sr1 (TEXT="List1")
|
||||
COMMAND sr1.insert (TEXT="sr1.insert", COMMENT="Bound with the name 'sr1.insert'")
|
||||
COMMAND sr1.append (TEXT="sr1.append", COMMENT="Bound with the name 'sr1.append'")
|
||||
COMMAND sr1.delete (TEXT="sr1.delete", COMMENT="Bound with the name 'sr1.delete'")
|
||||
COMMAND sr1.foo (TEXT="sr1.foo", COMMENT="Bound with the name 'sr1.foo'")
|
||||
END
|
||||
GROUP sr2 (TEXT="List2")
|
||||
COMMAND sr2.insert (TEXT="sr2.insert", COMMENT="Bound with the name 'sr2.insert'")
|
||||
COMMAND sr2.append (TEXT="sr2.append", COMMENT="Bound with the name 'sr2.append'")
|
||||
COMMAND sr2.delete (TEXT="sr2.delete", COMMENT="Bound with the name 'sr2.delete'")
|
||||
COMMAND sr2.foo (TEXT="sr2.foo", COMMENT="Bound with the name 'sr2.foo'")
|
||||
END
|
||||
GROUP bothlists (TEXT="Both Lists")
|
||||
COMMAND insert (TEXT="insert", COMMENT="Bound with the name 'insert'")
|
||||
COMMAND append (TEXT="append", COMMENT="Bound with the name 'append'")
|
||||
COMMAND delete (TEXT="delete", COMMENT="Bound with the name 'delete'")
|
||||
SEPARATOR
|
||||
COMMAND firstrow (COMMENT="Bound with the name 'firstrow'")
|
||||
COMMAND prevrow (COMMENT="Bound with the name 'prevrow'")
|
||||
COMMAND nextrow (COMMENT="Bound with the name 'nextrow'")
|
||||
COMMAND lastrow (COMMENT="Bound with the name 'lastrow'")
|
||||
END
|
||||
END
|
||||
|
||||
TOOLBAR
|
||||
ITEM insert (COMMENT="Bound with the name 'insert'")
|
||||
ITEM append (COMMENT="Bound with the name 'append'")
|
||||
ITEM delete (COMMENT="Bound with the name 'delete'")
|
||||
SEPARATOR
|
||||
ITEM firstrow (COMMENT="Bound with the name 'firstrow'")
|
||||
ITEM prevrow (COMMENT="Bound with the name 'prevrow'")
|
||||
ITEM nextrow (COMMENT="Bound with the name 'nextrow'")
|
||||
ITEM lastrow (COMMENT="Bound with the name 'lastrow'")
|
||||
SEPARATOR
|
||||
ITEM foo (COMMENT="Bound with the name 'foo'")
|
||||
ITEM bar (COMMENT="Bound with the name 'bar'")
|
||||
END
|
||||
|
||||
LAYOUT
|
||||
VBOX
|
||||
GRID
|
||||
{
|
||||
<g g1 >
|
||||
This group defines foo, bar actions
|
||||
[f1 ]
|
||||
[f2 ]
|
||||
[f3 ]
|
||||
[in_foo|in_bar]
|
||||
< >
|
||||
<g g2 >
|
||||
This group defines insert,append,delete,foo
|
||||
<t >
|
||||
[c11 |c12 ][t1_ins]
|
||||
[c11 |c12 ][t1_app]
|
||||
[c11 |c12 ][t1_del]
|
||||
[c11 |c12 ][t1_foo]
|
||||
[c11 |c12 ]
|
||||
< >
|
||||
< >
|
||||
<g g3 >
|
||||
This group defines insert,append,delete,foo
|
||||
<t >
|
||||
[c21 |c22 ][t2_ins]
|
||||
[c21 |c22 ][t2_app]
|
||||
[c21 |c22 ][t2_del]
|
||||
[c21 |c22 ][t2_foo]
|
||||
[c21 |c22 ]
|
||||
< >
|
||||
< >
|
||||
<g g4 >
|
||||
This group defines no actions
|
||||
[f4 ]
|
||||
< >
|
||||
}
|
||||
END
|
||||
END
|
||||
END
|
||||
|
||||
ATTRIBUTES
|
||||
|
||||
GROUP g1: TEXT = "This is input1";
|
||||
EDIT f1 = formonly.field1;
|
||||
BUTTONEDIT f2 = formonly.field2, ACTION=foo,
|
||||
COMMENT="Bound with the name 'foo'";
|
||||
BUTTONEDIT f3 = formonly.field3, ACTION=input1.bar,
|
||||
COMMENT="Bound with the name 'input1.bar'";
|
||||
BUTTON in_bar: input1.bar, COMMENT="Bound with the name 'input1.bar'";
|
||||
BUTTON in_foo: input1.foo, COMMENT="Bound with the name 'input1.foo'";
|
||||
|
||||
GROUP g2: TEXT = "This is list sr1";
|
||||
EDIT c11 = formonly.col11;
|
||||
EDIT c12 = formonly.col12;
|
||||
BUTTON t1_ins: sr1.insert, image="listins";
|
||||
BUTTON t1_app: sr1.append, image="listadd";
|
||||
BUTTON t1_del: sr1.delete, image="listdel";
|
||||
BUTTON t1_foo: sr1.foo, COMMENT="Bound with the name 'sr1.foo'";
|
||||
|
||||
GROUP g3: TEXT = "This is list sr2";
|
||||
EDIT c21 = formonly.col21;
|
||||
EDIT c22 = formonly.col22;
|
||||
BUTTON t2_ins: sr2.insert, image="listins";
|
||||
BUTTON t2_app: sr2.append, image="listadd";
|
||||
BUTTON t2_del: sr2.delete, image="listdel";
|
||||
BUTTON t2_foo: sr2.foo, COMMENT="Bound with the name 'sr2.foo'";
|
||||
|
||||
GROUP g4: TEXT = "This is input2";
|
||||
EDIT f4 = formonly.field4;
|
||||
|
||||
END
|
||||
|
||||
INSTRUCTIONS
|
||||
SCREEN RECORD sr1 (FORMONLY.col11, FORMONLY.col12);
|
||||
SCREEN RECORD sr2 (FORMONLY.col21, FORMONLY.col22);
|
||||
END
|
||||
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
# 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.
|
||||
|
||||
DEFINE arr1 DYNAMIC ARRAY OF RECORD name STRING END RECORD
|
||||
DEFINE arr2 DYNAMIC ARRAY OF RECORD name STRING END RECORD
|
||||
DEFINE arr3 DYNAMIC ARRAY OF RECORD name STRING END RECORD
|
||||
|
||||
DEFINE att1 DYNAMIC ARRAY OF RECORD color STRING END RECORD
|
||||
DEFINE att2 DYNAMIC ARRAY OF RECORD color STRING END RECORD
|
||||
DEFINE att3 DYNAMIC ARRAY OF RECORD color STRING END RECORD
|
||||
|
||||
MAIN
|
||||
|
||||
OPTIONS INPUT WRAP
|
||||
|
||||
OPEN FORM f1 FROM "ThreeLists"
|
||||
DISPLAY FORM f1
|
||||
|
||||
CALL fillarr(arr1, att1, "First List ", 5, "blue reverse")
|
||||
CALL fillarr(arr2, att2, "Second List ", 7, "red reverse")
|
||||
CALL fillarr(arr3, att3, "Third List ", 4, "green reverse")
|
||||
|
||||
DIALOG ATTRIBUTES(FIELD ORDER FORM, UNBUFFERED)
|
||||
|
||||
DISPLAY ARRAY arr1 TO sr1.*
|
||||
BEFORE ROW
|
||||
CALL setup(DIALOG)
|
||||
END DISPLAY
|
||||
|
||||
DISPLAY ARRAY arr2 TO sr2.*
|
||||
BEFORE ROW
|
||||
CALL setup(DIALOG)
|
||||
END DISPLAY
|
||||
|
||||
DISPLAY ARRAY arr3 TO sr3.*
|
||||
BEFORE ROW
|
||||
CALL setup(DIALOG)
|
||||
END DISPLAY
|
||||
|
||||
BEFORE DIALOG
|
||||
CALL DIALOG.setArrayAttributes("sr1", att1)
|
||||
CALL DIALOG.setArrayAttributes("sr2", att2)
|
||||
CALL DIALOG.setArrayAttributes("sr3", att3)
|
||||
|
||||
ON ACTION move_1_to_2 CALL move_row(DIALOG, "sr1", "sr2", arr1, arr2, att1, att2)
|
||||
ON ACTION move_2_to_1 CALL move_row(DIALOG, "sr2", "sr1", arr2, arr1, att2, att1)
|
||||
ON ACTION move_1_to_3 CALL move_row(DIALOG, "sr1", "sr3", arr1, arr3, att1, att3)
|
||||
ON ACTION move_2_to_3 CALL move_row(DIALOG, "sr2", "sr3", arr2, arr3, att2, att3)
|
||||
ON ACTION move_3_to_12 CALL move_3_to_12(DIALOG)
|
||||
|
||||
ON ACTION clear_current
|
||||
CALL clear_current(DIALOG)
|
||||
|
||||
ON ACTION quit ATTRIBUTES(TEXT="Quit")
|
||||
EXIT DIALOG
|
||||
|
||||
ON ACTION CLOSE
|
||||
EXIT DIALOG
|
||||
|
||||
END DIALOG
|
||||
|
||||
END MAIN
|
||||
|
||||
FUNCTION setup(d)
|
||||
DEFINE d ui.Dialog
|
||||
-- can't user ui.Dialog.getArrayLength() because it's not synchronized for now.
|
||||
CALL d.setActionActive("move_1_to_2", arr1.getLength()>0)
|
||||
CALL d.setActionActive("move_2_to_1", arr2.getLength()>0)
|
||||
CALL d.setActionActive("move_1_to_3", arr1.getLength()>0)
|
||||
CALL d.setActionActive("move_2_to_3", arr2.getLength()>0)
|
||||
CALL d.setActionActive("move_3_to_12", arr3.getLength()>0)
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION clear_current(d)
|
||||
DEFINE d ui.Dialog
|
||||
CALL d.deleteAllRows( d.getCurrentItem() )
|
||||
CALL setup(d)
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION move_row(d, sa, sb, arra, arrb, atta, attb)
|
||||
DEFINE d ui.Dialog
|
||||
DEFINE sa, sb STRING
|
||||
DEFINE arra DYNAMIC ARRAY OF RECORD name STRING END RECORD
|
||||
DEFINE arrb DYNAMIC ARRAY OF RECORD name STRING END RECORD
|
||||
DEFINE atta DYNAMIC ARRAY OF RECORD color STRING END RECORD
|
||||
DEFINE attb DYNAMIC ARRAY OF RECORD color STRING END RECORD
|
||||
DEFINE ra, rb INTEGER
|
||||
LET ra = d.getCurrentRow(sa)
|
||||
IF ra == 0 THEN RETURN END IF
|
||||
LET rb = d.getCurrentRow(sb)
|
||||
IF rb == 0 THEN
|
||||
CALL d.appendRow(sb)
|
||||
LET rb = 1
|
||||
ELSE
|
||||
CALL d.insertRow(sb, rb)
|
||||
END IF
|
||||
CALL d.setCurrentRow(sb, rb)
|
||||
LET arrb[rb].name = arra[ra].name
|
||||
LET attb[rb].color = atta[ra].color
|
||||
CALL d.deleteRow(sa, ra)
|
||||
CALL setup(d)
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION move_3_to_12(d)
|
||||
DEFINE d ui.Dialog
|
||||
DEFINE r1, r2, r3 INTEGER
|
||||
|
||||
LET r3 = d.getCurrentRow("sr3")
|
||||
IF r3 == 0 THEN RETURN END IF
|
||||
|
||||
LET r1 = d.getCurrentRow("sr1")
|
||||
LET r2 = d.getCurrentRow("sr2")
|
||||
|
||||
IF r1 == 0 THEN
|
||||
CALL d.appendRow("sr1")
|
||||
LET r1 = 1
|
||||
ELSE
|
||||
CALL d.insertRow("sr1", r1)
|
||||
END IF
|
||||
LET arr1[r1].name = arr3[r3].name
|
||||
LET att1[r1].color = att3[r3].color
|
||||
CALL d.setCurrentRow("sr1", r1)
|
||||
|
||||
IF r2 == 0 THEN
|
||||
CALL d.appendRow("sr2")
|
||||
LET r2 = 1
|
||||
ELSE
|
||||
CALL d.insertRow("sr2", r2)
|
||||
END IF
|
||||
LET arr2[r2].name = arr3[r3].name
|
||||
LET att2[r2].color = att3[r3].color
|
||||
CALL d.setCurrentRow("sr2", r2)
|
||||
|
||||
CALL d.deleteRow("sr3", r3)
|
||||
|
||||
CALL setup(d)
|
||||
END FUNCTION
|
||||
|
||||
FUNCTION fillarr(a, c, p, m, d)
|
||||
DEFINE a DYNAMIC ARRAY OF RECORD name STRING END RECORD
|
||||
DEFINE c DYNAMIC ARRAY OF RECORD color STRING END RECORD
|
||||
DEFINE p, d STRING
|
||||
DEFINE m, i INTEGER
|
||||
CALL a.clear()
|
||||
CALL c.clear()
|
||||
FOR i = 1 TO m
|
||||
LET a[i].name = p || "-" || i
|
||||
LET c[i].color = d
|
||||
END FOR
|
||||
END FUNCTION
|
||||
@@ -0,0 +1,54 @@
|
||||
# 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 ( TEXT = "3 Lists" )
|
||||
GRID
|
||||
{
|
||||
<t t1 ><g g><t t2 >
|
||||
List 1 [b12] List 2
|
||||
[c1 ] [c2 ]
|
||||
[c1 ] [c2 ]
|
||||
[c1 ] [c2 ]
|
||||
[c1 ] [c2 ]
|
||||
[c1 ][b21][c2 ]
|
||||
< >< >< >
|
||||
<g g2 >
|
||||
[bx1: :bxc:bx3: :bx2]
|
||||
< >
|
||||
<t t3 >
|
||||
List 3
|
||||
[c3 ]
|
||||
[c3 ]
|
||||
[c3 ]
|
||||
[c3 ]
|
||||
[c3 ]
|
||||
[c3 ]
|
||||
}
|
||||
END
|
||||
END
|
||||
|
||||
ATTRIBUTES
|
||||
EDIT c1 = FORMONLY.col1 TYPE VARCHAR;
|
||||
EDIT c2 = FORMONLY.col2 TYPE VARCHAR;
|
||||
EDIT c3 = FORMONLY.col3 TYPE VARCHAR;
|
||||
GROUP g: TEXT="", GRIDCHILDRENINPARENT;
|
||||
BUTTON b12 : move_1_to_2, IMAGE="arright";
|
||||
BUTTON b21 : move_2_to_1, IMAGE="arrleft";
|
||||
GROUP g2: TEXT="", GRIDCHILDRENINPARENT;
|
||||
BUTTON bx1 : move_1_to_3, IMAGE="arrdown";
|
||||
BUTTON bx2 : move_2_to_3, IMAGE="arrdown";
|
||||
BUTTON bx3 : move_3_to_12, IMAGE="arrup";
|
||||
BUTTON bxc : clear_current, IMAGE="eraser";
|
||||
END
|
||||
|
||||
INSTRUCTIONS
|
||||
SCREEN RECORD sr1 (FORMONLY.col1);
|
||||
SCREEN RECORD sr2 (FORMONLY.col2);
|
||||
SCREEN RECORD sr3 (FORMONLY.col3);
|
||||
END
|
||||
@@ -0,0 +1,71 @@
|
||||
# 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.
|
||||
|
||||
DEFINE arr1 DYNAMIC ARRAY OF VARCHAR(50)
|
||||
DEFINE arr2 DYNAMIC ARRAY OF VARCHAR(50)
|
||||
DEFINE nbrows1, nbrows2 INTEGER
|
||||
|
||||
MAIN
|
||||
|
||||
OPTIONS INPUT WRAP
|
||||
|
||||
OPEN FORM f1 FROM "TwoLists"
|
||||
DISPLAY FORM f1
|
||||
|
||||
LET nbrows1 = 5
|
||||
CALL fillarr(arr1, "First List", nbrows1)
|
||||
LET nbrows2 = 7
|
||||
CALL fillarr(arr2, "Second List", nbrows2)
|
||||
|
||||
DIALOG ATTRIBUTES(FIELD ORDER FORM, UNBUFFERED)
|
||||
|
||||
DISPLAY ARRAY arr1 TO sr1.*
|
||||
END DISPLAY
|
||||
|
||||
DISPLAY ARRAY arr2 TO sr2.*
|
||||
END DISPLAY
|
||||
|
||||
INPUT BY NAME nbrows1, nbrows2 ATTRIBUTES(WITHOUT DEFAULTS)
|
||||
END INPUT
|
||||
|
||||
COMMAND "Clear List 1"
|
||||
CALL arr1.clear()
|
||||
|
||||
COMMAND "Clear List 2"
|
||||
CALL arr2.clear()
|
||||
|
||||
COMMAND "Fill List 1"
|
||||
CALL fillarr(arr1, "Command Fill 1", nbrows1)
|
||||
|
||||
COMMAND "Fill List 2"
|
||||
CALL fillarr(arr2, "Command Fill 2", nbrows2)
|
||||
|
||||
ON KEY(CONTROL-F)
|
||||
CASE DIALOG.getCurrentItem()
|
||||
WHEN "sr1" CALL fillarr(arr1, "Key Fill 1", nbrows1)
|
||||
WHEN "sr2" CALL fillarr(arr2, "Key Fill 2", nbrows2)
|
||||
END CASE
|
||||
|
||||
COMMAND "Quit"
|
||||
EXIT DIALOG
|
||||
|
||||
END DIALOG
|
||||
|
||||
END MAIN
|
||||
|
||||
FUNCTION fillarr(a, p, m)
|
||||
DEFINE a DYNAMIC ARRAY OF VARCHAR(50)
|
||||
DEFINE p STRING
|
||||
DEFINE m, i INTEGER
|
||||
CALL a.CLEAR()
|
||||
FOR i = 1 TO m
|
||||
CALL a.appendElement()
|
||||
LET a[i] = p || " : " || i
|
||||
END FOR
|
||||
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.
|
||||
|
||||
SCREEN -- Can be executed in TUI mode
|
||||
{
|
||||
This demo can be executed in TUI mode (FGLGUI=0)
|
||||
Tab to the fields, change the row count and select
|
||||
the fill option or tab to a list and hit CONTROL-F
|
||||
----------------------------------------------------
|
||||
List 1 List 2
|
||||
[c1 ][c2 ]
|
||||
[c1 ][c2 ]
|
||||
[c1 ][c2 ]
|
||||
[c1 ][c2 ]
|
||||
[c1 ][c2 ]
|
||||
[c1 ][c2 ]
|
||||
[c1 ][c2 ]
|
||||
[c1 ][c2 ]
|
||||
[c1 ][c2 ]
|
||||
[c1 ][c2 ]
|
||||
----------------------------------------------------
|
||||
Rows in list1: [f1 ]
|
||||
Rows in list2: [f2 ]
|
||||
}
|
||||
END
|
||||
|
||||
ATTRIBUTES
|
||||
EDIT c1 = FORMONLY.col1 TYPE VARCHAR;
|
||||
EDIT c2 = FORMONLY.col2 TYPE VARCHAR;
|
||||
EDIT f1 = FORMONLY.nbrows1 TYPE INTEGER;
|
||||
EDIT f2 = FORMONLY.nbrows2 TYPE INTEGER;
|
||||
END
|
||||
|
||||
INSTRUCTIONS
|
||||
SCREEN RECORD sr1 (FORMONLY.col1);
|
||||
SCREEN RECORD sr2 (FORMONLY.col2);
|
||||
END
|
||||
@@ -0,0 +1,37 @@
|
||||
# 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.
|
||||
|
||||
FORMS=\
|
||||
CityList.42f\
|
||||
TwoLists.42f\
|
||||
ThreeLists.42f\
|
||||
QueryCustomers.42f\
|
||||
CustListEdit.42f\
|
||||
Mailer.42f\
|
||||
SubBlockActions.42f
|
||||
|
||||
MODULES=\
|
||||
MsgBox.42m\
|
||||
CityList.42m\
|
||||
Styles.42m\
|
||||
TwoLists.42m\
|
||||
ThreeLists.42m\
|
||||
QueryCustomers.42m\
|
||||
CustListEdit.42m\
|
||||
Mailer.42m\
|
||||
SubBlockActions.42m
|
||||
|
||||
SUBDIRS=\
|
||||
FormWizard\
|
||||
FileDlg\
|
||||
Folder\
|
||||
ListAndEditableDetail\
|
||||
MultiMasterDetail\
|
||||
|
||||
include ../Makeincl
|
||||
Reference in New Issue
Block a user