Table Code Extensions
Code extensions allow you to add new methods and wrap existing methods on a table without modifying its source code. This is achieved through augmentation classes decorated with the [ExtensionOf] attribute, using the Chain of Command (CoC) pattern.
Creating a Table Extension Class
An extension class targets a specific table using the [ExtensionOf(tableStr(TableName))] attribute. The class must be declared final and must not inherit from any other class.
[ExtensionOf(tableStr(CustTable))]
final class SAMOCustTable_Extension
{
// New methods and wrapped methods go here
}
Naming Convention
| Element | Pattern | Example |
|---|---|---|
| Extension class | <Prefix><TableName>_Extension | SAMOCustTable_Extension |
| Multiple extensions | <Prefix><TableName>_<Feature>_Extension | SAMOCustTable_Credit_Extension |
Only one [ExtensionOf] class per table per model is recommended. If you need multiple, differentiate with a feature suffix.
Chain of Command (CoC)
Chain of Command lets you wrap existing table methods — executing logic before and/or after the base implementation. The next keyword calls the next link in the chain (ultimately the base method).
Wrapping insert()
[ExtensionOf(tableStr(CustTable))]
final class SAMOCustTable_Extension
{
public void insert()
{
// Pre-insert logic
this.SAMOSetDefaultTier();
next insert();
// Post-insert logic
SAMOCustAudit::logCreation(this);
}
}
Wrapping validateWrite()
[ExtensionOf(tableStr(CustTable))]
final class SAMOCustTable_Extension
{
public boolean validateWrite()
{
boolean ret = next validateWrite();
if (ret && !this.SAMOCustTier)
{
ret = checkFailed("@SAMO:CustTierRequired");
}
return ret;
}
}
Wrapping validateField()
[ExtensionOf(tableStr(CustTable))]
final class SAMOCustTable_Extension
{
public boolean validateField(FieldId _fieldId)
{
boolean ret = next validateField(_fieldId);
switch (_fieldId)
{
case fieldNum(CustTable, SAMOCustTier):
if (ret && this.SAMOCustTier == SAMOCustTier::Premium
&& !this.CreditMax)
{
ret = checkFailed("@SAMO:PremiumRequiresCredit");
}
break;
}
return ret;
}
}
Wrapping modifiedField()
[ExtensionOf(tableStr(CustTable))]
final class SAMOCustTable_Extension
{
public void modifiedField(FieldId _fieldId)
{
next modifiedField(_fieldId);
switch (_fieldId)
{
case fieldNum(CustTable, SAMOCustTier):
this.SAMOApplyTierDefaults();
break;
}
}
}
Wrapping initValue()
[ExtensionOf(tableStr(CustTable))]
final class SAMOCustTable_Extension
{
public void initValue()
{
next initValue();
this.SAMOCustTier = SAMOCustTier::Standard;
this.SAMOOnboardingDate = DateTimeUtil::getSystemDate(
DateTimeUtil::getUserPreferredTimeZone());
}
}
Adding New Methods
Extension classes can define entirely new methods on the table. These are accessible from any code that has a buffer of that table type.
[ExtensionOf(tableStr(CustTable))]
final class SAMOCustTable_Extension
{
public SAMOCustTier SAMOCalculateTier()
{
SAMOCustTier tier;
if (this.CreditMax >= 100000)
{
tier = SAMOCustTier::Premium;
}
else if (this.CreditMax >= 10000)
{
tier = SAMOCustTier::Gold;
}
else
{
tier = SAMOCustTier::Standard;
}
return tier;
}
}
Methods That Can Be Wrapped
The following table methods support Chain of Command wrapping:
| Method | Category | Description |
|---|---|---|
insert() | Data manipulation | Called when a new record is written to the database. |
update() | Data manipulation | Called when an existing record is saved. |
delete() | Data manipulation | Called when a record is removed. |
initValue() | Initialisation | Called to set default values for a new record. |
validateWrite() | Validation | Called before insert/update to validate the record. |
validateDelete() | Validation | Called before delete to validate the operation. |
validateField(FieldId) | Validation | Called when a field value changes on a form. |
modifiedField(FieldId) | Field change | Called after a field value changes on a form. |
find() | Static | Static method for finding records by key. |
exist() | Static | Static method for checking record existence. |
renamePrimaryKey() | Key management | Called when the primary key value is changed. |
caption() | Display | Returns the caption for the current record. |
canSubmitToWorkflow() | Workflow | Determines if the record can be submitted to workflow. |
postLoad() | Lifecycle | Called after a record is loaded from the database. |
aosValidateInsert() | Server validation | Server-side insert validation. |
aosValidateUpdate() | Server validation | Server-side update validation. |
aosValidateDelete() | Server validation | Server-side delete validation. |
aosValidateRead() | Server validation | Server-side read validation. |
If a method is not in this list, use event handlers instead of CoC to inject logic. Some kernel-level methods cannot be wrapped.
CoC Rules and Constraints
- Always call
next. Every wrapped method must callnextexactly once. Failing to callnextbreaks the chain and prevents the base method and other extensions from executing. - Extension classes must be
final. The compiler enforces this. - No constructors. Extension classes cannot have constructors.
- No state fields. Extension classes should not declare instance variables — they share the table buffer's scope. Use
SysExtensionSerializerExtensionMapor other patterns for persistent state. - Method signatures must match exactly. The wrapping method must have the same name, return type, and parameter list as the base method.
- Access level cannot be more restrictive than the original method.