Caching display methods in forms

This post is probably not the most earth shaking news but still a relevant issue to visit once in a while.

When developing or customising forms we often have to put in display methods to show the user data not available directly from the datasource. That can be either data from related tables or the result of calculations. For each refresh of data the display fields will be updated and the content recalculated. This can result in a significant performance plunge which easily can be fixed.

I did a quick test and in the Customer group form with 7 records a display method is called 27(!) times just opening the form. If your display method is the first column in the grid you can actually add a couple of clicks to that number.

So there definitely motivation enough here to spent a few moments on this. So let us start by separating the display method into two categories: Form methods and table methods.

Caching table methods

This is the easy one. Go to the init method on the datasource and add the following piece of code after the super() call:

CustGroup_ds.cacheAddMethod(tableMethodStr(CustGroup, cacheTestTableCached));

That is it. Your table display method is now cached and in the above example goes from 27 to 7 clicks.

Caching form methods

In this example we have a display method on the CustGroup datasource looking like this:

display Description paymTermDescription(CustGroup _custGroup)
{
    return PaymTerm::find(_custGroup.PaymTermId).Description;
}

With the earlier mentioned example this data lookup will be performed 27 times for 7 records. Here we need to do a couple of lines of coding to do make this work. First of all we need a Map object in the form declaration method:

public class FormRun extends ObjectRun
{
    Map cacheDemo;
}

Then we change our display method to look like this:

display Description paymTermDescriptionCached(CustGroup _custGroup)
{
    if (! cacheDemo)
    {
        cacheDemo = new Map(Types::String, Types::String);
    }
    if (! cacheDemo.exists(_custGroup.PaymTermId))
    {
        cacheDemo.insert(_custGroup.PaymTermId, PaymTerm::find(_custGroup.PaymTermId).Description);
    }
    return cacheDemo.lookup(_custGroup.PaymTermId);
}

Let us break down what the method does now.

  1. First we make sure that the Map object cacheDemo is instantiated. This could be done in the forms init method instead.
  2. Then we check if we already have the value we need in the cache. This is done based on the PaymTermId field on the CustGroup record. If it is not found we add it to the cache.
  3. Finally, we lookup the value from the cache and returns it. We know that the value always is in the cache so no need to do an exists check again.

This means that we still hit the display method as many times as before; but the calculation/data search part is only executed once per Payment term id. In this case that makes it 6 clicks since to customer groups had the same Payment term id.

Advertisements

Including financial dimensions in a form

It is fairly easy to show and maintain financial dimensions from a record on a form. Here is a quick demo:

Create a new table called DEMO_FinancialDimInForm. Add 2 fields:

AccountNum (Extended data type = AccountNum)

DimensionDefault  (Extended data type = DimensionDefault)

Since this is just for showing off I will leave it at this

Demo_LedgerDim1

Now create a form with the same name based on the template SimpleListDetails. Add the DEMO_FinancialDimInForm table as datasource.

Add the AccountNum field to the grid. Add it to the details tab to make sure we can edit it when creating records. Create a new empty tab DimensionTab. It should end up looking more or less like this:

Demo_LedgerDim2

And now let’s move on to the cool part. Add this variable declaration to ClassDeclaration on the form:

    DimensionDefaultingController   dimensionDefaultingController;

This object does the work in handling the dimensions. And it does it without that much work from our site. In the init method add this after the super() call:

    dimensionDefaultingController = DimensionDefaultingController::constructInTabWithValues(true, true, true, 0, this, DimensionTab, "@SYS138487");
    dimensionDefaultingController.parmAttributeValueSetDataSource(DEMO_FinancialDimInForm_ds, fieldStr(DEMO_FinancialDimInForm, DimensionDefault));
    dimensionDefaultingController.pageActivated();

The first line instantiates the object which takes – among others – the formRun and the empty tab page as parameters.

The second line tells the class what data source and which field on the table it should use when handling the financial dimensions.

The third activates the class. If you have the dimensions on a separate tab page on your form you can call the pageActivated method when that tab page is activated to increase performance.

We are now getting close to the finish line. You need to add one line of code to the write, delete and active method on the data source. They should look like this:

public void delete()
{
    super();
     dimensionDefaultingController.deleted();
 }
public void write()
{
    dimensionDefaultingController.writing();
    super();
}
public int active()
{
    int ret;
    ret = super();
    dimensionDefaultingController.activated();
    return ret;
}

Done. Now open the form and watch the magic:

Demo_LedgerDim3

Adding tables to forms based on queries (List pages)

Form based on a query in the datasource cannot show new tables added to the query. All though the added datasource is visible in the query on the form it is not included in the list of available datasources from which you can drag’n’drop fields into the grid.

To make that happen you need to do a little massaging of the form outside AX.

Export the form and edit the export file in Notepad. Locate the definition of the datasources and add the following text (remember to change the name and table to whatever fits your current situation).

The text should be inserted immediately after the other data source objects and before the derived data source objects:

   DATASOURCE

      OBJECTPOOL
        PROPERTIES
          Name                #<DatasourceName>
          Table               #<TableName>
          AllowEdit           #No

          AllowCreate         #No
          AllowDelete         #No
          OnlyFetchActive     #Yes

        ENDPROPERTIES

        FIELDLIST

        ENDFIELDLIST

      ENDOBJECTPOOL

      METHODS

      ENDMETHODS

      LINKTYPES

        DATASOURCE

          PROPERTIES

            Name                #<DataSourcename>

            ParentDataSource    #<ParentDatasourceName>

          ENDPROPERTIES

      ENDLINKTYPES

    ENDDATASOURCE
Save the file and import it into AX.
It is not pretty but it works. 🙂