Using dynamic cursors
Implementing a dynamic dialog based on the database schema.
Using column information from base.SqlHandle
To write generic code accessing a database, implement the dynamic dialog with field names and
types coming from a base.SqlHandle
cursor.
The following code example builds a list of fields based on the SQL command passed as first parameter.
The function scans the result set column names and types of the
base.SqlHandle
cursor, to build the list of field definitions, that can then be used for the dynamic dialog
creation:TYPE t_dd_fields DYNAMIC ARRAY OF RECORD
name STRING,
type STRING
END RECORD
FUNCTION build_field_list(sqlcmd STRING, fields t_dd_fields) RETURNS ()
DEFINE h base.SqlHandle
DEFINE x INTEGER
LET h = base.SqlHandle.create()
CALL h.prepare(sqlcmd)
CALL h.open()
CALL h.fetch()
CALL fields.clear()
FOR x=1 TO h.getResultCount()
LET fields[x].name = h.getResultName(x)
LET fields[x].type = h.getResultType(x)
END FOR
END FUNCTION
By using a form template defining a TABLE with a single column, a function such as the following
can then build the table columns dynamically, that correspond to the fields of the SQL
statement:
FUNCTION adapt_form(fields t_dd_fields, f ui.Form) RETURNS ()
DEFINE fn, tn, cn, wn, rn om.DomNode
DEFINE nl om.NodeList
DEFINE x SMALLINT
LET fn = f.getNode()
LET nl = fn.selectByPath('//Table[@name="table1"]')
LET tn = nl.item(1)
LET nl = fn.selectByPath('//RecordView[@tabName="formonly"]')
LET rn = nl.item(1)
FOR x = 1 TO fields.getLength()
LET cn = IIF(x==1,tn.getFirstChild(),tn.createChild("TableColumn"))
CALL cn.setAttribute("name",SFMT("formonly.%1",fields[x].name))
CALL cn.setAttribute("sqlTabName","formonly")
CALL cn.setAttribute("colName",fields[x].name)
CALL cn.setAttribute("fieldId",x-1)
CALL cn.setAttribute("text",fields[x].name)
CALL cn.setAttribute("tabIndex",x)
LET wn = IIF(x==1,cn.getFirstChild(),cn.createChild("Edit"))
CALL wn.setAttribute("width",width_from_type(fields[x].type))
CALL wn.setAttribute("scroll",1)
LET cn = IIF(x==1,rn.getFirstChild(),rn.createChild("Link"))
CALL cn.setAttribute("colName",fields[x].name)
CALL cn.setAttribute("fieldIdRef",x-1)
END FOR
END FUNCTION
Complete code example
The following code example implements a DISPLAY ARRAY
dynamic dialog by using a
base.SqlHandle
object:
The form.per file:
LAYOUT
TABLE table1
{
[c11 ]
[c11 ]
[c11 ]
[c11 ]
}
END
END
ATTRIBUTES
EDIT c11 = FORMONLY.column1, SCROLL;
END
INSTRUCTIONS
SCREEN RECORD sr(FORMONLY.*);
END
The program code:
TYPE t_dd_fields DYNAMIC ARRAY OF RECORD
name STRING,
type STRING
END RECORD
MAIN
DEFINE fields t_dd_fields
DEFINE d ui.Dialog
DEFINE t STRING
DEFINE tn STRING
DEFINE sqlcmd STRING
CONNECT TO "test1"
OPEN FORM f1 FROM "form"
DISPLAY FORM f1
LET tn = arg_val(1)
IF tn IS NULL THEN
LET tn = "tab1"
CALL create_tab1()
END IF
LET sqlcmd = SFMT("SELECT * FROM %1",tn)
CALL build_field_list(sqlcmd,fields)
CALL adapt_form(fields,ui.Window.getCurrent().getForm())
LET d = ui.Dialog.createDisplayArrayTo(fields, "sr")
CALL d.addTrigger("ON ACTION accept")
CALL d.addTrigger("ON ACTION cancel")
CALL fill_array(sqlcmd,fields,d)
WHILE (t := d.nextEvent()) IS NOT NULL
CASE t
WHEN "BEFORE DISPLAY"
CALL d.setSelectionMode("sr", 2)
CALL d.setCurrentRow("sr",1)
WHEN "ON ACTION cancel"
LET int_flag = TRUE
EXIT WHILE
WHEN "ON ACTION accept"
CALL d.accept()
END CASE
END WHILE
END MAIN
FUNCTION create_tab1() RETURNS ()
WHENEVER ERROR CONTINUE
DROP TABLE tab1
WHENEVER ERROR STOP
CREATE TABLE tab1 ( pkey INTEGER, name VARCHAR(30) )
INSERT INTO tab1 VALUES ( 101, 'aaaa' )
INSERT INTO tab1 VALUES ( 102, 'bbbbbbb' )
INSERT INTO tab1 VALUES ( 103, 'ccccccccc' )
END FUNCTION
FUNCTION build_field_list(sqlcmd STRING, fields t_dd_fields) RETURNS ()
DEFINE h base.SqlHandle
DEFINE x INTEGER
LET h = base.SqlHandle.create()
CALL h.prepare(sqlcmd)
CALL h.open()
CALL h.fetch()
CALL fields.clear()
FOR x=1 TO h.getResultCount()
LET fields[x].name = h.getResultName(x)
LET fields[x].type = h.getResultType(x)
END FOR
END FUNCTION
FUNCTION adapt_form(fields t_dd_fields, f ui.Form) RETURNS ()
DEFINE fn, tn, cn, wn, rn om.DomNode
DEFINE nl om.NodeList
DEFINE x SMALLINT
LET fn = f.getNode()
LET nl = fn.selectByPath('//Table[@name="table1"]')
LET tn = nl.item(1)
LET nl = fn.selectByPath('//RecordView[@tabName="formonly"]')
LET rn = nl.item(1)
FOR x = 1 TO fields.getLength()
LET cn = IIF(x==1,tn.getFirstChild(),tn.createChild("TableColumn"))
CALL cn.setAttribute("name",SFMT("formonly.%1",fields[x].name))
CALL cn.setAttribute("sqlTabName","formonly")
CALL cn.setAttribute("colName",fields[x].name)
CALL cn.setAttribute("fieldId",x-1)
CALL cn.setAttribute("text",fields[x].name)
CALL cn.setAttribute("tabIndex",x)
LET wn = IIF(x==1,cn.getFirstChild(),cn.createChild("Edit"))
CALL wn.setAttribute("width",width_from_type(fields[x].type))
CALL wn.setAttribute("scroll",1)
LET cn = IIF(x==1,rn.getFirstChild(),rn.createChild("Link"))
CALL cn.setAttribute("colName",fields[x].name)
CALL cn.setAttribute("fieldIdRef",x-1)
END FOR
END FUNCTION
FUNCTION width_from_type(tn STRING) RETURNS SMALLINT
CASE
WHEN tn LIKE "CHAR%" OR tn LIKE "VARCHAR%" RETURN 20
WHEN tn == "SMALLINT" RETURN 6
WHEN tn == "INTEGER" RETURN 10
WHEN tn == "DATE" RETURN 10
WHEN tn LIKE "DATETIME%" RETURN 25
WHEN tn LIKE "DECIMAL%" RETURN 12
OTHERWISE RETURN 10
END CASE
END FUNCTION
FUNCTION fill_array(sqlcmd STRING, fields t_dd_fields, d ui.Dialog) RETURNS ()
DEFINE h base.SqlHandle
DEFINE r, x INTEGER
LET h = base.SqlHandle.create()
CALL h.prepare(sqlcmd)
CALL h.open()
LET r = 0
WHILE TRUE
CALL h.fetch()
IF sqlca.sqlcode == NOTFOUND THEN
EXIT WHILE
END IF
CALL d.appendRow("sr")
CALL d.setCurrentRow("sr",r:=r+1)
FOR x=1 TO fields.getLength()
CALL d.setFieldValue(fields[x].name, h.getResultValue(x))
END FOR
END WHILE
END FUNCTION