Skip to main content

Args and the Axapta Construction Pattern

When looking through Axapta code you will see a lot of references to the type Args. This type is used as a general method for sharing construction parameters. It works well because in the Axapta usage pattern because


1.the typical number of parameters is small
2.the types of parameters used is very similar
3.construction of the main objects often entails constructing multiple collaborating objects that need access to the same shared constructor arguments.

In traditional OO code each object will have one or more constructors. These constructors would have different numbers of arguments and types, as required. For example, a class representing a form might have a default constructor that would simply show all the items in the table. It could also have a constructor that accepted a string that was used to highlight the row with a matching primary key, newSelect(str key). Or one that filtered all the values to match the provided range, newMinMax(str smin, str smax). Side note: in Axapta there is no overloading, we accomplish the same thing with multiple static constructor methods. They are typically named newXyz, newAbc, etc. Sometimes the create prefix is used, for example createXyz, createAbc.



This works well in the simple case, however if a class needs to construct many collaborating objects each requiring the same specialized constructors, the process becomes unwieldy. In addition, the construction is often handled indirectly. That is, there is some logic used to determine which constructor to use and the results of these decisions must pass through multiple layers. Also, as it turns out in Axapta the variety of startup parameters is remarkable similar for report, forms, action classes and other objects. The solution chosen by Axapta is to have a single constructor that takes an Args parameter. This single parameter packages up the most commonly used parameters.



This transforms the traditional OO multiple constructors (like this)




final class FormRun extends ObjectRun

{

static MyForm newSelect(str key)

{ ...new up a form with row selected ...}



static MyForm newRange(str smin, str smax)

{ ...new up a form with only range visible ...}

}



Into the he Axapta Args constructor pattern, which would look like this:




final class FormRun extends ObjectRun

{

public void new(Args _args)

{

if (something in _args)

{ ...new up a form with row selected ...}

if (something else in _args)

{ ...new up a form with only range visible ...}



}

}



The work is simply moved for a multiple static constructors to multiple if-else blocks. Note that most forms and reports do the work in the init() method instead of an overloaded new. The effect is the same. When you consider that many forms/reports have at least one helper class (often with the same name) and the sub‑elements (sections, queries, controls) that may also need to modify their behavior based on the startup parameters you can see that passing a single Args around is much easier. You don’t even have to pass the Args around if the object inherits from ObjectRun (which all forms and reports do), since ObjectRun holds the initial constructor Args. The pattern would break down if the number and type of parameters were vastly different.



Classes and jobs also use the Args pattern. Classes that are the target of an Action menu item will have a static main method that accepts a single Args parameter. By default jobs will also receive a single Args parameter.



Generic Args Properties:




Property


Type


Description




caller


object


A reference to the object that created the Args.




dataset


tableId


The table that lookupField and record refer too. However, this is often left null since the proper dataset is implied.




designName


str


Which of the designs within a report should be used, null implies the default design.




lookupField


fieldId


When showing a form, the field to filter on.




lookupValue


str


When showing a form, the value to filter with.




menuItemName


str


The name of the menu item that caused the object to be created.




menuItemType


menuItemType


The type of the menu item that caused the object to be created.




name


str


For reports and forms this is the name of menu item that invoked the form/report




parentWnd


int


I have only seen this used internally in the kernel to insure dialogs child windows are properly nested.




parm


str


A generic string property.




parmEnum


int


One of the values from the base enum specified by parmEnumType. Works together with the parmEnumType to select different behaviors.




parmEnumType


int


The id of the base enum. Typically you would use the enumnum(some-type) function.




parmObject


object


A generic object property.




record


common


A blank record for dataset. You can create a blank record with the DictTable. makeRecord() method. Often the lookupField is filled in with the lookupValue.




An interesting example on using Args to launch a form/report is in the \Classes\ClassFactory\drillDown method.




public void drillDown(OutputField _outputField, MenuItemType _menuItemType, str _menuItemName)

{

Args args = new Args();

MenuFunction menuFunction = new MenuFunction(_menuItemName, _menuItemType);

DictTable dt = new DictTable(_outputField.tableHandle());

Common record = dt.makeRecord();

sysReportRun sysReportRun;

FormRun formRun;

;

args.caller(null);

record.(_outputField.fieldHandle()) = _outputField.formatValue();

args.record(record);

args.name(menuFunction.object());

args.lookupField(_outputField.fieldHandle());

args.lookupValue(_outputField.formatValue());

...SNIP...

}



An example of a form taking different actions based on the enumeration parameter can be seen in the PriceDiscGroup form:




void init()

{

InventTableModule inventTableModule;

;

super();

if (element.args().parmEnumType())

{

module = element.args().parmEnum();

}

...SNIP...

}



An example of a class preparing some enumeration args for a form to consume can be found in the ProdJournalCreateBOM class:




client static void main(Args args)

{

...SNIP...

if (journalId)

{

argsTable = new Args();

argsTable.name(formstr(ProdJournalTable));

argsTable.parmEnumType(enumnum(ProdJournalType));

argsTable.parmEnum(ProdJournalType::Picklist);



...SNIP...

}

thanks David Ferguson

Popular posts from this blog

Dynamics Axapta: Sales Orders & Business Connector

Well, again folllowing my same idea of writting close to nothing and pasting code, I'll paste in some code to create a sales order from some basic data and the invoice it. I'll try to explain more in the future. AxaptaObject axSalesTable = ax.CreateAxaptaObject("AxSalesTable"); AxaptaRecord rcInventDim = ax.CreateAxaptaRecord("InventDim"); AxaptaRecord rcCustTable = ax.CreateAxaptaRecord("CustTable"); rcCustTable.ExecuteStmt("select * from %1 where %1.AccountNum == '" + MySalesOrderObject.CustAccount + "'"); if (MySalesOrderObject.CurrencyCode.Trim().Length == 0) MySalesOrderObject.CurrencyCode = rcCustTable.get_Field("Currency").ToString().Trim(); string sTaxGroup = rcCustTable.get_Field("taxgroup").ToString().Trim(); //set header level fields axSalesTable.Call("parmSalesName", MySalesOrderObject.SalesName.Trim()); axSalesTable.Call("parmCustAccount", M

Passing values between form and class

Class name is EmplDuplication and Form is EmplTable . void clicked() {    MenuFunction mf;    args args = new Args();    ;     args.record(EmplTable);     mf = new menufunction(identifierstr(EmplDuplication), MenuItemType::Action); mf.run(args); } Meanwhile, in the main() method of the EmplDuplication class, we need to put this Axapta x++ code to get the datasource: static void main(Args args) {     EmplDuplication EmplDuplication; EmplTable localEmplTable; ;     if(args.record().TableId == tablenum(EmplTable)) localEmplTable = args.record();     ... }