Tutorial: Form “easy filters”

A customization type that pops up like a weeds in any given project is adding “easy filters” to forms. For some reason, users love em!*

Since I’ve done this bleeding super cool(!) customization so super-mega-ultra often, I’ve settled on a pattern that I like. But enough rambling! Here’s how I do “easy filters”.

Overview

1- Declare a form class-level variable, using the same datatype as the field to filter.

2 – Create an edit method that allows the user to modify the class variable.

3 – Modify the form datasource’s executeQuery() method to apply the value to the query before execution.

Reason why I like it

  • The code ends up in rather easy-to-see locations (rather than buried underneath controls [AutoDeclared controls, rawr!]).
  • The “filter” value can easily be modified from code.
  • Low impact on overall form design.

Walkthrough

This example will demonstrate modifying the Checks form [Cash and bank management > Common > Checks] to add an “easy filter” on the Date field.

Checks form

First step is to identify the datatype of the field we wish to filter on. To do that, we can identify the ‘Date’ field by [right-click on form > Personalize > find ‘Date’ control in the grid > System name], look up the table in the AOT, and check the field properties.

personalize form

As it turns out, the field uses the TransDate Extended data type. (If you wish the filter control to have a different label than the Extended data type, it is worth considering creating a custom Extended data type which extends TransDate with the label you desire. You can also just apply a label to the form control, and I probably wouldn’t tease you.)

Modify the form’s classDeclaration method to declare a variable for the filter value.

public class FormRun extends ObjectRun
{
 BankAccountTable bankAccountTable;
 boolean canPrintNonNegotiableCheque;
// Supreme filter --> 
 TransDate SupDateFilter;
// Supreme filter <-- 
}

Create an edit method in the form’s methods node.

// Supreme filter -->
public edit TransDate editSupDateFilter(boolean _set, TransDate _value)
{
 if (_set == true)
 {
     SupDateFilter = _value;
     BankChequeTable_ds.executeQuery();
 }
 
 return SupDateFilter;
}
// Supreme filter <--

Note the call to formDataSource.executeQuery(). This is what causes the form to update after the value is modified.

Create a DateEdit control in the form’s Design node. The easiest way is to drag the editSupDateFilter method into the Design node, although you can also create the DateEdit control manually and set the DataMethod property to “editSupDateFilter”.

Checks form AOT

Override the executeQuery() method on the BankChequeTable form datasource.

public void executeQuery()
{
// Supreme filter --> 
 QueryBuildRange SupTransDateR;
 
 SupTransDateR = SysQuery::findOrCreateRange(BankChequeTable_ds.queryBuildDataSource(), fieldNum(BankChequeTable, TransDate));
 if (SupDateFilter != dateNull())
 {
     SupTransDateR.value(SysQuery::value(SupDateFilter));
 }
 else
 {
     SupTransDateR.value(SysQuery::valueUnlimited());
 }
// Supreme filter <-- 
 super();
}

Note that the code is before the call to super(). This is because super() on a form datasource will execute the query, and the query range needs to take effect before that point.

Also notice the usage of SysQuery::findOrCreate(). This is superior (in this case) to QueryBuildDataSource.addRange() because the intention here is for there to be only one range on the TransDate field. This way there’s no need to override BankChequeTable_ds.init() or declare another form-level variable for a QueryBuildRange.

The reason for the if block is to handle the case where the date field is unspecified. In that condition, the range is cleared in order to display all records. The usage of SysQuery::value() is good practice any time values are getting put into a range, because it will handle datatypes correctly and will escape any special characters, so it avoids unexpected results due to e.g. wildcards. The SysQuery::valueUnlimited() call is just an explicit way to declare an “empty” (open-ended) range, since passing an empty string into SysQuery::value() will filter explicitly on blank values.

So that’s it. Easy filters, no problem.

Checks form filtered

And as expected, the query editor for the form looks like this:

Checks query editor

Other considerations

If the form gets launched from another form, it may be prudent to disable the “easy filter” functionality, because it would be unexpected from a user perspective to not see any records when launching the form via ‘View details’ from a different form. This mainly matters if the “easy filter” has a default value (rather than being open-ended).

It’s also worth considering “locking” the QueryBuildRange so the user cannot change it to a misleading value. That can be done with a single line (after the if/else block):

SupTransDateR.status(RangeStatus::Locked);

The flip side of that is that it makes it harder for a user to enter more advanced criteria, such as a range or comma-delimited list of dates. (Though it’s technically still possible if the user knows to type the field name in the Field value on the Query editor form.)

*I usually don’t think form “easy filters” are the best sort of customization to do because the functionality already exists (via Advanced Filter/Sort). It’s also easy to create unexpected results when the form is dynamically linked or when the user uses Advanced Filter/Sort in conjunction with the “easy filter”. If I can get my way I advise using the user-defined queries instead, though admittedly this sort of filter can be fairly convenient for the user.

Advertisements

Financial Dimension backing entities

One fairly common modification request I’ve gotten since the onset of Dynamics AX 2012 is to create new Financial Dimension backing entities. Luckily, they’re fairly easy to create and can be a rather cost-effective customization.

I’m referring to the ‘Use values from’ field in General ledger > Setup > Financial dimension > Financial dimensions. (Or the DimensionDetails form for you nerds developers.)Financial dimensions formThe options in the ‘Use values from’ field allow a user to configure a Financial dimension to use values from a selection of records from a table in Microsoft Dynamics AX. The value of ‘Site’ is not out-of-the box, but here’s how to add it!


Note: naming convention is extremely important here, so pay attention or debug at your peril!

1 – Create a new view. It has to be named starting with ‘DimAttribute’DimAttribute view

2 – The DataSource has to be named ‘BackingEntity’

3 – There must be three fields named (exactly) ‘Key’, ‘Name’, and ‘Value’.

Key refers to the Record Id of the underlying table
Name refers to the long identifier or description of the record
Value is the user-friendly key value.

So in this case,

Key = InventSite.RecId
Name = InventSite.Name
Value = InventSite.SiteId

4 – Set some properties on the new view. This is more or less business as usual here if you’ve created custom tables and views before. The Label is important if you want user-friendly text (which you will, obviously) in the ‘Use values from’ selection.

DimAttribute view properties

 

The Backing entities get cached the first time the Financial dimensions form opens. (So even if you did everything correctly you may not see the new value.) In order to clear the cache so you see your new backing entity, create a class (or job if you’re a truly Supreme developer) and execute:

DimensionCache::clearScope(DimensionCacheScope::DimensionEnabledTypes);

(Or you could restart the client, then the AOS, then the Server, then delete your caches and so on and so forth. You know, the things you do when you’re not sure how things work.)

In case you need to debug to figure out what you messed up what step of the instructions you’ve missed, here’s the call stack for the Dimension Attribute creation when the Financial dimensions form is launched.

dimAttribute call stack

And the rest is just functional setup, which as we all know is not my job.