# Event Actions
reviewed: 3 March 2025
Event actions can be compared (to an extend) to database triggers. You can execute specific code triggered by certain systems events.
Event actions can be used for many purposes; for example:
- Validating data (before insert or update)
- Writing an audit (post insert, delete or update)
- Etc
The _eventAction Method
Event actions for specific entities are implemented in a method with the reserved name _eventAction.
function _eventAction( timing, type, group: null() )
// Code goes here
end-function
The _eventAction method is automatically called by CaseMaster before and after specific system events. You should not invoke the _eventAction action directly (with one exception, explained in the Tricks & Pitfalls chapter).
The parameters to this method are:
| Parameter | Usage |
|---|---|
| timing | Pre, or post; depending on whether invoked before or after the event |
| type | The type of event |
| group | Only relevant for certain event types; the attribute group that is involved in the event |
Timing
As explained, the _eventAction method is invoked before and after certain system events. The timing and group parameters are the same in both invocations, the timing parameter has one of the following values:
eventActionTiming.PosteventActionTiming.Pre
The following example shows the timing parameter in action:
function _eventAction( timing, type, group: null() )
if and(
eq( [timing], eventActionTiming.Post ),
in( [type], eventActionType.Update, eventActionType.Insert )
)
// Some code execute AFTER update or insert
end-if
if and(
eq( [timing], eventActionTiming.Pre ),
eq( [type], eventActionType.Delete )
)
// Some code execute BEFORE delete
end-if
end-function
Typical examples for pre and post are:
- Data validation before inserting or updating data (after would obviously be too late)
- Checking for possible duplicates before inserting data (again, after would be too late)
- Update the total field on an order after inserting, deleting or updating an order line (so also the value of the affected order line is included)
Event Types
The type parameter indicates the type of event the _eventAction method is invoked for.
| Type | Usage |
|---|---|
| eventActionType.BO2BO | Data is copied from this BO to another BO |
| eventActionType.Create | An instance of this BO is created |
| eventActionType.Delete | Data for this entity is deleted (from the data source) |
| eventActionType.Insert | Data for this entity is being inserted |
| eventActionType.Load | Data for this entity is loaded |
| eventActionType.Reset | A bo.reset() is called on this BO |
| eventActionType.RS2BO | Data is copied from a record-set to this BO |
| eventActionType.SetAutomatics | A bo.setAutomatics() is called on this BO |
| eventActionType.Update | Data for this entity is being inserted |
The types delete, insert ad update are the most frequently used event types.
Group
The group parameter is only relevant for the event types BO2BO, insert, Load, Reset, RS2BO, SetAutomatics and Update (I guess it would have been faster to list the event types where group is not relevant...).
Note that group is also set for insert which may sound wrong. It is set to all the attributes that have been touched (see here).
You typically use the group parameter to avoid some logic from firing when there is no need.
Take the following example where we want to check for a duplicate name where you can only create a duplicate if the name attribute is actually set.
if and(
eq( [timing], eventActionTiming.Pre ),
in( [type], eventActionType.Insert, eventActionType.Update ),
boDesc.inGroup( [_me], 'name', [group] ),
bo.exists( 'account/account', 'id<>{ bo.pk( [_me] ) } & name={ bo.attr( [_me], "name" ) }' )
)
raise exceptionType.soft, qualifier.invoke( 'Account with this name already exists' )
end-if
Examples
Data validation
// Valid CC email addresses
if and(
eq( [timing], eventActionTiming.Pre ),
in( [type], eventActionType.Insert, eventActionType.Update ),
boDesc.inGroup( [_me], 'defaultCCConfirmationEmails', [group] ),
not( script.call( 'helpers:areValidEmailAddresses', bo.attr( [_me], 'defaultCCConfirmationEmails' ) ) )
)
raise exceptionType.soft, qualifier.invoke( 'Invalid email address(es) for CC confirmation email' )
end-if
Data Manipulation
// Format postcode
if and(
eq( [timing], eventActionTiming.Pre ),
in( [type], eventActionType.Insert, eventActionType.Update ),
boDesc.anyInGroup( [_me], 'postcode', [group] ),
)
bo.setAttr( [_me], 'postcode', replace( bo.attr( [_me], 'postCode' ),' ', '' ) )
end-if
Writing an Audit
(See here).
// Audit
if and(
eq( [timing], eventActionTiming.Pre ),
or(
in( [type], eventActionType.Insert, eventActionType.Delete ),
and(
eq( [type], eventActionType.Update ),
ne( [group], null() )
)
)
)
bo.writeAudit(
[_me],
translate(
[type],
eventActionType.Insert, 'insert',
eventActionType.Update, if( isNotNull( bo.attr( [_me], '_auditAction' ) ), 'action', 'update' ),
eventActionType.Delete, 'delete'
)
)
end-if
Tricks & Pitfalls
Recursion (or Otherwise Unwanted Event Actions)
You would not be the first that triggered an unintended recursive call when doing a delete, insert or update from the _eventAction method on the same entity.
A common solution to this problem is to add an attribute (without a column) that you set in order to avoid recursion.
// Validation
if and(
eq( [timing], eventActionTiming.Pre ),
eq( [type], eventActionType.Update ),
not( bo.attr( [_me], '_noRecursion_' ) ),
gt( bo.invoke( [_me], 'getDeliveryInfoForPTVLength' ), 255 )
)
bo.setAttr( [_me], 'quantity', null() )
bo.setAttr( [_me], '_noRecursion_', true() )
bo.update( [_me], 'quantity )
bo.setAttr( [_me], '_noRecursion_', false() )
end-if
The definition of the noRecursion attribute is:
_noRecursion_: <@bo/attribute/boolean>
<End of document>