Saturday, January 17, 2015

Tutorial: InventDimCtrl_Frm classes and the amazing things you can do with their help

Preamble

I was making some adjustments to one of the forms in the application today. Specifically, it was the PdsCustSellableDays form, which is a setup form you can open for a selected customer through the Sell\Setup\Sellable Days action pane button. You can read more about this functionality here:

Working on these changes brought back the memory of what it is that I have always enjoyed most about developing customizations in AX: Making a lot of difference with only a few small targeted code changes.

So I was inspired to use this as an example for describing some of the capabilities of the InventDimCtrl class hierarchy. There are a lot of classes in the hierarchy, targeted to various forms in the application that display Inventory dimensions one way or the other. The main purpose of this class hierarchy is to control the display and behavior of the inventory dimensions as well as the “Dimension display” button that is normally also present on such forms.

Most of you probably know that the way AX developers early on decided to handle inventory dimensions is through a “reference” table InventDim, and a lot of generically written code that would heavily exercise reflection and traversing form controls. This seemed like a good idea at that time, I bet, but over the years, in my opinion, it got completely out of control, and now is a pretty large overhead for almost all inventory related operations in our product. But, at the same time, it provides a nice level of flexibility to how dimensions are displayed on a particular form.

That being said, let’s start from the beginning and briefly explain the existing form behavior and the things I was set to change in it.

Existing behavior and task

Overview of the existing behavior

  • Only product dimensions are displayed in the main grid
  • The relationship to the products is implemented via a “standard” pattern of TableGroupAll. So you can set up Sellable days for a specific item, an item group, or all items.
  • Viewing Inventory\Dimension display shows the standard dialog with all inventory dimensions. However, storage and tracking dimension check-boxes are disabled for editing.
  • Upon adding a grid row and selecting a released product master, all product dimension values are shown, not just those from the released product variants.
  • Only the product dimensions active for the selected product are enabled for editing. If Item code is set to Group or All, all the dimensions are disabled for editing.

Original version of the form

Task

  • Storage and tracking dimensions cannot be selected in Dimensions Display, so should not be shown at all
  • Lookup for configuration and other product dimensions should only show those relevant for the released product master.

 

Explanation

OK, so let’s take a closer look at how such types of forms are built

  • A form-level variable of type InventDimCtrl_Frm is declared. This object is used to control the behavior of the inventory dimension controls on the form.
  • A form method called inventDimSetupObject simply returns the above reference. Through this method other AX forms and classes can access the current state of the form when it comes to the dimensions
    • This includes information about which inventory dimensions are visible in the grid, which of them are enabled for editing, which of them should be marked as mandatory, which of them should be shown when the Dimension Display button is pressed, whether or not the “large” version of the dialog is to be shown (that includes On-hand information like Closed/ClosedQty), whether Item number should be shown on the Dimension Display dialog, etc.You can browse the methods on this class to get a feeling for what else it can do.
    • Since we can only select product dimensions in the form, we want spread that over to the Dimension Display dialog as well.
  • A form method called updateDesign() exists, with a certain pattern for handling various “events” that happen on the form. The main purpose here is to update the inventDimSetupObject with the latest information from the form, and through it refresh the display of the inventory dimension controls.
    • In the PdsCustSellableDays form this happens when the form is opened (Init), whenever the item code or reference is changed (FieldChange), whenever the active record is changed (Active) or whenever the parent customer record is changed in turn refreshing the Sellable Days view (LinkActive).
      • This method is then called from the appropriate event handles or the form, as you can see if you open the form code.
    • In PdsCustSellableDays the intention is to only enable the active product dimensions for the selected item, and disable all of them if we selected all items or an item group instead. But only product dimensions are to be shown ever. This is achieved by using the following methods:
      • parmDimParmEnabled(InventDimParm) controls which dimensions can be edited
      • parmDimParmVisible(InventDimParm) controls which dimensions are shown
      • parmDimParmVisibleGrid(InventDimParm) controls which dimensions are shown in the grid.
      • parmDimParmLockedRightClick(InventDimParm) controls which dimensions can be selected through the Dimension Display button
      • formSetControls(boolean) actually executes the update of the inventory dimension controls based on the above settings. The first parameter controls whether the form is locked for the duration of the update (preventing unnecessary flicker, especially in cases where controls are being made visible/invisible in the grid).
      • formActiveSetup(InventDimGroupSetup), which is not used here, but is frequent in other forms, should be called when the active record is changed, and resets the enabled fields based on the now selected item dimension group information.
      • dimFieldsActive(InventDimParm) that is also used in the form is actually an inquiry-type method, and does not impact the state of the object in any way. Its purpose is to return a container of active dimension field IDs based on an InventDimParm buffer.
  • Lookups for all the product dimensions are based on the form called InventProductDimensionLookup, which is another example of generic handling of inventory dimensions, and has all the above properties as well.
    • It can show up to 3 tab pages, including the selected product dimension values, all relevant product variants and the on-hand information
    • In the current version of the form, all existing product dimension values are shown, regardless of the product. This is one of the things we need to fix.

 

Changes made and result

OK, so in order to accomplish the 2 tasks we set for ourselves above, we only need to change code in 3 places on the form. Let’s walk through each one in turn.

  • Modify the classDeclaration, and replace the type InventDimCtrl_Frm with InventDimCtrl_Frm_ProductDim. If we take a look at this class (it is only available starting from AX 2012 R3 CU8, so I am also including the code for this class below, for reference), we can see a few methods were overridden:
    • mustEnableField() makes sure that only the active product dimensions based on dimension group setup are enabled. This is used by the formActiveSetup method, and is not as interesting for our example.
    • mustShowGridField() makes sure that all product dimensions are shown in the grid, and only product dimensions. This was controlled by explicitly calling parmDimParmVisibleGrid() before. We’ll remove that code, as you’ll see later.
    • setupShowAllProductDimensions() – this method is important for our example. This method, if it returns true, will ensure only product dimensions are shown on the Dimension Display dialog.
  • Modify the updateDesign method as below
    • Remove the lock/unlock method calls, since this is already going to happen within formSetControls().
    • Initialize the inventDimSetupObject using the right class InventDimCtrl_Frm_ProductDim
    • Remove the variables inventDimParmDefault and inventDimParmDisenabled, and all code related to them. This code is now redundant.
    • Remove the call to dimFieldsActive. As I mentioned above, it does not do anything useful for us.
    • We can also add 1 extra line clearing the inventDim values that do not apply – this is important for when changing the item number where we already have product dimensions selected. Just a bug fix, not really related to this tutorial.
  • Add a new method itemId(), which returns the currently selected item number
    • This is necessary for the product dimension lookups to function properly. The lookup tries to find the item information from the calling form through the data source joined to InventDim as a parent, assuming it has an ItemId field. While that is true in most cases, our form uses a TableGroupAll pattern, so the field is a bit different. By creating an itemId() method on the form, we ensure that the lookup can find the right item number to reference when retrieving product dimension group information.
    • In updateDesign, after initializing the object, invoke parmSkipOnHandLookUp(true), which will ensure that the on-hand tab page is not shown on the lookups. Since this is not necessary for this type of setup form, it’s a good idea, since it will improve the lookup loading time. This is also demonstrating how the lookup form uses the inventDimSetupObject of the calling form to get the expected behavior settings.
    • The lookup will now also show the Combinations tab page, if the selected product has more than 1 product dimension active.

That’s it. With very few changes, we were able to drastically change the look and feel of this form. Here’s how it looks now:

Updated form behavior

 

Download the project

You can download the project with the changes I have made from my OneDrive.