# 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.Post
  • eventActionTiming.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>