In this article by Mindaugas Pocius, the author of Microsoft Dynamics AX 2012 R3 Development Cookbook, explains about data organization in the forms. We will cover the following recipes:
(For more resources related to this topic, see here.)
Number sequences are widely used throughout the system as a part of the standard application. Dynamics AX also provides a special number sequence handler class to be used in forms. It is called NumberSeqFormHandler, and its purpose is to simplify the usage of record numbering on the user interface. Some of the standard Dynamics AX forms, such as Customers or Vendors, already have this feature implemented.
This recipe shows you how to use the number sequence handler class. Although in this demonstration we will use an existing form, the same approach will be applied when creating brand-new forms.
For demonstration purposes, we will use the existing Customer groups form located in Accounts receivable | Setup | Customers and change the Customer group field from manual to automatic numbering.
Carry out the following steps in order to complete this recipe:
NumberSeqFormHandler numberSeqFormHandler;
NumberSeqFormHandler numberSeqFormHandler() { if (!numberSeqFormHandler) { numberSeqFormHandler = NumberSeqFormHandler::newForm( CustParameters::numRefCustGroupId().NumberSequenceId, element, CustGroup_ds, fieldNum(CustGroup,CustGroup)); } return numberSeqFormHandler; }
void create(boolean _append = false) { element.numberSeqFormHandler( ).formMethodDataSourceCreatePre(); super(_append); element.numberSeqFormHandler( ).formMethodDataSourceCreate(); }
void delete() { ttsBegin; element.numberSeqFormHandler().formMethodDataSourceDelete(); super(); ttsCommit; }
void write() { ttsBegin; super(); element.numberSeqFormHandler().formMethodDataSourceWrite(); ttsCommit; }
boolean validateWrite() { boolean ret; ret = super(); ret = element.numberSeqFormHandler( ).formMethodDataSourceValidateWrite(ret) && ret; return ret; }
void linkActive() { element.numberSeqFormHandler( ).formMethodDataSourceLinkActive(); super(); }
void close() { if (numberSeqFormHandler) { numberSeqFormHandler.formMethodClose(); } super(); }
First, we declare an object of type NumberSeqFormHandler in the form’s class declaration. Then, we create a new corresponding form method called numberSeqFormHandler(), which instantiates the object if it is not instantiated yet and returns it. This method allows us to hold the handler creation code in one place and reuse it many times within the form.
In this method, we use the newForm() constructor of the NumberSeqFormHandler class to create the numberSeqFormHandler object. It accepts the following arguments:
Finally, we add the various NumberSeqFormHandler methods to the corresponding methods on the form’s data source to ensure proper handling of the numbering when various events are triggered.
Filtering in forms in Dynamics AX is implemented in a variety of ways. As a part of the standard application, Dynamics AX provides various filtering options, such as Filter By Selection, Filter By Grid, or Advanced Filter/Sort that allows you to modify the underlying query of the currently displayed form. In addition to the standard filters, the Dynamics AX list pages normally allow quick filtering on most commonly used fields. Besides that, some of the existing forms have even more advanced filtering options, which allow users to quickly define complex search criteria.
Although the latter option needs additional programming, it is more user-friendly than standard filtering and is a very common request in most of the Dynamics AX implementations.
In this recipe, we will learn how to add custom filters to a form. We will use the Main accounts form as a basis and add a few custom filters, which will allow users to search for accounts based on their name and type.
Carry out the following steps in order to complete this recipe:
Property | Value |
Columns | 2 |
Property | Value |
Name | FilterName |
AutoDeclaration | Yes |
ExtendedDataType | AccountName |
Property | Value |
Name | FilterType |
AutoDeclaration | Yes |
EnumType | DimensionLedgerAccountType |
Selection | 10 |
boolean modified() { boolean ret; ret = super(); if (ret) { MainAccount_ds.executeQuery(); } return ret; }
public void executeQuery() { QueryBuildRange qbrName; QueryBuildRange qbrType; MainAccount::updateBalances(); qbrName = SysQuery::findOrCreateRange( MainAccount_q.dataSourceTable(tableNum(MainAccount)), fieldNum(MainAccount,Name)); qbrType = SysQuery::findOrCreateRange( MainAccount_q.dataSourceTable(tableNum(MainAccount)), fieldNum(MainAccount,Type)); if (FilterName.text()) { qbrName.value(SysQuery::valueLike(queryValue( FilterName.text()))); } else { qbrName.value(SysQuery::valueUnlimited()); } if (FilterType.selection() == DimensionLedgerAccountType::Blank) { qbrType.value(SysQuery::valueUnlimited()); } else { qbrType.value(queryValue(FilterType.selection())); } super(); }
We start by changing the Columns property of the existing empty Filter group control to make sure all our controls are placed from the left to the right in one line.
We add two new controls that represent the Account name and Main account type filters and enable them to be automatically declared for later usage in the code. We also override their modified() event methods to ensure that the MainAccount data source’s query is re-executed whenever the controls’ value change.
All the code is placed in the executeQuery() method of the form’s data source. The code has to be placed before super() to make sure the query is modified before fetching the data.
Here, we declare and create two new QueryBuildRange objects, which represent the ranges on the query. We use the findOrCreateRange() method of the SysQuery application class to get the range object. This method is very useful and important, as it allows you to reuse previously created ranges.
Next, we set the ranges’ values. If the filter controls are blank, we use the valueUnlimited() method of the SysQuery application class to clear the ranges. If the user types some text into the filter controls, we pass those values to the query ranges. The global queryValue() function—which is actually a shortcut to SysQuery::value()—ensures that only safe characters are passed to the range. The SysQuery::valueLike() method adds the * character around the account name value to make sure that the search is done based on partial text.
Note that the SysQuery helper class is very useful when working with queries, as it does all kinds of input data conversions to make sure they can be safely used. Here is a brief summary of few other useful methods in the SysQuery class:
The standard form filters and majority of customized form filters in Dynamics AX are only applied once the user presses some button or key. It is acceptable in most cases, especially if multiple criteria are used. However, when the result retrieval speed and usage simplicity has priority over system performance, it is possible to set up the search so the record list is updated instantly when the user starts typing.
In this recipe, to demonstrate the instant search, we will modify the Main accounts form. We will add a custom Account name filter, which will update the account list automatically when the user starts typing.
Carry out the following steps in order to complete this recipe:
Property | Value |
Name | FilterName |
AutoDeclaration | Yes |
ExtendedDataType | AccountName |
void textChange() { super(); MainAccount_ds.executeQuery(); }
void enter() { super(); this.setSelection( strLen(this.text()), strLen(this.text())); }
public void executeQuery() { QueryBuildRange qbrName; MainAccount::updateBalances(); qbrName = SysQuery::findOrCreateRange( this.queryBuildDataSource(), fieldNum(MainAccount,Name)); qbrName.value( FilterName.text() ? SysQuery::valueLike(queryValue(FilterName.text())) : SysQuery::valueUnlimited()); super(); }
Firstly, we add a new control, which represents the Account name filter. Normally, the user’s typing triggers the textChange() event method on the active control every time a character is entered. So, we override this method and add the code to re-execute the form’s query whenever a new character is typed in.
Next, we have to correct the cursor’s behavior. Currently, once the user types in the first character, the search is executed and the system moves the focus out of this control and then moves back into the control selecting all the typed text. If the user continues typing, the existing text will be overwritten with the new character and the loop will continue.
In order to get around this, we have to override the control’s enter() event method. This method is called every time the control receives a focus whether it was done by a user’s mouse, key, or by the system. Here, we call the setSelection() method. Normally, the purpose of this method is to mark a control’s text or a part of it as selected. Its first argument specifies the beginning of the selection and the second one specifies the end. In this recipe, we are using this method in a slightly different way.
We pass the length of the typed text as a first argument, which means the selection starts at the end of the text. We pass the same value as a second argument, which means that selection ends at the end of the text. It does not make any sense from the selection point of view, but it ensures that the cursor always stays at the end of the typed text allowing the user to continue typing.
The last thing to do is to add some code to the executeQuery() method to change the query before it is executed. Modifying the query was discussed in detail in the Creating a custom filter control recipe. The only thing to note here is that we use the SysQuery::valueLike() helper method which adds * to the beginning and the end of the search string to make the search by a partial string.
Note that the system’s performance might be affected as the data search is executed every time the user types in a character. It is not recommended to use this approach for large tables.
In this article, we learned how to add custom filters to forms to allow users to filter data and create record lists for quick data manipulation. We also learned how to build filter controls on forms and how to create custom instant search filters.
Further resources on this subject:
I remember deciding to pursue my first IT certification, the CompTIA A+. I had signed…
Key takeaways The transformer architecture has proved to be revolutionary in outperforming the classical RNN…
Once we learn how to deploy an Ubuntu server, how to manage users, and how…
Key-takeaways: Clean code isn’t just a nice thing to have or a luxury in software projects; it's a necessity. If we…
While developing a web application, or setting dynamic pages and meta tags we need to deal with…
Software architecture is one of the most discussed topics in the software industry today, and…