Table Event Handlers
Event handlers allow you to subscribe to lifecycle events on a table without modifying its source code. Unlike Chain of Command (which wraps a method), event handlers fire at specific moments during the table's lifecycle and are registered using attributes on static methods in a separate class.
Event handlers are ideal for additive logic — logging, cascading updates, integration triggers — where you need to react to an event without altering the method's core behaviour.
Registering an Event Handler
Event handler methods are static, defined on any class in your model, and decorated with one of the table event attributes. The method signature must match the delegate signature for the specific event.
class SAMOCustTableEventHandlers
{
// Event handlers defined as static methods
}
Naming convention: Name the class <Prefix><TableName>EventHandlers (e.g., SAMOCustTableEventHandlers). Group all event handlers for a single table in one class.
Data Event Handlers
These events fire during record manipulation operations (insert, update, delete).
onInserted
Fires after a record has been successfully inserted into the database.
[DataEventHandler(tableStr(CustTable), DataEventType::Inserted)]
public static void CustTable_onInserted(Common _sender, DataEventArgs _e)
{
CustTable custTable = _sender as CustTable;
SAMOCustAudit::logEvent(custTable.RecId, SAMOAuditAction::Created);
}
onInserting
Fires before the record is inserted (before super() in insert()). You can modify field values on the buffer before it is written.
[DataEventHandler(tableStr(CustTable), DataEventType::Inserting)]
public static void CustTable_onInserting(Common _sender, DataEventArgs _e)
{
CustTable custTable = _sender as CustTable;
if (!custTable.SAMOCustTier)
{
custTable.SAMOCustTier = SAMOCustTier::Standard;
}
}
onUpdated
Fires after a record has been successfully updated in the database.
[DataEventHandler(tableStr(CustTable), DataEventType::Updated)]
public static void CustTable_onUpdated(Common _sender, DataEventArgs _e)
{
CustTable custTable = _sender as CustTable;
SAMOCustAudit::logEvent(custTable.RecId, SAMOAuditAction::Modified);
}
onUpdating
Fires before the record is updated (before super() in update()). You can modify the buffer before the database write.
[DataEventHandler(tableStr(CustTable), DataEventType::Updating)]
public static void CustTable_onUpdating(Common _sender, DataEventArgs _e)
{
CustTable custTable = _sender as CustTable;
custTable.SAMOLastReviewDate = DateTimeUtil::getSystemDate(
DateTimeUtil::getUserPreferredTimeZone());
}
onDeleted
Fires after a record has been deleted from the database.
[DataEventHandler(tableStr(CustTable), DataEventType::Deleted)]
public static void CustTable_onDeleted(Common _sender, DataEventArgs _e)
{
CustTable custTable = _sender as CustTable;
SAMOCustAudit::logEvent(custTable.RecId, SAMOAuditAction::Deleted);
}
onDeleting
Fires before the record is deleted (before super() in delete()). You can perform cleanup of related records or prevent deletion by throwing an error.
[DataEventHandler(tableStr(CustTable), DataEventType::Deleting)]
public static void CustTable_onDeleting(Common _sender, DataEventArgs _e)
{
CustTable custTable = _sender as CustTable;
// Clean up custom child records
SAMOCustTierHistory tierHistory;
delete_from tierHistory
where tierHistory.CustAccount == custTable.AccountNum;
}
onValidatedWrite
Fires after validateWrite() completes. The ValidateEventArgs parameter contains the current validation result, which you can modify.
[DataEventHandler(tableStr(CustTable), DataEventType::ValidatedWrite)]
public static void CustTable_onValidatedWrite(Common _sender, DataEventArgs _e)
{
ValidateEventArgs validateArgs = _e as ValidateEventArgs;
CustTable custTable = _sender as CustTable;
if (validateArgs.parmValidateResult()
&& !custTable.SAMOCustTier)
{
validateArgs.parmValidateResult(
checkFailed("@SAMO:CustTierRequired"));
}
}
onValidatingWrite
Fires before validateWrite() executes. Use this to add validation that should run before the standard validation.
[DataEventHandler(tableStr(CustTable), DataEventType::ValidatingWrite)]
public static void CustTable_onValidatingWrite(Common _sender, DataEventArgs _e)
{
ValidateEventArgs validateArgs = _e as ValidateEventArgs;
CustTable custTable = _sender as CustTable;
if (custTable.SAMOCustTier == SAMOCustTier::Premium
&& custTable.CreditMax < 50000)
{
validateArgs.parmValidateResult(
checkFailed("@SAMO:PremiumMinCredit"));
}
}
onValidatedDelete
Fires after validateDelete() completes.
[DataEventHandler(tableStr(CustTable), DataEventType::ValidatedDelete)]
public static void CustTable_onValidatedDelete(Common _sender, DataEventArgs _e)
{
ValidateEventArgs validateArgs = _e as ValidateEventArgs;
CustTable custTable = _sender as CustTable;
if (validateArgs.parmValidateResult())
{
// Additional delete validation
if (SAMOCustTierHistory::hasActiveRecords(custTable.AccountNum))
{
validateArgs.parmValidateResult(
checkFailed("@SAMO:CannotDeleteWithActiveHistory"));
}
}
}
onValidatingDelete
Fires before validateDelete() executes.
[DataEventHandler(tableStr(CustTable), DataEventType::ValidatingDelete)]
public static void CustTable_onValidatingDelete(Common _sender, DataEventArgs _e)
{
ValidateEventArgs validateArgs = _e as ValidateEventArgs;
CustTable custTable = _sender as CustTable;
if (custTable.SAMOIsProtected)
{
validateArgs.parmValidateResult(
checkFailed("@SAMO:ProtectedRecordCannotDelete"));
}
}
onValidatedField
Fires after validateField() completes for a specific field.
[DataEventHandler(tableStr(CustTable), DataEventType::ValidatedField)]
public static void CustTable_onValidatedField(Common _sender, DataEventArgs _e)
{
ValidateFieldValueEventArgs validateArgs = _e as ValidateFieldValueEventArgs;
CustTable custTable = _sender as CustTable;
if (validateArgs.parmFieldId() == fieldNum(CustTable, SAMOCustTier))
{
if (validateArgs.parmValidateResult()
&& custTable.SAMOCustTier == SAMOCustTier::Premium
&& !custTable.CreditMax)
{
validateArgs.parmValidateResult(
checkFailed("@SAMO:PremiumRequiresCredit"));
}
}
}
onValidatingField
Fires before validateField() executes for a specific field.
[DataEventHandler(tableStr(CustTable), DataEventType::ValidatingField)]
public static void CustTable_onValidatingField(Common _sender, DataEventArgs _e)
{
ValidateFieldValueEventArgs validateArgs = _e as ValidateFieldValueEventArgs;
CustTable custTable = _sender as CustTable;
if (validateArgs.parmFieldId() == fieldNum(CustTable, SAMOCustTier))
{
// Pre-validation logic for the tier field
}
}
onModifiedField
Fires after modifiedField() completes for a specific field.
[DataEventHandler(tableStr(CustTable), DataEventType::ModifiedField)]
public static void CustTable_onModifiedField(Common _sender, DataEventArgs _e)
{
ModifyFieldValueEventArgs modifyArgs = _e as ModifyFieldValueEventArgs;
CustTable custTable = _sender as CustTable;
if (modifyArgs.parmFieldId() == fieldNum(CustTable, SAMOCustTier))
{
// Cascade default values when tier changes
custTable.SAMOTierChangedDate = DateTimeUtil::getSystemDate(
DateTimeUtil::getUserPreferredTimeZone());
}
}
onModifyingField
Fires before modifiedField() executes for a specific field.
[DataEventHandler(tableStr(CustTable), DataEventType::ModifyingField)]
public static void CustTable_onModifyingField(Common _sender, DataEventArgs _e)
{
ModifyFieldValueEventArgs modifyArgs = _e as ModifyFieldValueEventArgs;
CustTable custTable = _sender as CustTable;
if (modifyArgs.parmFieldId() == fieldNum(CustTable, SAMOCustTier))
{
// Store previous tier for comparison in post-event
}
}
onInitializedRecord
Fires after initValue() completes.
[DataEventHandler(tableStr(CustTable), DataEventType::InitializedRecord)]
public static void CustTable_onInitializedRecord(Common _sender, DataEventArgs _e)
{
CustTable custTable = _sender as CustTable;
custTable.SAMOCustTier = SAMOCustTier::Standard;
custTable.SAMOOnboardingDate = DateTimeUtil::getSystemDate(
DateTimeUtil::getUserPreferredTimeZone());
}
onInitializingRecord
Fires before initValue() executes.
[DataEventHandler(tableStr(CustTable), DataEventType::InitializingRecord)]
public static void CustTable_onInitializingRecord(Common _sender, DataEventArgs _e)
{
// Pre-initialisation logic (rarely needed)
}
onMappingEntityToDataSource / onMappedEntityToDataSource
These events fire during data entity mapping operations — when a data entity maps its fields to/from the underlying table during import/export operations.
[DataEventHandler(tableStr(CustTable), DataEventType::MappedEntityToDataSource)]
public static void CustTable_onMappedEntityToDataSource(Common _sender, DataEventArgs _e)
{
CustTable custTable = _sender as CustTable;
// Post-mapping logic for data entity integration
}
onMappingDatasourceToEntity / onMappedDatasourceToEntity
These fire in the reverse direction — when data flows from the table buffer back to the data entity for export.
[DataEventHandler(tableStr(CustTable), DataEventType::MappedDatasourceToEntity)]
public static void CustTable_onMappedDatasourceToEntity(Common _sender, DataEventArgs _e)
{
CustTable custTable = _sender as CustTable;
// Post-mapping logic for data entity export
}
onPostLoad
Fires after postLoad() — when a record has been loaded from the database. Useful for populating computed or display fields.
[DataEventHandler(tableStr(CustTable), DataEventType::PostLoad)]
public static void CustTable_onPostLoad(Common _sender, DataEventArgs _e)
{
CustTable custTable = _sender as CustTable;
// Populate computed fields after load
}
Complete DataEventType Reference
| Event | Fires | Buffer Modifiable | Can Cancel |
|---|---|---|---|
Inserting | Before insert super() | Yes | No (throw to prevent) |
Inserted | After insert completes | No | No |
Updating | Before update super() | Yes | No (throw to prevent) |
Updated | After update completes | No | No |
Deleting | Before delete super() | No | No (throw to prevent) |
Deleted | After delete completes | No | No |
ValidatingWrite | Before validateWrite() | Yes | Yes (via parmValidateResult) |
ValidatedWrite | After validateWrite() | Yes | Yes (via parmValidateResult) |
ValidatingDelete | Before validateDelete() | No | Yes (via parmValidateResult) |
ValidatedDelete | After validateDelete() | No | Yes (via parmValidateResult) |
ValidatingField | Before validateField() | Yes | Yes (via parmValidateResult) |
ValidatedField | After validateField() | Yes | Yes (via parmValidateResult) |
ModifyingField | Before modifiedField() | Yes | No |
ModifiedField | After modifiedField() | Yes | No |
InitializingRecord | Before initValue() | Yes | No |
InitializedRecord | After initValue() | Yes | No |
PostLoad | After record loaded | Yes | No |
MappingEntityToDataSource | Before entity→table map | Yes | No |
MappedEntityToDataSource | After entity→table map | Yes | No |
MappingDatasourceToEntity | Before table→entity map | No | No |
MappedDatasourceToEntity | After table→entity map | No | No |
Choosing Between Event Handlers and Chain of Command
| Scenario | Recommended Approach |
|---|---|
| Add validation without altering existing validation | Event handler (ValidatedWrite) |
| Set default values on new fields | Event handler (InitializedRecord) |
| Modify parameters or return values of a method | Chain of Command |
| Log or audit record changes | Event handler (Inserted, Updated, Deleted) |
| Add cascading field logic on existing fields | Chain of Command (modifiedField) |
| React to data entity import/export | Event handler (MappedEntityToDataSource) |
| Prevent an operation conditionally | Event handler (ValidatingDelete) or CoC |