Tuesday, March 3, 2020

Extend the packed list of standard class variables

This topic shows how to extend the list of packable variables of the existing class which realize SysPackable interface or not, the class needs to have only pack and unpack methods.
For example, you want to extend the SrsPrintDestinationSettings class which have a pack and unpack method, but it did not realize SysPackable interface and add a new print setup for selecting Email Template.
The following example shows how to implement this scenario:

[ExtensionOf(classStr(SrsPrintDestinationSettings))]
public final class MySrsPrintDestinationSettings_Extension 
{
    private SysEmailSystemId    emailTemplateId;

    #define.CurrentVersion(1)
    #localmacro.CurrentList
        emailTemplateId
    #endmacro

    public SrsReportEMailDataContract parmEMailContract(SrsReportEMailDataContract _emailContract)
    {
        next parmEMailContract(_emailContract);

        if(!emailContract.parmBody() && emailTemplateId)
        {
            SysEmailMessageTable sysEmailMessageTable = SysEmailMessageTable::find(emailTemplateId, CompanyInfo::languageId());
            emailContract.parmBody(SysEmailMessage::stringExpand(sysEmailMessageTable.Mail, SysEmailMessage::createStandardPlaceholders()));
        }

        return emailContract;
    }

    [DataMemberAttribute]
    public SysEmailSystemId myParmEmailTemplateId(SysEmailSystemId _emailTemplateId = emailTemplateId)
    {
        emailTemplateId = _emailTemplateId;
        return emailTemplateId;
    }

    public container pack()
    {
        container packedClass = next pack();
        return SysPackExtensions::appendExtension(packedClass, classStr(MySrsPrintDestinationSettings_Extension), this.packMyExtension());
    }

    public boolean unpack(container _packedClass)
    {
        boolean result = next unpack(_packedClass);

        if (result)
        {
            container packedClassExtension = SysPackExtensions::findExtension(_packedClass, classStr(MySrsPrintDestinationSettings_Extension));
            //Also unpack the extension
            if (!this.unpackMyExtension(packedClassExtension))
            {
                result = false;
            }
        }

        return result;
    }

    private container packMyExtension()
    {
        return [#CurrentVersion, #CurrentList];
    }

    private boolean unpackMyExtension(container _packedClass)
    {
        Integer version;

        if (typeOf(conPeek(_packedClass, 1)) == Types::Integer)
        {
            version = conPeek(_packedClass, 1);
        }
        else
        {
            version = RunBase::getVersion(_packedClass);
        }
        
        switch (version)
        {
            case #CurrentVersion:
                [version, #currentList] = _packedClass;
                break;
            default:
                return false;
        }
        return true;
    }

}

To avoid collisions with other eventual extensions, I followed these best practices:

  • Prefix members and methods. In the example, the prefix “my” is used. This practice is important because it helps prevent name clashes with other extensions and future versions of the augmented class.
If you want to extend the RunBase class, docs from MS shows how a RunBase class can be augmented end to end https://docs.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/extensibility/extend-runbase-class