Activating Local Validation
1. | The coldef for the XTREE must specify LocValSBX=xxxxxx in the zero column definition, where xxxxxx is the name of this SBX, i.e. LocValSBX=xtra24v |
2. | Add the V (conditional) or v (unconditional) local validation cformat codes to any column for which you want local validation. You can combine the V/v codes with existing X/x codes to enable local validation followed by app-level validation if the local validation isn't available or doesn't clear the exitcode; see above. Doubling the X or x code forces app-level validation regardless of what the local validation returns. |
Identifying Exit Column
One potential complication with separating the validation logic in an SBX apart from the application that sets up and calls XTREE is the increased difficulty of identifying the column to be validated. For this, there are multiple, possibly overlapping, strategies:
1. | Use the physical exit column (xtr.XCOL). This is the least robust since any change to the column layout in the main program will potentially change some or all of the physical column numbers. |
2. | Use xtr.COLID. These values are assigned to columns by adding the Advanced Coldef Option ID=# to the coldef specification for each column requiring validation. These ID numbers do not need to be in any particular order or consecutive, and they are immune to rearrangement of the physical columns. This is the best general strategy. |
3. | In cases where some of the validation logic can be shared between columns of the same general type, you can use xtr.COLTYPEID to identify the type. This requires use of the Advanced Coldef Option TYPEID=# in the coldef specification for those columns. For example, you may have a set of shortcuts and other formatting logic that apply to all dates across your application. These could be implemented in functions that were associated with your COLTYPEID values and shared across multiple validation modules. This example uses two COLTYPEID values: 1 for a basic date that defaults to today, with various abbreviated forms expanding to mm/dd/yy, and 2 for the same but with no default, allowing a blank date. |
Retrieving Cell Data From Row
Typically validation starts with the cell just exited. You can retrieve that cell either via ansrow[cpos;clen] or by using a structure definition for the answer row, matching the one used in the application, in which case you can just access the fields directly, e.g. ansrow.trxdate.
In cases where you want to validate some other cell(s) besides the one just exited, you will almost certainly need to use the structure definition method.
Beware of potential problems with numeric cells using masks containing thousands separators or currency (e.g. $###,###.##) since most string-to-number conversions will terminate at the first non-digit, non-decimal point character. To avoid that problem, use .VALX(cell$).
Updating The Row Contents
If you update the row contents, you need to set status to XVF_OK_UPD or XVF_NV to signal to XTREE that the tree data needs to be updated.
There's no particular harm to always returning XVF_OK_UPD for successful validation, regardless of whether you updated the row data, but returning XVF_OK instead of XVF_OK_UPD when you didn't make any change does allow XTREE to avoid the operation of updating the cells in the row and thus is more efficient.
You can only update cells on the current row. If only updating the exit cell, you can do it by writing to ansrow[cpos;clen], but if updating any other cells on the row, there is no practical way other than to define the same answer row structure here as in the application and then use it to reference the cells on the row, e.g. ansrow.trxdate = mm/dd/yy
When updating cells in the row, ideally you should fill the entire cell, using a mask or pad() to space fill the trailing nulls. But if you don't, XTREE will replace any nulls left embedded in the ansrow with spaces.
Failed Validation
If the cell fails the validation, set return status = XVF_NV and set xtr.TARGETCOL back to the exit column (xtr.XCOL). If you don't set xtr.TARGETCOL back to the exit column and don't otherwise display an error message, the user might not realize that the validation failed. Note however that any updates to the row data made here will be transferred back to the tree when the return status = XVF_NV, just as it would be for XVF_OK_UPD. This allows the validation routine more latitude in communicating back to the user.)
Multi-Row Validation
Local validation currently only provides visibility to a single row, so if you need multi-row validation, you need to use traditional application-level validation.
Relationship between Local and Traditional Validation
Local validation is triggered first, based on the V (conditional) or v (unconditional) cformat code. If the column definition also calls for traditional application-level validation (X or x), then the exitcode parameter will set to -48 coming in to this routine. On return, if exitcode is not zero, XTREE will exit with that exitcode. So in almost all cases you will want to set exitcode to 0; the exceptions would be where you want to force an application-level exit.
Even when the returned exitcode is set to 0, the application can force application-level validation by doubling the X or x cformat code. For example, if the cformat = VXX then if the cell is changed, XTREE will first call the local validation routine, and then regardless of what it returns, it will exit with the normal -48 exitcode. This capability is mainly of use as a transition strategy. For example, if you want to implement local validation in an existing program with application-level validation routines, you might start by reimplementing the validation logic in an SBX. You can then easily enable or disable the local validation (for testing, debugging, etc.) merely by placing or removing the SBX from the client.
Debugging
The typically debugging technique is to embed DEBUG.PRINT statements which can be activated by the SET DEBUG TAGS command. But note that in the ATE environment, the SET command is executed on the server and doesn't affect the ATE client, so you can’t set specific tags that way. Instead, it's probably best to just use ordinary DEBUG.PRINT statements without (level,tags) qualifiers, in which case you can activate them by using the right-click menu on the System Messages window to set debug mode on the client.
Another built-in debugging tool is the XTREE trace option. Open the System Messages window, right click on it for Properties, and then check the XTREE option. The window will then display details about each time the app calls XTREE, each time it returns to the app, plus each call to the local validation routine will generate the following two traces:
Local Validation XTRA24V > Xcell=1,3, Tcell=1,4, colid=2, typeid=1, treeid=XTRA24, exitcode=0
Local Validation < target=1,4, status=9004, exitcode=0, valver=100
The first trace line shows information being passed to the local validation routine XTRA24V: xtr.XROW, xtr.XCOL, xtr.TARGETROW, xtr.TARGETCOL, xtr.COLID, xtr.COLTYPEID, the TreeID, and the exitcode.
The second trace shows the information being returned: xtr.TARGETROW, xtr.TARGETCOL, the status, returned exitcode, and the valver (version indicator set by the validation routine.)
Calling the Local Validation Routine from the Application
There is nothing stopping you from using the local validation routine at the application level! But the application code would need to handle the logic to re-enter the tree.