Form Code Extensions
Chain of Command (CoC) allows you to wrap existing form methods — executing logic before and after the original method — without modifying the base code. Form CoC is more granular than table CoC because forms have three distinct extension targets: the form itself, its data sources, and individual data source fields.
Form-Level CoC
Target the form using [ExtensionOf(formStr(FormName))].
[ExtensionOf(formStr(SalesTable))]
final class SAMOSalesTable_Form_Extension
{
public void init()
{
// Pre-init logic
next init();
// Post-init logic
FormRun formRun = this as FormRun;
FormControl myControl = formRun.design().controlName('SAMOCustomControl');
}
public void close()
{
// Pre-close logic
next close();
}
}
Wrappable Form Methods
| Method | Description |
|---|---|
init() | Form initialisation — add ranges, set up state. |
run() | Executes after init() — the form is visible. |
close() | Form is closing — cleanup, refresh caller. |
canClose() | Determines if the form can close (return false to prevent). |
task(int) | Intercept form tasks (keyboard shortcuts, menu commands). |
Data Source CoC
Target a specific data source using [ExtensionOf(formDataSourceStr(FormName, DataSourceName))].
[ExtensionOf(formDataSourceStr(SalesTable, SalesTable))]
final class SAMOSalesTableDS_Extension
{
public void init()
{
next init();
// Add a filter range after data source initialisation
this.query().dataSourceTable(tableNum(SalesTable))
.addRange(fieldNum(SalesTable, SalesStatus))
.value(queryValue(SalesStatus::Invoiced));
}
public void executeQuery()
{
// Modify query before execution
next executeQuery();
// Post-query logic
}
public int active()
{
int ret = next active();
// Enable/disable controls based on current record
SalesTable salesTable = this.cursor();
FormRun formRun = this.formRun();
formRun.design().controlName('SAMOPostButton')
.enabled(salesTable.SalesStatus == SalesStatus::Backorder);
return ret;
}
}
Wrappable Data Source Methods
| Method | Description |
|---|---|
init() | Data source initialisation — add ranges, configure query. |
executeQuery() | Refresh data — modify ranges before next, react to results after. |
active() | Current record changed — enable/disable controls. |
validateWrite() | Validate before save. |
validateDelete() | Validate before delete. |
write() | Record save operation. |
create() | New record creation. |
delete() | Record deletion. |
initValue() | Set default values for new records. |
Data Source Field CoC
Target a specific field using [ExtensionOf(formDataFieldStr(FormName, DataSourceName, FieldName))].
[ExtensionOf(formDataFieldStr(SalesTable, SalesTable, SalesId))]
final class SAMOSalesId_Field_Extension
{
public void modified()
{
next modified();
// React to SalesId field change
FormDataSource fds = this.datasource();
SalesTable salesTable = fds.cursor();
// Lookup related data
}
public boolean validate()
{
boolean ret = next validate();
if (ret)
{
// Additional field validation
}
return ret;
}
}
Wrappable Field Methods
| Method | Description |
|---|---|
modified() | Field value changed — cascade updates. |
validate() | Field validation — return false to reject the value. |
lookup() | Custom lookup — override the dropdown list. |
jumpRef() | Go-to-main-table navigation. |
Naming Conventions
| Target | Pattern | Example |
|---|---|---|
| Form extension class | <Prefix><FormName>_Form_Extension | SAMOSalesTable_Form_Extension |
| Data source class | <Prefix><FormName><DS>_Extension | SAMOSalesTableDS_Extension |
| Field class | <Prefix><FieldName>_Field_Extension | SAMOSalesId_Field_Extension |
CoC Rules
- Always call
next. Every wrapped method must callnextexactly once. - Classes must be
final. The compiler enforces this. - No constructors or state fields. Extension classes share the form's scope.
- Method signatures must match exactly. Same name, return type, and parameters as the base method.
- Access level cannot be more restrictive than the original method.
tip
Choose CoC vs Event Handlers:
- Use CoC when you need to modify parameters, return values, or execution flow (e.g., adding ranges before
super(), altering validation results). - Use event handlers when you need to respond to a lifecycle moment without modifying behaviour (e.g., post-init setup, logging).