Table of Contents

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.

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<test_Recalc_Date$>
     qty =  @record<test_Recalc_qty$>

     * 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<test_recalc_base_unit_price$>, 'MD2') * ( 1 - discount_pct@ )
     tmp = Int(tmp * 100 + .5) / 100
     discount_unit_price@ = Iconv(tmp, 'MD2')

Return
////////////////////////////////////
onExt_Price:
     retval = @record<test_recalc_base_unit_price$> * @record<test_recalc_qty$>
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<test_recalc_qty$> * 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 <tablename>_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 <tablename>_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@<ctlN>

          * Edit tables can have multiple columns
          ctlCol_count = Fieldcount( ControlSemantics@<ctlN,CS_TABLE$> , @svm )
          For ctlColN = 1 To ctlCol_count
               this_table    = ControlSemantics@<ctlN,CS_TABLE$,ctlColN>
               this_Col      = ControlSemantics@<ctlN,CS_COLUMN$,ctlColN>
               this_field_nr = ControlSemantics@<ctlN,CS_POS$,ctlColN>
               
               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<pos,-1> = ctlN:@svm:ctlColN
                         arr_oiwin_tablecol_ctlName<pos,-1> = ctlName:@svm:ctlColN
                    End Else
                         * a distinct table*colname
                         owin_tc_count += 1
                         arr_oiwin_tablecol<owin_tc_count> = this_tc
                         arr_oiwin_tablecol_ctlNum<owin_tc_count,1>  = ctlN:@svm:ctlColN
                         arr_oiwin_tablecol_ctlName<owin_tc_count,1> = 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<ccN>
          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<tcn>
                    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<tc_ref_pos>
                              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<refN,1>
                                   refIx = Ref_ctls<refN,2>

                                   * ref_control is an item which affects each recalc_Control
                                   recalc_list = ControlSemantics@<refNr,CS_RECALC$, refIx>
                                   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<recalcN,1> : @stm : this_arr_recalc_ctls<recalcN,2>
                                        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@<refNr,CS_RECALC$, refIx> = recalc_list
                                             ControlSemantics@<refNr,CS_LOSTFOCUS$, refIx> = 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