Skip to main content

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
}
tip

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

EventFiresBuffer ModifiableCan Cancel
InsertingBefore insert super()YesNo (throw to prevent)
InsertedAfter insert completesNoNo
UpdatingBefore update super()YesNo (throw to prevent)
UpdatedAfter update completesNoNo
DeletingBefore delete super()NoNo (throw to prevent)
DeletedAfter delete completesNoNo
ValidatingWriteBefore validateWrite()YesYes (via parmValidateResult)
ValidatedWriteAfter validateWrite()YesYes (via parmValidateResult)
ValidatingDeleteBefore validateDelete()NoYes (via parmValidateResult)
ValidatedDeleteAfter validateDelete()NoYes (via parmValidateResult)
ValidatingFieldBefore validateField()YesYes (via parmValidateResult)
ValidatedFieldAfter validateField()YesYes (via parmValidateResult)
ModifyingFieldBefore modifiedField()YesNo
ModifiedFieldAfter modifiedField()YesNo
InitializingRecordBefore initValue()YesNo
InitializedRecordAfter initValue()YesNo
PostLoadAfter record loadedYesNo
MappingEntityToDataSourceBefore entity→table mapYesNo
MappedEntityToDataSourceAfter entity→table mapYesNo
MappingDatasourceToEntityBefore table→entity mapNoNo
MappedDatasourceToEntityAfter table→entity mapNoNo

Choosing Between Event Handlers and Chain of Command

ScenarioRecommended Approach
Add validation without altering existing validationEvent handler (ValidatedWrite)
Set default values on new fieldsEvent handler (InitializedRecord)
Modify parameters or return values of a methodChain of Command
Log or audit record changesEvent handler (Inserted, Updated, Deleted)
Add cascading field logic on existing fieldsChain of Command (modifiedField)
React to data entity import/exportEvent handler (MappedEntityToDataSource)
Prevent an operation conditionallyEvent handler (ValidatingDelete) or CoC