Showing posts with label packunpack. Show all posts
Showing posts with label packunpack. Show all posts

Monday, June 12, 2023

Complete pack/unpack pack for RunBaseBatch adventure

Usage

We can use pack-unpack methods in RunBaseBatch classes and all its descendants to save and/or store the state of an object, and then later re-instantiate the same object.

We need it, for example, to use the user's input for any batch job: some parameters can be used when a batch job is executed on the server. 

Documentation

Microsoft doc article is here.

Quote: "A reinstantiated object is not the same object as the one that was saved. It is just an object of the same class whose members contain the same information." 

You need to teleport your objects without a fly or other bugs.

Case examples

Basics

Basic example of such a class is Tutorial_RunbaseBatch.

    TransDate       transDate;
    CustAccount     custAccount;

    #define.CurrentVersion(1)
    #localmacro.CurrentList
        transDate,
        custAccount
    #endmacro
    public container pack()
    {
        return [#CurrentVersion,#CurrentList];
    }

    public boolean unpack(container packedClass)
    {
        Version version = RunBase::getVersion(packedClass);
        ;
        switch (version)
        {
            case #CurrentVersion:
                [version,#CurrentList] = packedClass;
                break;
            default:
                return false;
        }

        return true;
    }

Containers

Basically you can add a container directly to pack() or convert a container to a string and save the latter. Opposite conversion required for restoring. 

container                   siteIds;
str                         siteIdsStr;

#define.CurrentVersion(1)
#define.Version1(1)
#localmacro.CurrentList
  siteIdsStr
#endmacro

public boolean getFromDialog()
{
  ...
  // convert it to string for pack/unpack
  siteIdsStr  = con2Str(siteIds);
  
  return super();
}

public void dialogPostRun(DialogRunbase _dialog)
{
    FormRun formRun;

    super(dialog);

    formRun = _dialog.dialogForm().formRun();

    if (formRun)
    { 
        // if we restored from last values
        if(siteIdsStr)
        {
            //then we convert the string to container
            siteIds = str2con(siteIdsStr);
        }
    }
}

See the following article for the complete usage https://alexvoy.blogspot.com/2014/11/dialog-field-with-multiple-choice.html

Tables

Save an unique key value, say, RecId to find a target buffer after unpack()

Collection types

Maps, lists, and sets are equipped with method pack, which you can use directly for packing.

        // collections
        List        list;
        Map         map;
        Set         set;
        // saved states
        container   packedCont;

        packedCont = list.pack();
        list = List::create(packedCont);

        packedCont = map.pack();
        map = Map::create(packedCont);

        packedCont = set.pack();
        set = Set::create(packedCont);

Queries

How to pack/unpack a query among other parameters, you can find in IntrastatTransfer class.

    QueryRun        queryRunIntrastatTransfer;

    container pack()
    {
        return [#CurrentVersion,#CurrentList,queryRunIntrastatTransfer.pack()];
    }

    boolean unpack(container packedClass)
    {
        Integer      version      = conPeek(packedClass,1);
        container    packedQuery;

        switch (version)
        {
            case #CurrentVersion:
                [version,#CurrentList,packedQuery]      = packedClass;
                if (packedQuery)
                {
                    queryRunIntrastatTransfer = new QueryRun(packedQuery);
                }
                break;

            default:
                return false;
        }
        return true;
    }

Subclasses

How to pack/unpack additional parameters in a subclass.

class myVendCreateGlobalPaymJournal extends CustVendCreatePaymJournal_Vend

    NoYes       myParm;

    #define.CurrentVersion(1)
    #localmacro.CurrentList
        myParm
    #endmacro
    /// <summary>
    /// Pack the new parameters
    /// </summary>
    /// <returns>standard list of parameters with the new ones</returns>
    public container pack()
    {
        return [#CurrentVersion,#CurrentList] + [super()];
    }

    /// <summary>
    /// Unpacks saved parameters
    /// </summary>
    /// <param name = "_packedClass">Parameters container</param>
    /// <returns>True if OK</returns>
    public boolean unpack(container  _packedClass)
    {
        Integer  version = conPeek(_packedClass,1);
        container packedBase;

        switch (version)
        {
            case #CurrentVersion:
                [version, #CurrentList, packedBase] = _packedClass;
                return super(packedBase);
        }
        
        return super(_packedClass);
    }

Extensions

How to pack/unpack additional parameters in an augmented class (extension).

/// <summary>
/// We are going to use a new additional parameter
/// </summary>
[ExtensionOf(classStr(<ClassName>))]
public final class My<ClassName>_Extension
{
    private boolean     myNewParm;   
    #define.CurrentVersion(1)
    #localmacro.CurrentList
        myNewParm
    #endmacro

    
    /// <summary>
    /// myNewParm access
    /// </summary>
    /// <param name = "_parm">boolean</param>
    /// <returns>boolean</returns>
    public boolean parmMyNewParm(boolean _parm = myNewParm)
    {
        myNewParm= _parm;
        return myNewParm;
    }

    /// <summary>
    /// Extends Pack
    /// </summary>
    /// <returns>container</returns>
    public container pack()
    {
        container packedClass = next pack();
        return SysPackExtensions::appendExtension(packedClass, classStr(My<ClassName>_Extension), this.myPack());
    }

    /// <summary>
    /// Extends Unpack
    /// </summary>
    /// <param name = "packedClass">container</param>
    /// <returns>boolean</returns>
    private boolean myUnpack(container packedClass)
    {
        Integer version = RunBase::getVersion(packedClass);
        switch (version)
        {
            case #CurrentVersion:
                [version, #currentList] = packedClass;
                break;
            default:
                return false;
        }
        return true;
    }

    /// <summary>
    /// Packs my locals
    /// </summary>
    /// <returns>container</returns>
    private container myPack()
    {
        return [#CurrentVersion, #CurrentList];
    }

    /// <summary>
    /// Extends unpack
    /// </summary>
    /// <param name = "_packedClass">container</param>
    /// <returns>boolean</returns>
    public boolean unpack(container _packedClass)
    {
        boolean result = next unpack(_packedClass);

        if (result)
        {
            container myState = SysPackExtensions::findExtension(_packedClass, classStr(My<ClassName>_Extension));
            //Also unpack the extension
            if (!this.myUnpack(myState))
            {
                result = false;
            }
        }

        return result;
    }

}

Originally from https://alexvoy.blogspot.com/2022/02/additional-parameters-in-runbasebatch.html

Please, ping me if I missed anything.

Friday, February 11, 2022

Additional parameters in RunBaseBatch class extension

 Old good pack/unpack patterns with SysPackExtension call. Say, we need to augment RunBaseBatch based class <ClassName>.


/// <summary>
/// We are going to use a new additional parameter
/// </summary>
[ExtensionOf(classStr(<ClassName>))]
public final class My<ClassName>_Extension
{
    private boolean     myNewParm;   
    #define.CurrentVersion(1)
    #localmacro.CurrentList
        myNewParm
    #endmacro

    
    /// <summary>
    /// myNewParm access
    /// </summary>
    /// <param name = "_parm">boolean</param>
    /// <returns>boolean</returns>
    public boolean parmMyNewParm(boolean _parm = myNewParm)
{ myNewParm= _parm;
return myNewParm;
} /// <summary> /// Extends Pack /// </summary> /// <returns>container</returns> public container pack() { container packedClass = next pack(); return SysPackExtensions::appendExtension(packedClass, classStr(My<ClassName>_Extension), this.myPack());
} /// <summary> /// Extends Unpack /// </summary> /// <param name = "packedClass">container</param> /// <returns>boolean</returns> private boolean myUnpack(container packedClass) { Integer version = RunBase::getVersion(packedClass); switch (version) { case #CurrentVersion: [version, #currentList] = packedClass; break; default: return false; } return true; } /// <summary> /// Packs my locals /// </summary> /// <returns>container</returns> private container myPack() { return [#CurrentVersion, #CurrentList]; } /// <summary> /// Extends unpack /// </summary> /// <param name = "_packedClass">container</param> /// <returns>boolean</returns> public boolean unpack(container _packedClass) { boolean result = next unpack(_packedClass); if (result) { container myState = SysPackExtensions::findExtension(_packedClass, classStr(My<ClassName>_Extension));
//Also unpack the extension if (!this.myUnpack(myState)) { result = false; } } return result; } }

Wednesday, November 5, 2014

Dialog field with multiple choice

This is a small tutorial class on how to work with the new AX 2012 SysLookupMultiSelectCtrl class.

There are good examples on the internet as well as a tutorial class in AX 2012 itself but I want to explain how to pack/unpack values for this field and make it mandatory.

Let's say we need to select multiple sites in the dialog.



I hope my comments inline will be enough. If not let me know, please.


class tmxMultiSelectSiteTutorial extends RunBase
{
    FormBuildStringControl      fbscMultiSite;
    FormStringControl           fscMultiSite;
    container                   siteIds;
    str                         siteIdsStr;

    SysLookupMultiSelectCtrl    multiSiteCtrl;
    DialogRunbase               dialog;

    #define.CurrentVersion(1)
    #define.Version1(1)
    #localmacro.CurrentList
        siteIdsStr
    #endmacro
}



protected Object dialog()
{
    dialog = super();
    // add a new form build control for multiple choice; string type
    fbscMultiSite        = dialog.curFormGroup().addControl(FormControlType::String, identifierstr(AnyFormControlNameYouLike));
    fbscMultiSite.label('Site');

    return dialog;
}





public void dialogPostRun(DialogRunbase _dialog)
{
    FormRun formRun;

    super(dialog);

    formRun = _dialog.dialogForm().formRun();

    if (formRun)
    {
        // to get the access to the form control we created on the dialog
        fscMultiSite = formRun.design().control(fbscMultiSite.id());
        // create multiple loookup of SysLookupMultiSelectCtrl type
        // cgiInventSite query must exist in AOT; simply SiteId and Name from InventSite table
        multiSiteCtrl = SysLookupMultiSelectCtrl::construct(formRun, fscMultiSite, querystr(cgiInventSite));
        // to underline it red; actually it does not validate; so check the Validate method
        multiSiteCtrl.setMandatory(true);
        // if we restored from last values
        if(siteIdsStr)
        {
            //then we convert the string to container
            siteIds = str2con(siteIdsStr);
            // after create the special container of SiteIds and Names
            multiSiteCtrl.set(this.siteIds2Names(siteIds));
        }
    }
}





public boolean getFromDialog()
{
    if (multiSiteCtrl)
    {
        // selected sites convert to container of RecIds
        siteIds     = multiSiteCtrl.get();
        // convert it to string for pack/unpack
        siteIdsStr  = con2Str(siteIds);
    }
    return super();
}





private container siteIds2Names(container _c)
{
    InventSite      inventSite;
    container       contSiteId, contRecId;
    int             i, cLen = conLen(_c);

    for (i = 1 ; i <= cLen ; i++)
    {
        inventSite = inventSite::findRecId(conPeek(_c, i));
        // this part will be visible
        contSiteId += [inventSite.SiteId];
        // this part will be used by SysLookupMultiSelectCtrl as invisible
        contRecId  += [inventSite.RecId];
    }
    return [contRecId, contSiteId];
}





public boolean validate(Object _calledFrom = null)
{
    boolean ret;

    ret = super(_calledFrom);

    if(!conPeek(siteIds, 1))
    {
         ret = checkFailed('Site must be selected!');
    }

    return ret;
}





public void run()
{
    InventSite  inventSite;
    int         i;
    int         conNum = conLen(siteIds);

    // any business logic for the selected sites
    for( i = 1; i<=conNum; i++)
    {
        inventSite = inventSite::findRecId(conPeek(siteIds, i));
        info(strFmt("Site: %1 - %2", inventSite.SiteId, inventSite.Name));
    }
}