====== RTI_GET_CURRENT_SYMBOLIC function ======
==== Description ====
Returns the table and symbolic dictionary item name that is currently being evaluated
==== Syntax ====
//current_info// = **RTI_GET_CURRENT_SYMBOLIC**()
==== Returns ====
The name of the table (in value 1) and the name of the symbolic (in value 2) that is currently being evaluated.
==== Notes ====
RTI_GET_CURRENT_SYMBOLIC can be used in a stored procedure that is called by a symbolic dictionary. Its most likely use is in a stored procedure that handles calls from multiple dictionary items, where its information can be used to dispatch processing to appropriate sections of code based on the table and/or dictionary item that is currently being evaluated.
==== Example ====
You could use programs like below to encapsulate the table logic in a single module called a table controller and ensure that the calculated columns on the form recalculate correctly. The examples are for a table named TEST_RECALC with a window named TEST_RECALC.
{{:programming:programmers_reference_manual:test_recalc_dict.png?nolink&400|}}
function TEST_RECALC_CONTROLLER(inmethod, p1, p2,p3,p4)
/*
** Table controller for an example table named TEST_RECALC
** It uses RTI_GET_CURRENT_SYMBOLIC to determing the name of the current calculated column
**
** In your calculated columns you would use the formula
** @ans = function(test_recalc_controller())
**
** Note:
** See the generic TABLE_CONTROLLER example below. It can call this function.
**
** OI will not resolve calculated column dependencies when you use these table_Controllers
** You can write a routine to help resolve the dependencies
** See the table_Controller_registerRecalc example below
** This module implements a GET_COLUMN_REFERENCES method to support the table_Controller_registerRecalc example function
**
** 09-19-24 rjc Created
*/
$insert logical
$Insert rlist_equates
$Insert test_recalc_equates
#pragma format_indent_Comments
declare function get_current_symbolic,retstack
Equ non_calc_methods$ To "GET_COLUMN_REFERENCES"
Equ calc_methods$ To "EXT_PRICE,QTY_DISCOUNT_PCT,DISCOUNT_UNIT_PRICE,DISCOUNT_EXT_PRICE,WEEK_END_DATE"
* Perform the expensive calculations when you read a new record, cache the result in a common so that indivual columns return their result when called
* use a separate common for each stack depth
arr_retstack = retstack(); restack_depth = fieldcount(arr_retstack,@fm)
com_Id = 'TEST_RECALC_CTRLR':restack_depth
common //com_id//init@,prev_id@,colnames@,references@, prev_Date@, week_end_Date@,discount_pct@,discount_unit_price@
*---
* Main routine
*---
retval = ''
method = if Assigned(inmethod) Then inmethod else ''
if method eq '' then
info = get_current_Symbolic()
table = info[1,@vm,1]
method = info[bcol2()+1,@vm,1]
End
LocateC method In calc_methods$ Using ',' Setting pos Then
* Calc methods assume calculate context, i.e appropriate @dict, @id, @record
Gosub doCalcMethod
End Else
* Non calc methods should not rely on @record, @id, @dict
LocateC method In non_calc_methods$ Using ',' Setting pos Then
Gosub doNonCalcMethod
end Else
* Invalid method
End
end
Return retval
/////////////////////////////////////
/////////////////////////////////////
doCalcMethod:
* Preserve the environment
atdict = @Dict
atrecord = @Record
atid = @Id
atprifile = @pri.file
* Update the calcs once per row, or when called by window recalc
called_by_window = inlist(arr_retstack,'OIWIN_CALCULATE', @fm)
If @id != prev_id@ Or called_by_window Then
Gosub recalc
end
* get the answer
On pos Gosub onExt_price, onQty_discount_pct, onDiscount_unit_price, onDiscount_ext_price, onWeek_end_date
* remember the last id, so we can reuse the calcs
prev_id@ = @id
@dict = atDict
@record = atRecord
@id = atId
@pri.file = atprifile
Return
/////////////////////////////////////
doNonCalcMethod:
On pos Gosub onGet_column_references
Return
/////////////////////////////////////
recalc:
* ASSUME that @DICT, @RECORD, @ID are all set correctly
* Use the date to get the week end date
date = @record
qty = @record
* Quantity discount - should be in a
Begin Case
Case qty Lt 100 ; discount_pct@ = 0
Case qty Lt 500 ; discount_pct@ = .1
Case qty Lt 1000 ; discount_pct@ = .2
Case qty Ge 1000 ; discount_pct@ = .3
End Case
* Pretend there is a weekly discount, stored in a table keyed by week ending date
* Week end date
If Date Else Date = Date()
day_Of_week_nr = Mod(Date,7)
sunday = Date - day_Of_week_nr
week_end_Date@ = sunday + 6
* get the weekly discount per the total qty for this we
weekly_discount = Xlate('WEEKLY_DISCOUNT', week_end_date@, 1, 'X')
discount_pct@ += weekly_discount
* round To 2 places
tmp = Oconv(@record, 'MD2') * ( 1 - discount_pct@ )
tmp = Int(tmp * 100 + .5) / 100
discount_unit_price@ = Iconv(tmp, 'MD2')
Return
////////////////////////////////////
onExt_Price:
retval = @record * @record
Return
////////////////////////////////////
onQty_discount_pct:
* Pick the answer out of the cache
retval = Iconv(discount_pct@, 'MD2')
Return
////////////////////////////////////
onWeek_end_date:
retval = week_end_Date@
Return
////////////////////////////////////
onDiscount_unit_price:
retval = discount_unit_price@
Return
////////////////////////////////////
onDiscount_ext_price:
retval = @record * discount_unit_price@
Return
////////////////////////////////////
onGet_column_references:
colname = If Assigned(p1) Then p1 Else null$
* Hard coded list of references. You could store these in a %TABLE_COLUMN_DEPENDENCIES% record in the dictionary of the table.
Begin Case
Case colname _Eqc 'EXT_PRICE'
references = 'BASE_UNIT_PRICE,QTY'
Case colname _Eqc 'QTY_DISCOUNT_PCT'
references = 'DATE,QTY'
Case colname _Eqc 'WEEK_END_DATE'
references = 'DATE'
Case colname _Eqc 'DISCOUNT_UNIT_PRICE'
references = 'BASE_UNIT_PRICE,QTY'
Case colname _Eqc 'DISCOUNT_EXT_PRICE'
references = 'DISCOUNT_UNIT_PRICE,QTY'
Case otherwise$
references = null$
End Case
* return the references as table:@svm:colname1:@vm:table:@svm:colname2:@vm ...
If references != Null$ Then
retval = 'TEST_RECALC':@svm:references
Swap ',' With @VM:'TEST_RECALC':@svm In retval
end
Return
////////////////////////////////////
function table_controller(tableCol)
/*
* a generic function to route calculated columns to the table specific "controller"
* The controller implements the logic of complex calculated columns.
*
* in your calculated columns use
* @ans = function(table_controller())
*
* For each table, have a program named _controller.
* For example, the PERSON table would have a program named PERSON_CONTROLLER
*
* This program uses RTI_GET_CURRENT_SYMBOLIC to determine the name of the program and call it with
* the name of the current calculated column. It expects the controller to return the value
* that belongs in @ans
*/
Declare Function rti_get_current_symbolic
$Insert logical
If Assigned(tableCol) Else tableCol = null$
If tableCol == null$ then
tableCol = rti_get_current_symbolic()
end
ans = null$
table = tableCol<1,1>
Column = tableCol<1,2>
controller = table:'_CONTROLLER'
If table And column then
ans = Function(@controller())
end
Return ans
Function table_Controller_registerRecalcs(WinId)
/*
* With a window whose calculated columns have a formula like
*
* @ans = function(table_controller())
*
* Openinsight will not resolve column dependencies well, so the window may not update calculated columns when it should.
*
* You could create a registerRecalcs procedure like this example to update the CS_RECALC$ values in the window. That would let the window update correctly.
* You would call this function from the create event of the window
*
* This example assumes that your table controllers are named _controller, and that they implement a 'GET_COLUMN_REFERENCES' function
*
* usage:
* In the create event of your form
* call table_Controller_registerRecalcs(@window )
*
* 08-19-24 rjc Created
*/
Declare Function rti_get_current_symbolic, oiwin_compile_impacts,rti_Dedupe_Array, Get_Property, rti_verify_proc
$Insert logical
$Insert dict_equates
$Insert OIWin_Equates
$Insert OIWin_Comm_Init
* Get out if missing or invalid window
WinID = If Assigned(WinID) Then WinID Else null$
If WinID == null$ then Return ""
If get_property( WinID, 'HANDLE' ) Else
Return ""
end
* Use an array of distinct data bindings used by the window
* Keep associated arrays of controlname and control number to cross reference the data bindings back to the controls
owin_tc_count = 0 ; * count of unique table*colname databindings
arr_oiwin_tablecol = "" ; * fm delimited list of the unique table*colname data bindings in the window
arr_oiwin_tablecol_ctlNum = "" ; * associated fm/vm delimited list of ControlMap@ position * colIndex, i.e the control(s) bound to this table*column, expressed as two numbers
arr_oiwin_tablecol_ctlName = "" ; * associated fm/vm delimited list of ControlName * colIndex, i.e the control(s) bound to this table*column, expressed as ControlName and Control column number
arr_tablecol_calculated = "" ; * fm delimited list of the unique calculated table*colname data bindings in the window
* First step
* Load the table*column and tablecol_Ctrl arrays, get the list of calcuated columns
ctlCount = Fieldcount( controlMap@, @fm)
For ctlN = 1 To ctlCount
ctlName = ControlMap@
* Edit tables can have multiple columns
ctlCol_count = Fieldcount( ControlSemantics@ , @svm )
For ctlColN = 1 To ctlCol_count
this_table = ControlSemantics@
this_Col = ControlSemantics@
this_field_nr = ControlSemantics@
is_calculated = ( this_table != null$ And Num(this_field_nr) And this_field_nr != null$ )
* add it to the list
If this_table != null$ then
this_tc = this_table:@svm:this_col
Locate this_tc In arr_oiwin_tablecol Using @fm Setting pos Then
* a repeated table*colname. Multiple controls use the same column?
arr_oiwin_tablecol_ctlNum = ctlN:@svm:ctlColN
arr_oiwin_tablecol_ctlName = ctlName:@svm:ctlColN
End Else
* a distinct table*colname
owin_tc_count += 1
arr_oiwin_tablecol = this_tc
arr_oiwin_tablecol_ctlNum = ctlN:@svm:ctlColN
arr_oiwin_tablecol_ctlName = ctlName:@svm:ctlColN
* Is it calculated?
Begin Case
Case Num(this_field_nr) And this_field_nr != null$ ; * F type
Case otherwise$
arr_tablecol_calculated<-1> = this_tc
End Case
End /* locate */
End /* table ne null */
Next /* control column */
Next /* control */
* No Calculated columns? Get out
If Blen(arr_tablecol_calculated) Else
return
end
* ----
* Second Step
* for each TABLE*COLNAME, resolve the references
* This is a kind of bill of materials problem, must recurse through the references until none remain
* Columns may appear in multiple references. Keep track of which ones have been examined to save time and to prevent endless recursion
* to resolve, call the system routine, then the table routine
* A reference is a column which THIS column uses to calculate its answer
* -----
references_found = false$ ; *
* Initialize with the list of calculated columns bound to the form
arr_tc_unexamined = arr_tablecol_calculated
arr_tc_examined = null$
calculated_Count = Fieldcount(arr_tablecol_calculated, @fm)
For ccN = 1 To calculated_Count
this_tc = arr_tablecol_calculated
this_col_refs = null$
* Get the top-level references
this_table = this_tc<1,1,1>
this_col = this_tc<1,1,2>
Gosub get_new_refs
this_col_refs = new_refs
/* Recurse through the top-level references until all have been examined */
examined_refs = this_tc
unexamined_refs = this_col_refs
unexamined_refs = null$
utc_pos = 1
Loop
While unexamined_refs != null$
* Pop the next unexamined item from the list
ref_tc = unexamined_refs[utc_pos, @vm, 1]; utc_pos = Bcol2()+1
/* check one reference */
If Blen(ref_tc) Then
* Did we already check this one? else do it now
Locate ref_tc In examined_refs Using @vm Setting unused else
examined_refs<1,-1> = ref_tc
this_table = ref_tc<1,1,1>
this_col = ref_tc<1,1,2>
Gosub get_new_refs
* Add new references to the unexamined list as needed
tmpCol = "";tmpMark=""
Loop
tmp_ref = new_refs[1,@vm]
new_refs[1,Col2()] = null$
Begin Case
Case tmp_ref == null$
Case inList(examined_refs, tmp_ref, @vm) ; * already examined, ignore
Case inList(unexamined_refs, tmp_ref, @vm) ; * already queued, ignore
Case otherwise$
* Needs to be examined enqueue it
unexamined_refs<1,-1> = tmp_ref
End case
While new_Refs != null$
Repeat /* add new references to unexamined list */
* Add these new references to the list for the column.
If new_refs != Null$ then
Convert @svm To '*' In new_refs
this_col_refs<1,-1> = new_refs
end
End
End /* check one reference */
While utc_pos Lt Blen(unexamined_refs)
Repeat /* recurse through references */
*---
* Update the recalc list on referenced controls
* At this point col references is a list of the table*column(s) used by this calculated table*column
* Need to make the information useful to the window by converting table*column references into controlName*controlColumn references
*
* First, get the ctrlName*colindex value(s) for the current table*column - this is that column(s) which need recalculation if one of the references changes
* Next, for each of the references, find the ctrlName*colindex value(s) where the reference appears on the window.
* Ignore any reference which is not bound to a control on the the window.
*---
this_ctrlrefs = null$
If Blen(this_col_refs) then
If Index(this_col_refs, @vm,1) then
this_col_refs = rti_Dedupe_Array( this_col_refs, @vm)
End
* Get the current ctrlName*IX items bound to this table*column
Locate this_tc In arr_oiwin_tablecol Using @fm Setting tcn Then
this_arr_recalc_ctls = arr_oiwin_tablecol_ctlName
Convert @vm:@svm To @fm:@vm In this_arr_recalc_ctls
End Else
* Should not happen
this_arr_recalc_ctls = null$
End
recalc_ctls_count = Fieldcount(this_arr_recalc_ctls, @fm)
* Now, find the controls (if any) bound to the table*colname references
* and update their recalc list with this recalc_ctrl
* generate CtrlName*colIX references from tableName*Colname references
xx = null$;xmark=null$;
this_ref_count = 0
tcr_pos = 1
Loop
tc_Ref = this_col_refs[tcr_pos,@vm,1]; tcr_pos = Bcol2()+1
If tc_Ref Then
* find the reference in the list of tablecols used by the windpw
* ignore any that are not found, it means the column is not used in this window
Locate tc_Ref In arr_oiwin_tablecol Using @fm Setting tc_ref_pos Then
* We have a reference to a table*colname bound to one or more controls
* get the ctrlNr*ctrlIX for the bound controls. This lets us index to the CS_RECALC$ list(s) to update.
Ref_ctls = arr_oiwin_tablecol_ctlNum
Convert @vm:@svm To @fm:@vm In Ref_ctls
ref_Ctl_count = Fieldcount(Ref_ctls, @fm)
For refN = 1 To ref_Ctl_count
refNr = Ref_ctls
refIx = Ref_ctls
* ref_control is an item which affects each recalc_Control
recalc_list = ControlSemantics@
recalc_changed = false$
* ensure ref_control's CS_RECALC$ mentions every recalc_control
For recalcN = 1 To recalc_ctls_count
recalc_ctl = this_arr_recalc_ctls : @stm : this_arr_recalc_ctls
Locate recalc_ctl In recalc_list Using @tm Setting unused Else
If Blen(recalc_list) then
* Append item to list
recalc_list := @tm:recalc_ctl
End else
* Add item to blank list
recalc_list = recalc_ctl
End
* Update the refresh list, flag it for action on lostfocus
ControlSemantics@ = recalc_list
ControlSemantics@ = true$
end
* TBD - check the recalc of the recalc control, ensure it does not contain the referenced control, else we go circular?
Next /* recalc_ctl */
Next /* referenced control */
End /* locate tc_Ref*/
End /* if tc_Ref */
While tcr_pos Lt Blen(this_col_refs)
Repeat /* next tc_Ref */
End /*If Blen(this_col_refs) */
Next /* calculated column */
Return
/////////////////////////////////////
get_new_refs:
* get the list of table_name * colname references for a symbolic
* in this example I'm calling the table_Controller, but you could also create a %COLUMN_DEPENCENCIES% record in the dictionary instead
new_refs = null$
* Is it calculated?
this_dict_Type = Xlate('DICT.':this_Table, this_Col, DICT_TYPE$,'X')
If this_dict_type == 'S' then
* call the table_controller, assume it has a 'GET_COLUMN_REFERENCES' method which
* expects a column_name and returns a vm / svm delimited or vm / asterisk delimited list of table/column references
this_controller = this_table:'_CONTROLLER'
* Does the controller exist? Call it
If rti_Verify_Proc( this_controller) then
new_refs = Function(@this_Controller('GET_COLUMN_REFERENCES', this_Col))
end
* Nothing from the controller?
* Call the system routine - it examines the formula, returns a vm / svm delimited list of table/column references
If new_refs == null$ Then
new_refs = oiwin_compile_impacts( this_table, this_col )
end
* Remove duplicates
If Index(new_refs, @vm,1) then
new_refs = rti_Dedupe_Array( new_refs, @vm)
End
end
return