AX 7. Trick to override method in derived class without overlaying.

Scenario:

The VendDocumentLineType class has several subclasses that handle specific types of vendor document lines. We want to change a behaviour for invoice lines and implement new logic for Purchase Unit field defaulting.

Solution using overlaying:

Overlay VendDocumentLineType_Invoice class and override determineDefaultPurchUnit method.

Solution without overlaying:

venddocumentlinetypehierarchy

1) Create class derived from VendDocumentLineType_Invoice:

[Form]
/// <summary>
/// The <c>VendDocumentLineType_Invoice_Sample</c> class is used for validation and applying default values to invoice lines.
/// </summary>
class VendDocumentLineType_Sample extends VendDocumentLineType_Invoice
{
}

2) Override determineDefaultPurchUnit method:

protected PurchUnit determineDefaultPurchUnit()
{
    // implement new behaviour
    return VendParameters::find().DefaultPurchUnit_Sample;
}

3) Create post event handler for VendDocumentLineType constructor to replace VendDocumentLineType_Invoice with new implementation:

/// <summary>
/// Handles events raised by <c>VendDocumentLineType</c> class.
/// </summary>

public static class VendDocumentLineTypeEventHandler_Sample
{
    /// <summary>
    /// Post event handler for VendDocumentLineType.createFromTable(...)
    /// </summary>
    /// <param name="_args"></param>
    [PostHandlerFor(classStr(VendDocumentLineType), staticMethodStr(VendDocumentLineType, createFromTable))]
    public static void VendDocumentLineType_Post_createFromTable(XppPrePostArgs _args)
    {
        // get params from args
        VendDocumentLineMap vendDocumentLineMap = _args.getArg(identifierStr(_vendDocumentLineMap));
        PurchLine           purchLine           = _args.getArg(identifierStr(_purchLine));
        PurchParmUpdate     purchParmUpdate     = _args.getArg(identifierStr(_purchParmUpdate));

        // construct and init new object
        VendDocumentLineType strategy = VendDocumentLineType_Invoice_Sample::createFromTable(vendDocumentLineMap, purchLine, purchParmUpdate);

        // replace return value only if new strategy is created; otherwise use standard
        if (strategy)
        {
           // replace return value with new implementation
            _args.setReturnValue(strategy);
        }
    }

}

4) Implement methods that will instantiate and initialize VendDocumentLineType_Invoice_Sample class:

/// <summary>
/// Constructs a new instance of a <c>VendDocumentLineType_Invoice_Sample</c> class derivative.
/// </summary>
/// <param name="_vendDocumentLineMap">
/// A <c>VendDocumentLineMap</c> record.
/// </param>
/// <param name="_purchLine">
/// A <c>PurchLine</c> table record that is used when you apply the default values; optional.
/// </param>
/// <param name="_purchParmUpdate">
/// A <c>PurchParmUpdate</c> table record that is used when you apply the default values; optional.
/// </param>
/// <returns>
/// A <c>VendDocumentLineType_Invoice_Sample</c> class.
/// </returns>
public static VendDocumentLineType_Invoice_Sample createFromTable(VendDocumentLineMap _vendDocumentLineMap, PurchLine _purchLine = null, PurchParmUpdate _purchParmUpdate = null)
{
    VendDocumentLineType_Invoice_Sample strategy;

    // VendDocumentLineType_Invoice is created only for this 2 types
    if (_vendDocumentLineMap.Ordering == DocumentStatus::Invoice ||
        _vendDocumentLineMap.Ordering == DocumentStatus::ApproveJournal)
    {
        strategy = new VendDocumentLineType_Invoice_Sample();
        strategy.init(_vendDocumentLineMap, _purchLine, _purchParmUpdate);
    }

    return strategy;
}

public void init(VendDocumentLineMap _vendDocumentLineMap, PurchLine _purchLine, PurchParmUpdate _purchParmUpdate)
{
    // code copied from VendDocumentLineType::createFromTable to init new instance
    this.vendDocumentLineMap(_vendDocumentLineMap);
    this.purchLine(_purchLine);
    this.physicalStrategy(VendDocumentLineTypePhysical::createFromTable(this, _vendDocumentLineMap));
    this.purchParmUpdate(_purchParmUpdate);
}

You can apply this approach to any class hierarchy in AX that has construct method (like SalesLineType, PurchLineType, etc.) when you need to override methods in derived class to implement new behaviour.

Advertisements

AX 7. Breaking dependencies. Part 2.

In the first part we looked how to use SysPluginFactory, today we will talk about event delegates.

Delegates were introduced in AX 2012 but were rare used by standard code and partners.

In AX 7 delegates obtained second life.  They are widely used by MS to create customization points for customers and partners. As we can see number of them is constantly growing with each update release.

Another use of delegates is a situation when you want to access a code from another model without referencing it, for example accessing the code in a higher model from a lower model.

Create new delegate:

delegate void validateAmountDelegate(Amount _amount, EventHandlerResult _res)
{
}

Delegate does not have return value and method body.

Subscribe to:

[SubscribesTo(classStr(MyClass), delegatestr(MyClass, validateAmountDelegate))]
public static void validateAmount(Amount _amount, EventHandlerResult _res)
{
    if (amount > 0)
    {
        _res.result(true);
    }
    else
    {
        _res.result(false);
    }
}

Method signature should be the same for subscriber and delegate.

Invoke delegate:

EventHandlerResult result = new EventHandlerResult();

this.validateAmountDelegate(amount, result);

boolean res = result.result();

 

To read about usage scenarios and find out more details please refer to AX wiki
https://ax.help.dynamics.com/en/wiki/delegates-for-migration/

AX 7. How to override form control methods using extensions.

In one of the previous posts we discussed how to override methods on form data source fields (AX 7. How to override form data source field methods without overlaying.). Today we will talk about form controls.

In AX 7 we have a bunch of events to subscribe on a form control level:

FormControlEvents

Let’s change a lookup, because it is a quite common customization.

/// <summary>
/// Adds a lookup to the <c>CustAccount</c> control on <c>SalesTable</c> form.
/// </summary>
/// <param name="_sender">The source of the event.</param>
/// <param name="_e">Arguments of the OnLookup event.</param>
[FormControlEventHandler(formControlStr(SalesTable, CustAccount), FormControlEventType::Lookup)]
public static void SalesTable_CustAccount_OnLookup(FormControl _sender, FormControlEventArgs _e)
{
    SysTableLookup sysTableLookup = SysTableLookup::newParameters(tablenum(CustTable), _sender);
    Query query = new Query();

    sysTableLookup.addLookupfield(fieldnum(CustTable, AccountNum), true);
    sysTableLookup.addLookupfield(fieldnum(CustTable, Party));
    sysTableLookup.addLookupMethod(tableMethodStr(CustTable, nameAlias));
    sysTableLookup.addLookupfield(fieldnum(CustTable, OurAccountNum));

    QueryBuildDataSource qbds = query.addDataSource(tablenum(CustTable));
    sysTableLookup.parmQuery(query);

    sysTableLookup.performFormLookup();

    FormControlCancelableSuperEventArgs ce = _e as FormControlCancelableSuperEventArgs;

    //cancel super() to prevent error.
    ce.CancelSuperCall();
}

Please note that execution of original lookup should be cancelled by FormControlCancelableSuperEventArgs.CancelSuperCall() otherwise AX will throw an error “More than one form was opened at once for the lookup control.”

AX 7. How to override form data source field methods without overlaying.

It is a common customization task to override standard form data source field methods like jumpRef(), modified() or  validate().

Recommended approach in AX 7 is to avoid overlaying and use extensions as much as possible. To achieve this new events were introduced. However on form data source field level we have only 3 available events:

SalesTable

We can try to use form controls instead of form data source fields but this approach has several cons:

  • User can add new control using form personalization and this control won’t support overridden logic. It could be critical if you are restricting field lookup values or adding validations.
  • One form could have several controls that refers to one data source field so you have to duplicate your code.
  • Number of delegates are limited as well. Microsoft will add more later 🙂

For example we want to add custom jumpRef() and validate() methods to itemId field on sales order form. To achieve this we have to either overlay it or use next approach.

First of all we will create class to handle method overrides.

/// <summary>
/// Handles events raised by <c>SalesTable</c> form.
/// </summary>
public class SalesTableEventHandler
{
    /// <summary>
    /// Post event handler for <c>SalesTable</c> <c>SalesLine</c> Initialized event.
    /// </summary>
    /// <param name=“_sender”></param>
    /// <param name=“_e”></param>
    [FormDataSourceEventHandler(formDataSourceStr(SalesTable, SalesLine), FormDataSourceEventType::Initialized)]
    public static void SalesLine_OnInitialized(FormDataSource _sender, FormDataSourceEventArgs _e)
    {
        var overrides = SalesTableFormExtensionOverrides::construct();

        _sender.object(fieldNum(SalesLine, ItemId)).registerOverrideMethod(methodStr(FormDataObject, jumpRef),
            methodStr(SalesTableFormExtensionOverrides, itemId_OnJumpRef), overrides);

        _sender.object(fieldNum(SalesLine, ItemId)).registerOverrideMethod(methodStr(FormDataObject, validate),
            methodStr(SalesTableFormExtensionOverrides, itemId_OnValidate), overrides);
    }
}

Then we will create new event handler class and subscribe to OnInitialized event of SalesLine data source.

/// <summary>
/// Contains methods which are used to override <c>SalesLine</c> data source field methods.
/// </summary>
public class SalesTableFormExtensionOverrides
{
    protected void new()
    {
    }

    /// <summary>
    /// Constructs a new instance of <c>SalesTableFormExtensionOverrides</c> class.
    /// </summary>
    /// <returns>
    /// A <c>SalesTableFormExtensionOverrides</c> class.
    /// </returns>
    public static SalesTableFormExtensionOverrides construct()
    {
        return new SalesTableFormExtensionOverrides();
    }

    /// <summary>
    /// Provides the open main table functionality for an item.
    /// </summary>
    /// <param name ="_targetField"> The <c>FormDataObject</c> where the jumpRef is triggered.</param>
    public void itemId_OnJumpRef(FormDataObject _targetField)
    {
        InventTable::jumpRefItemId(_targetField.getValue(), OpenMode::Edit);
    }

    /// <summary>
    /// Checks whether <c>ItemId</c> is valid.
    /// </summary>
    /// <param name = "_targetField"> The <c>FormDataObject</c> where the Validate is triggered.</param>
    public boolean itemId_OnValidate(FormDataObject _targetField)
    {
        //emulate super() call. Comment out to skip.
        boolean ret = _targetField.validate();

        if (ret)
        {
            //custom validation here.
        }

        return ret;
    }
}

That’s all. Using this approach you can override any data source field method.