D365O. Issues with Microsoft Dynamics App for Office setup.

Recently I’ve helped my colleague to setup Word Templates for D365O. We followed Office integration tutorial on the wiki, but it has few gaps.

First of all, it’s not clear where to find share that contains the Microsoft Dynamics App manifest ( step 1.d). Use base URL for the cloud environment, for example, https://topo00dfa4stbobaos.cloudax.test.dynamics.com/

Second step where we stuck for a while is not so obvious. 4.b says that you need to select Template Designer after loading applets. However, there was nothing to select. To fix this issue you actually need to go to D365O -> System Administration -> Setup -> Office app parameters and on “Registered applets” tab check that there is a record for Microsoft.Dynamics.Platform.Integration.Office.WordDesignerApplet in a grid. 

OfficeAppParameters.jpg

If it’s not there click on “Initialize applet registration” button and AX will auto populate the grid.

 

Advertisements

AX 7. Link class instances to a form.

In one of my previous blogs we discussed how to override form control methods or form data source field methods without overlaying using registerOverrideMethod(). But what if we need to link class instance to a form? We have two options: use form extension classes with [ExtensionOf] attribute or register class instance on the form using formRun.registerExtensionInstance() method. Main difference between them is that with extension classes you add new methods or state extending form functionality and using registerExtensionInstance() we link class instance with new logic to a from, so we can have one class that could handle multiple forms. First approach is described in this blog post, so today we will focus on the second one.

Under the hood FromRun class has map variable to store registered instances:

Map formExtensionInstance = new Map(Types::string, Types::Class);

And 3 methods to work with it:

/// <summary>
/// Registers an extension instance.
/// </summary>
/// <param name="_extensionKey">The key for the extension.</param>
/// <param name="_extensionInstance">The instance for the extension.</param>
public void registerExtensionInstance(str _extensionKey, Object _extensionInstance)
{
    formExtensionInstance.insert(_extensionKey, _extensionInstance);
}
/// <summary>
/// Gets a registered extension instance.
/// </summary>
/// <param name="_extensionKey">The key for the extension.</param>
/// <returns>The instance for the extension if one is found.</returns>
public Object getExtensionInstance(str _extensionKey)
{
    return formExtensionInstance.lookup(_extensionKey);
}
/// <summary>
/// Gets a Boolean value indicating if an extension with the specified key is registered.
/// </summary>
/// <param name="_extensionKey">The key for the extension.</param>
/// <returns>true if an exteinsion with the specified key is registered; otherwise; false.</returns>
public boolean hasExtensionInstance(str _extensionKey)
{
    return formExtensionInstance.exists(_extensionKey);
}

There is a common way in standard code how to build extension classes:

  1. Create new class and add variable to store FormRun object, flag that indicates if instance is initialized and other required variables. Let’s add FormStringControl for example.
public class MySampleExtension
{
    FormRun element;
    boolean initialized;

    FormStringControl myFormStringControl;
}
  1. Create new method to initialize FormRun and register current instance on the form.
public void new(FormRun _formRunInstance)
{
    initialized = false;
    element     = _formRunInstance;

    // register this extension with the main form
    element.registerExtensionInstance(classStr(MySampleExtension), this);
}
  1. Create init() method to initialize all required variables and register override if needed.
private void init()
{
    if (initialized)
    {
        return;
    }

    initialized = true;

    myFormStringControl = element.design().controlName(formControlStr(MySampleForm, MyFormStringControl));

    //register overrides here if needed
}
  1. Subscribe to form pre init() event to register extension instance.
[FormEventHandler(formStr(MySampleForm), FormEventType::Initializing)]
public static void MySampleForm_OnInitializing(xFormRun _sender, FormEventArgs _e)
{
    MySampleExtension extensionInstance = new MySampleExtension(sender);
}
  1. Subscribe to form post init() event to initialize extension instance.
[FormEventHandler(formStr(MySampleForm), FormEventType::Initialized)]
{
    // Initialize the instance of this form extension handler now that the controls exist
    FormRun mySampleForm = _sender as FormRun;
    MySampleExtension extensionInstance  = mySampleForm.getExtensionInstance(classStr(MySampleExtension));

    extensionInstance.init();
}
  1. Use extension class
[FormControlEventHandler(formControlStr(MySampleForm, MySampleButton), FormControlEventType::Clicked)]
public static void mySampleButton_OnClicked(FormControl _sender, FormControlEventArgs _e)
{
    if (_sender && _sender.formRun())
    {
        FormRun formRun = _sender.formRun();

        MySampleExtension extensionInstance = formRun.getExtensionInstance(classStr(MySampleExtension));

        extensionInstance.mySampleButtonClicked();
    }
}

public void mySampleButtonClicked()
{
    myFormStringControl.visible(false);
}

That’s pretty much all of it.

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.