12 min read

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:

  • Using a number sequence handler
  • Creating a custom filter control
  • Creating a custom instant search filter

(For more resources related to this topic, see here.)

Using a number sequence handler

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.

How to do it…

Carry out the following steps in order to complete this recipe:

  1. In the AOT, open the CustGroup form and add the following code snippet to its class declaration:
    NumberSeqFormHandler numberSeqFormHandler;
  2. Also, create a new method called numberSeqFormHandler() in the same form:
    NumberSeqFormHandler numberSeqFormHandler()
    {
       if (!numberSeqFormHandler)
       {
           numberSeqFormHandler = NumberSeqFormHandler::newForm(
               CustParameters::numRefCustGroupId().NumberSequenceId,
               element,
               CustGroup_ds,
               fieldNum(CustGroup,CustGroup));
       }
       return numberSeqFormHandler;
    }
  3. In the same form, override the CustGroup data source’s create() method with the following code snippet:
    void create(boolean _append = false)
    {
       element.numberSeqFormHandler(
           ).formMethodDataSourceCreatePre();
      
       super(_append);
     
       element.numberSeqFormHandler(
           ).formMethodDataSourceCreate();
    }
  4. Then, override its delete() method with the following code snippet:
    void delete()
    {
       ttsBegin;
     
       element.numberSeqFormHandler().formMethodDataSourceDelete();
     
       super();
     
       ttsCommit;
    }
  5. Then, override the data source’s write() method with the following code snippet:
    void write()
    {
       ttsBegin;
     
       super();
     
       element.numberSeqFormHandler().formMethodDataSourceWrite();
     
       ttsCommit;
    }
  6. Similarly, override its validateWrite() method with the following code snippet:
    boolean validateWrite()
    {
       boolean ret;
     
       ret = super();
     
       ret = element.numberSeqFormHandler(
           ).formMethodDataSourceValidateWrite(ret) && ret;
     
       return ret;
    }
  7. In the same data source, override its linkActive() method with the following code snippet:
    void linkActive()
    {
       element.numberSeqFormHandler(
           ).formMethodDataSourceLinkActive();
     
       super();
    }
  8. Finally, override the form’s close() method with the following code snippet:
    void close()
    {
       if (numberSeqFormHandler)
       {
           numberSeqFormHandler.formMethodClose();
       }
     
       super();
    }
  9. In order to test the numbering, navigate to Accounts receivable | Setup | Customers | Customer groups and try to create several new records—the Customer group value will be generated automatically:

    Microsoft Dynamics AX 2012 R3 Development Cookbook

How it works…

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:

  • The number sequence code ensures a proper format of the customer group numbering. Here, we call the numRefCustGroupId() helper method from the CustParameters table to find which number sequence code will be used when creating a new customer group record.
  • The FormRun object, which represents the form itself.
  • The form data source, where we need to apply the number sequence handler.
  • The field ID into which the number sequence will be populated.

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.

Creating a custom filter control

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.

How to do it…

Carry out the following steps in order to complete this recipe:

  1. In the AOT, locate the MainAccountListPage form and change the following property for its Filter group:

    Property

    Value

    Columns

    2

  2. In the same group, add a new StringEdit control with the following properties:

    Property

    Value

    Name

    FilterName

    AutoDeclaration

    Yes

    ExtendedDataType

    AccountName

  3. Add a new ComboBox control to the same group with the following properties:

    Property

    Value

    Name

    FilterType

    AutoDeclaration

    Yes

    EnumType

    DimensionLedgerAccountType

    Selection

    10

  4. Override the modified() methods for both the newly created controls with the following code snippet:
    boolean modified()
    {
       boolean ret;
     
       ret = super();
     
       if (ret)
       {
           MainAccount_ds.executeQuery();
       }
     
       return ret;
    }
  5. After all modifications, in the AOT, the MainAccountListPage form will look similar to the following screenshot:

    Microsoft Dynamics AX 2012 R3 Development Cookbook

  6. In the same form, update the executeQuery() method of the MainAccount data source as follows:
    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();
    }
  7. In order to test the filters, navigate to General ledger | Common | Main accounts and change the values in the newly created filters—the account list will change reflecting the selected criteria:

    Microsoft Dynamics AX 2012 R3 Development Cookbook

  8. Click on the Advanced Filter/Sort button in the toolbar to inspect how the criteria was applied in the underlying query (note that although changing the filter values here will affect the search results, the earlier created filter controls will not reflect those changes):

    Microsoft Dynamics AX 2012 R3 Development Cookbook

How it works…

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:

  • valueUnlimited(): This method returns a string representing an unlimited query range value, that is, no range at all.
  • value(): This method converts an argument into a safe string. The global queryValue() method is a shortcut for this.
  • valueNot(): This method converts an argument into a safe string and adds an inversion sign in front of it.

Creating a custom instant search filter

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.

How to do it…

Carry out the following steps in order to complete this recipe:

  1. In the AOT, open the MainAccountListPage form and add a new StringEdit control with the following properties to the existing Filter group:

    Property

    Value

    Name

    FilterName

    AutoDeclaration

    Yes

    ExtendedDataType

    AccountName

  2. Override the control’s textChange() method with the following code snippet:
    void textChange()
    {
       super();
     
       MainAccount_ds.executeQuery();
    }
  3. On the same control, override the control’s enter() method with the following code snippet:
    void enter()
    {
       super();
       this.setSelection(
           strLen(this.text()),
           strLen(this.text()));
    }
  4. Update the executeQuery() method of the MainAccount data source as follows:
    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();
    }
  5. In order to test the search, navigate to General ledger | Common | Main accounts and start typing into the Account name filter. Note how the account list is being filtered automatically:

    Microsoft Dynamics AX 2012 R3 Development Cookbook

How it works…

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.

See also

  • The Creating a custom filter control recipe

Summary

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.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here