Monday 17 March 2008

Lookup Form

Axapta usually creates required lookup forms on the fly, but it also allows the developer to create a custom lookup form and assign it as "FormHelp" to an extended datatype, so that it is automatically used. Why would you do that? There might be many reasons. Typically you need to display the data in a non standard order or you want to show only a subset of the data depending on some criteria. Here are the steps needed to create a proper lookup form.


Contents


* 1 Assumptions
* 2 Create the form
* 3 Make it selectable
* 4 Make sure the form opens with the previously selected value
* 5 Make lookup react to search by wild card
* 6 Make Form Default lookup for an Extended Data Type

Assumptions

In the following example I assume you create a lookup for table "xyz" and the ID field of that table is called "id".

Create the form

First create a basic form. Usually this form contains not much more than a grid. But you might add additional controls. Then set the following properties on the datasource, so that the form can not be used for editing:

AllowCheck: No
AllowCreate: No
AllowDelete: No
AllowEdit: No
AutoNotify: No
InsertAtEnd: No
InsertIfEmpty: No

Additionally set for the design the following properties to make it look like a proper lookup:

AlwaysOnTop: Yes
Frame: Border
HideToolbar: Yes
WindowType: Popup


[edit] Make it selectable

Now we need to tell the lookup form, which control will return the selected value. Lets assume you have a form with a grid and in that grid you have a control called "xyz_id". In this case you overwrite the init method of the form as shown below. Note that if you are using Axapta v3.0 or higher, then you can set the control's AutoDeclaration property to Yes and use the control name directly in the selectMode() call.

public void init()
{
FormControl xyz_id;
;

super();

xyz_id= element.design().control(control::xyz_id);
element.selectMode(xyz_id);
}

[edit] Make sure the form opens with the previously selected value

Strange enough the following is missing in most/all custom lookup forms, which you find in Axapta Standard. But it is required, if you want your lookup to mark the previously selected value as active record. Therefore overwrite the ExecuteQuery method of your datasource:

public void executeQuery()
{
FormStringControl callerControl = SysTableLookup::getCallerStringControl(element.args());
;

super();

xyz_ds.findValue(fieldnum(xyz,id),callerControl.text());
}

With a lot of pain I had top learn that the above will not work, if you use the standard query, which is automatically created by Axapta. This might be due to existing Dynalinks or other reasons, which I am not aware of. But it will work fine, if you create your own query for example in the Init method of the datasource. Here you can also define your custom sort or range criteria:

public void init()
{
Query q = new Query();
QueryBuildDataSource qbds;
;

super();

qbds = q.addDataSource(tablenum(xyz));
qbds.orderMode(OrderMode::OrderBy);
qbds.addSortField(fieldNum(xyz,some_other_field));
this.query(q);
}

[edit] Make lookup react to search by wild card

The following you will find in the official documentation and also in a lot of Axapta examples. If you type in a standard Axapta StringEdit Control something like "abc*", then automatically the lookup will open and only the matching subset of records will be shown. You achieve this by overwriting the run method of the form:

public void run()
{
FormStringControl callerControl = SysTableLookup::getCallerStringControl(element.args());
Boolean filterLookup = false;
;

// if lookup was called with filter, then supress autoSearch
if (callerControl.text() && callerControl.hasChanged())
{
filterLookup = true;
xyz_ds.autoSearch(false);
}

super();

// after call of super filter search manually by applying past filter
if (filterLookup)
{
xyz_ds.research();
xyz_ds.filter(fieldnum(xyz,id),callerControl.text());
}
}

Make Form Default lookup for an Extended Data Type

To have Axapta use your form by default every time a lookup is performed, you can set your form's name in the Extended Data Type's property called 'FormHelp'. You do not necessarily need a display menu item, the property is your actual form's name.

So that's it. Now you have a wonderfully working custom lookup.

Wednesday 12 March 2008

Dynamics AX 4.0 Meta Model

If you are a new developer and is trying to ramp up on AX, this might be a post for you. In MorphX (the IDE for AX) you do model-driven development. This basically means you create new elements of certain concepts, decorate them with properties, and wire them all together. Naturally there will be requirements to your features that cannot be expressed in the model, in these cases you have to resort to writing X++ code.

Let's return to modeling. In the AOT (Application Object Tree) you will find a lot of modeling concepts, such as Forms, Tables, Extended Data Types etc. The AOT is quite good as a dictionary of concepts, but how do they relate? For Inside Dynamics AX 4.0 we created the below overview diagrams to answer this question. After the book has been through editing the diagrams were chopped up. This gives a better details view, but the overview was lost. But not anymore. :-)

Read each arrow as "using". For example. A Menu uses Menu Items. A Menu Item uses a Form. A Form uses a Table. A Table uses Extended Data Types. And so on.

I've attach a Microsoft Office Visio version of the diagrams to this post as well for your convenience.

The win client version:


















The web client version:

Temporary tables in Axapta

Temporary tables are used for non-persistent storage in Microsoft Axapta.

They are useful in two common situations

1. As the datasource for a form or report, where the original data is too complex to be easily queried.
2. As temporary storage during complicated processing, to hold the results midway through the process.


Contents
[hide]

* 1 Scoping rules for temporary tables
* 2 Creating temporary tables
o 2.1 In the AOT
o 2.2 Making an existing table temporary
* 3 Temporary tables in forms
* 4 Temporary tables in reports
* 5 Temporary table performance
* 6 Indexes on temporary tables
* 7 Security on temporary tables
* 8 Database transactions (tts) on temporary tables

[edit] Scoping rules for temporary tables

In general, each instance of a temporary table, and it's associated data, will only exist while the buffer variable used to access it is in scope.

You can point multiple buffer variables to the same instance of a temporary table by using either the .setTmpData() method or by directly assigning the buffers to each other, identically to normal tables. In this way, even if your original buffer variable goes out of scope, your data will be retained while one of the other referencing variables remains.

Be aware that static table methods - such as find() - will not work with temporary tables unless you pass through the buffer variable to the method.

For example, this method will not work on a temporary table, as the tempTable variable used is newly created and will always contain no records.

// This won't work on temporary table
public static TempTable find(AccountNum _accountNum, boolean _forUpdate = false)
{
TempTable tempTable;
;

if (_accountNum)
{
tempTable.selectForUpdate(_forUpdate);

select firstonly tempTable
where tempTable.AccountNum == _accountNum;
}

return tempTable;
}

If you want to have a find() method on your temporary table, then you will need to modify it slightly to pass through a reference to our populated temporary table.

// Use this pattern instead
public static TempTable find(AccountNum _accountNum, TempTable _tempTable, boolean _forUpdate = false)
{

if (_accountNum)
{
_tempTable.selectForUpdate(_forUpdate);

select firstonly _tempTable
where _tempTable.AccountNum == _accountNum;
}

return _tempTable;
}

Some examples of populating and using temporary tables can be found in Image:TRG TempTablesGeneral.xpo project.
[edit] Creating temporary tables
[edit] In the AOT

Set the Temporary property to Yes to create a table which will always be temporary.

Note that any existing data will be permanently deleted if you do this!

Of course, you can no longer use the Table Browser to check the data, as the data is stored only per scoped instance of this table.
[edit] Making an existing table temporary

You can convert a normal table to a temporary table in code. For example, if you wish to create a temporary copy of the inventory table:

InventTable inventTable;
;

inventTable.setTmp();

Doing so will remove all data from the temporary copy of the table. If you wish to create a populated temporary copy of a standard table, you can do the following:

InventTable inventTable;
InventTable inventTableTmp;
;

inventTableTmp.setTmp();
while select inventTable
{
inventTableTmp.data(inventTable.data());
inventTableTmp.doInsert();
}

You can now add, modify or delete data from the table without affecting the real contents stored in the database.
[edit] Temporary tables in forms

Using temporary tables in forms requires the use of the .setTmpData() method.

For example:

The temporary table data is populated in a static class method (running server side), which is called from the form and returns the populated table. We could populate a form-level buffer with the temporary data if needed, or else just call the populating method directly from the setTmpData() call as shown below.

In the form datasource init(), we use .setTmpData() to instruct the datasource query to use our temporary table. Our datasource name in this example is TempTable.

public void init()
{
super();

TempTable.setTmpData(tmpTableClass::populateTmpData());
}

See Image:TRG TempTablesForm.xpo for an example of a working form based on a temporary table.

It is also possible to add a table to a form which is not a temporary table, but the data that is shown must be temporary. Compared to the previous example there are not a lot of changes; just make sure the table is made temporary using .setTmp(). As in the previous example the temporary table data is populated in a static class method (running server side), which is called from the form and returns the populated table.

In the following example the InventTable is used.

public void init()
{
super();

InventTable.setTmp();
InventTable.setTmpData(InventTableClass::populateTmpData());
}

[edit] Temporary tables in reports

The correct method of using temporary tables in reports is slightly different from that of forms.

The most important difference is the use of .setRecord() instead of .setTmpData(). A simple example follows:

public boolean fetch()
{
boolean ret;
;
this.queryRun().setRecord(tmpTableClass::populateTmpData());

ret = super();

return ret;
}

As there is often already a supporting RunBaseReport class being used to run the report, it is easy to integrate the population of the temporary data into that existing class. This is particularly useful if you need the data in the temporary table to be dependent on information entered into the report dialog prompt by the user.

See Image:TRG TempTablesReports.xpo for an example of using a RunBaseReport class to run a report based on a temporary table.
[edit] Temporary table performance

Data being stored in temporary tables is stored in a temporary physical file in the file system. The file itself is created when the first record is being inserted in that particular instance of the temporary table. Hence, in a 3 tier environment, the file will be maintained on server or client side, depending on where the first record is inserted. From a performance standpoint this is a concern when using temporary tables.
[edit] Indexes on temporary tables

As with normal tables, indexes can be created on temporary tables. When a temporary copy of a normal table is used with .setTmp(), then the existing indexes will also be created on the temporary version. For new temporary tables (with the Temporary property set to Yes), you must create any desired indexes through the AOT in the normal way.

Indexes have a substantial effect on temporary table performance. For temporary tables with a lot of records you will experience major performance limitations when searching on non-indexed fields.
[edit] Security on temporary tables

You can assign a SecurityKey to a temporary table, like any other. The security key will work well, limiting access. However, temporary tables never show up in the tree for assigning permissions, so it's not possible to actually enable them for users. Therefore it's important not to put a security key on any temporary table or users will never be able to use it.
[edit] Database transactions (tts) on temporary tables

Temporary tables are not included in Dynamics Ax's normal transaction processing capabilities. If you include population of a temporary table inside a ttsBegin/ttsCommit which then aborts, changes made to the temporary table will not be aborted.

To activate transaction capabilities on temporary tables, use the local ttsBegin and ttsCommit methods on the temporary table buffer themselves. These work as expected.

Macros - Definitions and Pitfalls

The X++ language features a macro expansion facility. With it, you can define macros, use macro values, do conditional compilation etc. In this blog I'll describe the semantics of the constructs and provide some guidance to resolve some of the problems beginners and experts alike are having with this language feature.

Macros are unstructured in that they are not defined by the grammar of the language. The handling of macros takes place before the text reaches the compiler.

Macros may appear inside methods and class declarations anywhere that white space is permitted, and may also appear after the ending } in class definitions.

The semantics of each of the macro keywords are described below:
Macro constructs

#define
The syntax is

#define.MyName(SomeValue)

This defines a macro called MyName with the value SomeValue. When this definition is in effect, any references to #MyName will be replaced with the character sequence SomeValue. The definition has no other semantics aside from defining the symbol MyName: The text does not reach the compiler itself. When the compilation of the current method is over, the symbol (MyName in this case) is no longer remembered. If the symbol is already defined, the old value is discarded and replaced by the new value.

#globaldefine
The syntax is

#globaldefine.MyName(SomeValue)

This has the same semantics as #define, described above.

#definc
The syntax is

#definc.MyName

This macro construct is used mainly when the value is a integer value. The preprocessor will increment the value of the symbol by one. If the value was not defined before the #definc occurs, an error is issued by the compiler. If the value before the #definc is not an integer, the old value will be overwritten with the value 0 and then incremented, yielding the value 1.

#defdec
The syntax is

#defdec.MyName

This macro construct is used mainly when the value is a integer value. The preprocessor will decrement the value of the symbol by one. If the value was not defined before the #defdec occurs an error is issued by the compiler. If the value before the #defdec is not an integer, the old value will be overwritten with the value 0 and then decremented, yielding the value -1.


#undef
The syntax is

#undef.MyName

The effect of this is to remove the symbol MyName from the list of current macro definitions. It is not considered an error to remove a symbol that has not previously been #defined.
#if ... #endif
The syntax is

#if.MySymbol

#endif

Or

#if.MySymbol(SomeValue)

#endif

In the first case the textual content marked with … in the examples above is inserted into the source stream if MySymbol has previously been defined. In the second case, the content is inserted into the source stream if and only if the symbol is defined and has the indicated value.

The #if constructs may be nested to any level but there is no #else construct.


#ifnot ... #endif
The syntax is

#ifnot.MySymbol

#endif

Or

#ifnot.MySymbol(SomeValue)

#endif

In the first case the textual content marked with … in the examples above is inserted into the source stream if MySymbol has not been defined. In the second case, the content is inserted into the source stream if the symbol is not defined or is defined but does not have the indicated value.

The #if constructs may be nested to any level. There is no #else construct.
#macrolib
The syntax is

#macrolib.MyName

The name must denote a node in the macros branch of the AOT. The text in that node is processed by the preprocessor. The net effect is to insert the content of the named macro in the source text where the directive appears. It is an error if the node is not found in the macros branch in the AOT.

#macro / #localmacro

The keywords #macro and #localmacro are interchangeable; there is no difference in the semantics of the two. This construct is used to define a symbol to denote textual content possibly spanning several lines.

The syntax is

#localmacro.MySymbol
….
#endmacro

Example:

class MyBaseClass extends Runbase
{
int v1;
#define.myMacro(“Hello world”)
#localmacro.currentlist
v1
#endmacro

public container pack()
{
return [#currentlist]; // #currentlist expands to v1
}

public void run()
{
print #myMacro; // #myMacro expands to “Hello world”
}
}

class MyDerivedClass extends myBaseClass
{
int v2;
#localmacro.currentlist
v2
#endmacro


public container pack()
{
return [super(), #currentlist]; // #currentlist expands to v2
}

public void run()
{
print #myMacro; // #myMacro expands to “Hello world”
}
}

#MySymbol
The syntax is

#MySymbol

This inserts the value of the symbol into the source stream. It is an error to refer to a symbol that has not been defined.
If the name denotes a node in the macros branch of the AOT, the text in that node is processed by the preprocessor (in this case #MySymbol is a shorthand for #macrolib.MySymbol).
Common Problems
In this part of the blog post, I'd like to describe some of the problems programmers have when dealing with macros.
Macro parameters
It seems to be a little known fact and a source of some confusion that macros can be parametrized: Values can be given to the % placeholders at the macro expansion site. Simple textual substitution of the positional parameter with the given, actual parameter then takes place. If no parameter is supplied, the empty string is used. So

#define.MyString("Hello World from %1")

will define a named macro (MyString) with one parameter. If that macro is expanded as shown below:

#MyString(X++)

the resulting string will be

"Hello World from X++"

Note that the place of expansion did not supply the letters X++ inside quotes. That would have generated a compiletime error:

#MyString("X++")

would have generated

"Hello World from "X++""

That is what is meant by simple string substitution.

The confusion can also occur because the % notation is also used for parameter substitution in the strFmt string formatting function. As you know, this function has a variable length parameter list, and each reference of %n in the first argument (a string) will be expanded to contain a textual representation of the n'th argument, as shown below:

print strfmt("The value is %1", theValue);

Now, some programmers have been known to want to specify the first argument, i.e. the string containing the substitutions, with a macro symbol:

#define.TheText("The value is %1")
print strfmt(#TheText, theValue);

But, the macro substition kicks in before the compiler sees the source code. The macro substition engive will not find a parameter to place where the %1 is, so the compiler will see:

print strfmt("The value is ", theValue);

which is probably not what the programmer intended.
Macros in class declarations

Some confusion stems from the situation where macros are defined in class declarations. In order to understand how this is handled it is useful to review what the compiler does when it compiles a method. It starts by calculating the sequence of class derivations that the class is part of. It then parses each of the class declarations with the least derived one first, filling its internal symbol table with the macros as it goes along. After compiling the most local class declaration (the most derived one) the compiler compiles the method itself. Any symbol defined in any of the class declarations will subsequently be available for use in the methods. Symbols defined in a class declaration may be replaced by values defined in more derived class declarations.
Parenthesis in macro strings

The scanner dealing with macro strings is quite simple minded. It will not handle the situation where closing parenthesis characters are included in the string. So,

#define.Another("(This is text in parenthesis)")

will generate a compiler lexical error. If you need to do this, you should use the #localmacro directive instead:

#localmacro.Another
"(This is text in parenthesis)"
#endmacro

In this context the end of the macro is signalled by the #endmacro string, not a right parenthesis.