Kofax Transformation modules is a product that primarily focuses on one thing: user productivity. However, this post is not about the importance of focusing on the user behind the screen, despite the fact that it can not be stressed enough HOW important it is; I just want to describe one method on how to improve productivity.
Does the following sound familiar to you: you want your users to be able to:
- insert some dummy values inside a field when a hotkey is pressed,
- reset a group of fields as re-typing sometimes is faster than correcting field by field,
- automatically calculate the missing amount in a field group.
So, we can distinguish two groups:
Sounds easy, you think? Just add some buttons to the validation, link them with hotkeys and call your own functions in script? Good, then let’s increase the difficulty level for you.
Lets assume you’ve got 3 normal fields and you have to make sure each field can be reset to a dummy value. Then there’s a group of three fields – net, tax and rate – which should offer computer-aided calculation if one number is missing. Our validation form will look like this:
Of course you will agree that this is a pretty gross solution. Four different buttons for the user to press, four different hotkeys are assigned. As in the sample above the designer tried to save some space, those hotkeys are not part of the buttons text, so either the user has to memorize them all or hit the button using the mouse. But hold on, it gets even worse. Take a look at the three fields net, tax and rate. The logic should calculate the missing amount. What if this is not possible, because two fields are empty or one field contains letters?
Right, NOTHING happens. The user is not prompted with any hint that one field is missing, or as in our case, one field contained non-numeric letters. Ah yes, and please do not tell me that you could show a message box. For the time being, we assume the message box never has been invented.
We are facing two major problems:
Despite the fact that you will drive your users into infinite madness, you will run out of hotkeys eventually. Not to bad, as your users weren’t using the buttons anyway. They forgot what they did, and you never told them. Something we definitely don’t want, so our objectives are clear: we want to:
- have just ONE button and ONE hotkey that does all the magic for us,
- show the users what is going to happen if they press the button (or hit the hotkey),
- show them a confirmation when the action was performed successfully,
- show them an error message if the action failed, and why it failed.
That’s where my ActionEvents come into play:
ActionEvents are supported by a dedicated area in validation which will make sure error messages and status infos are displayed. And there is one button which does all the magic. Let’s see how the system behaves before digging deeper:
As you can see, ActionEvents provide the user with all the details required. They tell what is going to happen if the user performs the action and it tells the user whether the action was a success or not. So even if your validation users forgot what action is linked to which button, they will immediately see it.
So you will ask yourself, “wouldn’t I need to know which field currently is focussed then?”, and you are correct. Simply put, that is all we need to achieve:
So, every time a field (or table cell) is focused, we set a global variable containing the currently focused field (or table cell). In addition, we bind the action we want to execute to the field (or table cell). Thereby we can access that currently focused field whereever we want, for example when the button is clicked. Then we execute the action bound to that field (or – you guessed it, the cell). For the ActionEvents, we make use of a delegator. Using that technique we can simply bind a pointer to a function to another global variable which holds the event to be executed when the magic button is pressed.
Part 1: storing the field or table cell currently in focus
As said, two global variables hold the currently selected field or table cell.
' the following two global variables act as a container for the currently focussed field in validation
Dim fieldInFocus As CscXDocField
Dim tableCellInFocus As CscXDocTableCell
The next part deals makes sure the selected item is set correctly:
Private Sub ValidationForm_FieldGotFocus(ByVal pxdoc As CASCADELib.CscXDocument, ByVal pField As CASCADELib.CscXDocField)
' normal field got focused, set the global fieldInFous and "unset" the tableCellInFocus
Set fieldInFocus = pField
Set tableCellInFocus = Nothing
End Sub
Private Sub ValidationForm_TableCellGotFocus(ByVal pxdoc As CASCADELib.CscXDocument, ByVal pField As CASCADELib.CscXDocField, ByVal RowIndex As Long, ByVal ColumnIndex As Long)
' table cell got focused, set the global tableCellInFocus and "unset" the fieldInFocus
Set tableCellInFocus = pField.Table.Rows(RowIndex).Cells(ColumnIndex)
Set fieldInFocus = Nothing
End Sub
Thats it – now we do know which field or table cell is currently focused!
Part 2: the ActionEvents
Each ActionEvent is expected to perform three tasks:
- Display an info what is going to happen if the user performs the action,
- execute the action itself if the button is pressed,
- display a status message in the case of success,
- display an error message in case of failure.
So, we define a signature for all ActionEvents to make sure freshly added events do satisfy our requirements (as you know, there are no own classes or interfaces in WinWrap Basic). In addition to the delegator, I listed a sample ActionEvent that sets a dummy value:
Delegate Function ActionEvent(pxdoc As CscXDocument, doRun As Boolean) As String
' this is an example for a single field action - a dummy value will be inserted and the field will be made valid
Private Function Dummy_ActionEvent(pxdoc As CscXDocument, doRun As Boolean) As String
Dummy_ActionEvent = "sets a dummy value for this field and makes it valid"
If doRun Then
With fieldInFocus
.Text = "ABC-1234"
.Valid = True
.Confidence = 1
End With
' this is optional: give the user an additional indication that the action has been performed (more useful to deliver error messages)
Dummy_ActionEvent = "dummy value it is"
End If
End Function
Pretty simple as you see. When the doRun parameter is set to true, the event should be fired. In the other case, only status information should be displayed. You’ll see why I used that parameter later, for the moment it is only important for you to see that the ActionEvent fully satisfies our requirements listed above. Of course, as nothing can go wrong when setting a dummy value, you will not see any error message. Fine, here you go – another ActionEvent for a field group:
' this is an example for a multi field action - calculates the missing value from a tax group
Private Function TaxGroup_ActionEvent(pxdoc As CscXDocument, doRun As Boolean) As String
TaxGroup_ActionEvent = "calulates the missing value (tax group) - make sure that this only works if one (1) value is missing!"
If doRun Then
If CalculateMissingAmount_NetTaxRate(pxdoc.Fields.ItemByName("Net"), pxdoc.Fields.ItemByName("Tax"), pxdoc.Fields.ItemByName("Rate")) Then
TaxGroup_ActionEvent = "how can you expect me to work if you don't respect the rules!"
End If
End If
End Function
You see? Pretty easy. Just imagine the CalculateMissingAmount_NetTaxRate function does a modification of the items provided by reference, i.e. calculating the missing amount or returning false if that is impossible.
Part 3: binding and executing the ActionEvents
The last step is rather simple: we only need to bind the ActionEvents and later on execute them. First we need the global variable, which will hold the ActionEvent to be executed, and that variable is of the delegator’s type:
Dim ae As ActionEventEnd Function
Then, we modify the focus event for fields to correctly set the ActionEvent, depending on the currently focused field:
Private Sub ValidationForm_FieldGotFocus(ByVal pxdoc As CASCADELib.CscXDocument, ByVal pField As CASCADELib.CscXDocField)
' normal field got focused, set the global fieldInFous and "unset" the tableCellInFocus
Set fieldInFocus = pField
Set tableCellInFocus = Nothing
' set the action event associated with the field selected
' first reset both the action event associated and the info label
Set ae = Nothing
' set a dummy action for all the other fields
Set ae = AddressOf OtherFields_ActionEvent
If pField.Name = "Dummy" Then Set ae = AddressOf Dummy_ActionEvent
If pField.Name = "AmountReset" Then Set ae = AddressOf ResetAmounts_ActionEvent
If pField.Name = "Net" Or pField.Name = "Tax" Or pField.Name = "Rate" Then Set ae = AddressOf TaxGroup_ActionEvent
If pField.Name = "A" Or pField.Name = "B" Or pField.Name = "C" Then Set ae = AddressOf SumGroup_ActionEvent
' update only if ae is set!
If Not ae Is Nothing Then UpdateActionEventInfo(ae, pField, pxdoc)
End Sub
The UpdateActionEventInfo method now makes use of the doRun-parameter of the ActionEvent: it does NOT execute the action, but makes sure the info text is updated:
Private Sub UpdateActionEventInfo(ae As ActionEvent, field As CscXDocField, pxdoc As CscXDocument)
SetPassiveFieldInfo(ae.Invoke(pxdoc, False))
End Sub
' this sub sets the passive field info label
Private Sub SetPassiveFieldInfo(txt As String)
ValidationForm.Labels.ItemByName("ActionEventInfo").Text = txt
End Sub
The last step is concerned with (finally) executing the action.
Private Sub ValidationForm_ButtonClicked(ByVal ButtonName As String, ByVal pxdoc As CASCADELib.CscXDocument)
Select Case ButtonName
Case "FireActionEvent"
' piece of cake: just fire the set action event (if one is set, that is) and set the success/error message
If Not ae Is Nothing Then SetPassiveFieldInfo(ae.Invoke(pxdoc, True))
End Select
End Sub
That’s all – you now have successfully extended KTM with ActionEvents and hopefully made life for your users easier.