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