9 min read

Building checklists

Anyone who preformed Dynamics AX application installation or upgrade has to be familiar with standard checklists. Normally, a checklist is a list of menu items displayed in logical sequence. Each item represents either mandatory or optional actions to be executed by the user in order to complete the whole procedure. In custom Dynamics AX implementations, checklists can be used as a convenient way to configure non standard settings. Checklists can also be implemented as a part of third-party modules for their initial setup.

In this recipe, we will create a checklist for user-friendly ledger budget setup. The checklist will consists of two mandatory and one optional item.

How to do it…

  1. Open AOT, and create a new class called SysCheckListInterfaceBudget:

    interface SysCheckListInterfaceBudget
    extends SysCheckListInterface
    {
    }
  2. Create three more classes—one for each checklist item, with the following code:
    class SysCheckListItem_BudgetModel
    extends SysCheckListItem
    implements SysCheckListInterfaceBudget
    {
    }
    public str getCheckListGroup()
    {
    return "Setup";
    }
    public str getHelpLink()
    {
    #define.TopicId('AxShared.chm::/html/' +
    '84030522-0057-412C-BFC7-DBEB4D40E5A1.htm')
    ;
    return SysCheckListItem::sharedGuide(#TopicId);
    }
    public MenuItemName getMenuItemName()
    {
    return menuitemdisplaystr(BudgetModel);
    }
    public MenuItemType getMenuItemType()
    {
    return MenuItemType::Display;
    }
    str label()
    {
    return "Models";
    }
    class SysCheckListItem_BudgetRevision
    extends SysCheckListItem
    implements SysCheckListInterfaceBudget
    {
    }
    public void new()
    {;
    super();
    this.placeAfter(classnum(SysCheckListItem_BudgetModel));
    this.indeterminate(true);
    }
    public str getCheckListGroup()
    {
    return "Setup";
    }
    public str getHelpLink()
    {
    #define.TopicId('AxShared.chm::/html/' +
    'AACC4353-C3EB-4982-BB7F-2B36D97FF25B.htm')
    ;
    return SysCheckListItem::sharedGuide(#TopicId);
    }
    public MenuItemName getMenuItemName()
    {
    return menuitemdisplaystr(BudgetRevision);
    }
    public MenuItemType getMenuItemType()
    {
    return MenuItemType::Display;
    }
    str label()
    {
    return "Revisions";
    }
    class SysCheckListItem_Budget
    extends SysCheckListItem
    implements SysCheckListInterfaceBudget
    {
    }
    public void new()
    {;
    super();
    this.addDependency(
    classnum(SysCheckListItem_BudgetModel));
    this.placeAfter(
    classnum(SysCheckListItem_BudgetRevision));
    }
    public str getCheckListGroup()
    {
    return "Create budgets";
    }
    public str getHelpLink()
    {
    #define.TopicId('AxShared.chm::/html/' +
    '6A596E1E-6803-4410-B4E4-EDE4EF44AF6D.htm')
    ;
    return SysCheckListItem::sharedGuide(#TopicId);
    }
    public MenuItemName getMenuItemName()
    {
    return menuitemdisplaystr(LedgerBudget);
    }
    public MenuItemType getMenuItemType()
    {
    return MenuItemType::Display;
    }
    str label()
    {
    return "Budgets";
    }

  3. Create another class for the checklist itself:
    class SysCheckList_Budget extends SysCheckList
    {
    container log;
    }
    protected str getCheckListCaption()
    {
    return "Budget checklist";
    }
    protected str getHtmlHeader()
    {
    return "Budget checklist";
    }
    protected classId getInterfaceId()
    {
    return classnum(SysCheckListInterfaceBudget);
    }
    public void save(
    identifiername _name,
    ClassDescription _description)
    {;
    if (!confind(log, _name))
    {
    log = conins(log, conlen(log)+1, _name);
    }
    }
    public boolean find(
    identifiername _name,
    ClassDescription _description)
    {
    return confind(log, _name) ? true : false;
    }
    static void main(Args _args)
    {;
    SysCheckList::runCheckListSpecific(
    classnum(SysCheckList_Budget),
    true);
    }
  4. Open the SysCheckList class in AOT, and replace its checkListItemsHook() and checkListsHook() with the following code:
    protected static container checkListsHook()
    {
    return [classnum(SysCheckList_Budget)];
    }
    protected static container checkListItemsHook()
    {
    return [classnum(SysCheckListItem_Budget),
    classnum(SysCheckListItem_BudgetRevision),
    classnum(SysCheckListItem_BudgetModel)];
    }
  5. Open the BudgetModel form in AOT, and override its close() with the following code:
    public void close()
    {;
    super();
    SysCheckList::finished(
    classnum(SysCheckListItem_BudgetModel));
    }
  6. Open the BudgetRevision form in AOT, and override its close() with the following code:

    public void close()
    {;
    super();
    SysCheckList::finished(
    classnum(SysCheckListItem_BudgetRevision));
    }
  7. Open the LedgerBudget form in AOT, and override its close() with the following code:
    public void close()
    {;
    super();
    SysCheckList::finished(
    classnum(SysCheckListItem_Budget));
    }
  8. Create a new Display menu item SysCheckList_Budget with the following properties:

Property

Value

Name

SysCheckList_Budget

Label

Budget checklist

ObjectType

Class

Object

SysCheckList_Budget

  1. To test the checklist, run the SysCheckList_Budget menu item from AOT. The following should appear on the right-hand side of the Dynamics AX window:

    Microsoft Dynamics AX 2009 Development Cookbook

  2. Click on the listed items to start and complete relevant actions. Notice how the status icons change upon completion of each task.

    Microsoft Dynamics AX 2009 Development Cookbook

How it works…

The main principle behind checklists is that we have to create a main class, which represents the checklist itself and a number of SysCheckListItem item classes, which act as list items. The relation between the main class and the items is made by the use of an interface, that is, each list item implements it, and the main class holds the reference to it.

In this example, we create an interface SysCheckListInterfaceBudget and specify it in the getInterfaceId() of the main checklist class SysCheckList_Budget. Next, we implement the interface in three SysCheckListItem classes, which correspond to Models, Revisions, and Budgets items in the checklist.

Each SysCheckListItem class contains a set of inherited methods, which allows us to define a number of different parameters for individual items:

  • All initialization code can be added to the new() methods. In this example, we use placeAfter() to determine the position of the item in the list relative to other items, indeterminate() to make item optional and addDependency() to make an item inactive until another specified item is completed.
  • getCheckListGroup() defines item dependency to a specific group. The Budget checklist has two groups, Setup and Create budgets.
  • getHelpLink() is responsible for placing the relevant help link in the form of a question mark next to the item.
  • getMenuItemName() and getMenuItemType() contain a name and a type of menu item, which is executed upon user request. Here, we have Budget model, Budget revisions, and Ledger budget forms respectively in each class.
  • And finally custom labels can be set in label().

Once the items are ready, we create the main checklist class SysCheckList_Budget, which extends the standard SysCheckList. We override some of the methods to add custom functionality to the checklist:

  • getCheckListCaption() sets the title of the checklist.
  • getHtmlHeader() could be used to add some descriptive text.
  • As mentioned before, getInterfaceId() is the place where we specify the name of the checklist item interface.
  • The methods save() and find() are used to store and retrieve respectively the status of each item in the list. In this example, we store statuses in the local variable log to make sure that statuses are reset every time we run the checklist.
  • The static method main() runs the class. Here, we use runCheckListSpecific() of the system SysCheckList class to start the checklist.

The display menu item we created is pointing to the checklist class and may be used to add the checklist to a user menu.

When building checklists, it is necessary to add them and their items to the global checklist and checklist item list. The SysCheckList class contains two methods—checkLists() and checkListItems()—where all system checklists and their items are registered. The same class provides two more methods—checkListsHook() and checkListItemsHook()—where custom checklists should be added. As a part of this example, we also add our budget checklist and its items to the SysCheckList.

Final modifications have to be done in all checklist forms. We call the finished() of the SysCheckList class in the close() of each form to update the status of the corresponding checklist item. In other words, it means that item status will be set as completed when the user closes the form. This code does not affect the normal use of the form when it is opened from the regular menu. Of course, more logic could be added here if the completion of a specific item is not that straightforward.

Also notice that the system automatically adds a link called Information, which describes the checklist statuses:

Microsoft Dynamics AX 2009 Development Cookbook

There’s more…

The checklist in this example stores item statuses per each run. This means that every time you close the checklist, its statuses are lost and are set to their initial states upon checklist start. By replacing save() and find() in SysCheckList_Budget with the following code, we can store statuses permanently in the SysSetupLog table:

public boolean find(
identifiername _name,
ClassDescription _description)
{
return SysSetupLog::find(_name, _description).RecId != 0;
}
public void save(
identifiername _name,
ClassDescription _description)
{;
SysSetupLog::save(_name, _description);
}

In this case, every time the checklist starts, the system will pick up its last status from the SysSetupLog table and allow the user to continue the checklist.

Adding a “Go to the Main Table Form” link

Go to the Main Table Form is a feature of Dynamics AX, which allows users to jump to the main record just by right-clicking on the field and selecting the Go to the Main Table Form option. It is based on table relations and is available for those controls whose data fields have foreign key relationships with other tables.

Because of the data structure integrity, this feature works most of the time. However, when it comes to complex table relations, it does not work correctly or does not work at all. Another example of when this feature does not work automatically is when the form control is not bound to a table field. In such situations, Go to the Main Table Form has to be implemented manually.

In this recipe, to demonstrate how it works, we will modify the Business relations form in the CRM module to make sure that the Employee filter at the top of the form allows users to use the Go to the Main Table Form feature from the context menu.

How to do it…

  1. Open the smmBusRelTable form in AOT, and override jumpRef() of the EmployeeFilter control with:
    public void jumpRef()
    {
    EmplTable emplTable;
    Args args;
    MenuFunction menuFunction;
    ;
    emplTable = EmplTable::find(this.text());
    if (!emplTable)
    {
    return;
    }
    args = new Args();
    args.caller(element);
    args.record(emplTable);
    menuFunction = new MenuFunction(
    menuitemdisplaystr(EmplTable),
    MenuItemType::Display);
    menuFunction.run(args);
    }
  2. To test the result, open CRM | Business Relation Details, make sure an employee number is specified in the Employee filter, and right-click on the filter control. Notice that the Go to the Main Table Form option, which will open the Employee form, is now available:
    Microsoft Dynamics AX 2009 Development Cookbook

How it works…

Normally, the Go to the Main Table Form feature is controlled by the relations between tables. If there are no relations or the form control is not bound to a table field, then this option is not available. But, we can force this option to appear by overriding the control’s jumpRef() method.

In this method, we have to add code that opens the relevant form. This can be done by creating, initializing, and running a FormRun object, but the easier way is to simply run the relevant menu item. In this recipe, the code in jumpRef() does exactly that.

First, we check if the value in the control is a valid employee number. If yes, then we run the Display menu item EmplTable with an Args object containing the proper employee record. The rest is done automatically by the system, that is, the Employee form is opened with the employee information.

LEAVE A REPLY

Please enter your comment!
Please enter your name here