Getting data from custom form on SysOperation

Hello everyone!
I`m new in AX (just around 2 month).

I`m currently learning SysOperation framework and my task is to create a dialog for creating sales order using this framework.

First of all Ive already done it on RunBaseBatch and now Im stuck with SysOperation.

On my RBB dialog I used custom form with grid and temp table as datasource to add items, quantity and dimensions for the order as well as choosing customer account on another tab.

I used getFromDialog() method to get all input data for running main job in batch.

It looks like next (link to imgur)

my dialog

the code is

public boolean getFromDialog()
{
    SEDYSsalesLineTemp sEDYSsalesLineTemprecord;
    InventDim          inventDim;
    FormRun            formRun;
    FormDataSource     ds, dimDs;

    custAccount         = custAccountField.value();

    formRun  = dialog.dialogForm().formRun();
    ds       = formRun.dataSource();
    dimDs    = formRun.dataSource("InventDim");

    sEDYSsalesLineTemprecord = ds.getFirst();
    inventDim                = dimDs.getFirst();
    while(sEDYSsalesLineTemprecord)
    {
        inventDim                = InventDim::findOrCreate(inventDim);
        sEDYSsalesLineTemprecord.InventDimId = inventDim.inventDimId;
        itemList.addEnd(sEDYSsalesLineTemprecord);
        sEDYSsalesLineTemprecord = ds.getNext();
        inventDim                = dimDs.getNext();
    }

    conItemList = itemList.pack();
}

Then i run main logic which creates sales order header and lines.

Now I have to do same thing with sysOperation. I have created my custom form from SysOperationTemplateForm, added same grid and data sources.

And the problem is I don`t understand how to get data from my dialog.

I understand that generally I need to pass that ‘conItemList’ container to some parm method on a contract class and simply unpack it in service class and use. But where do I have to put my packing method and how to get access to the form`s dataSource?

I figured out that method getFromDialog() on AutomaticUIBuilder gets data for contract members, and that is the way i get my custAccount from form. But still I cant figure out how to get data from forms datasource and pack it to container.

Of course Im not sure that I am thinking in right direction, so Ill be glad on any suggestions. But the main thing is that I have to use this form (not a standard template), and I must have ability to add as many items to order as necessary .

Looking forward on any suggestions and thanks for attention!

UPD
for now Ive figured out how to extract data from my custom form. I wrote new method in my Controller class which almost the same as I presented above. And I call it from overriden getFromDialog() on that contract class (Ive found that it is called when I press OK on form). So Im getting the container with all my input records with itemId, dimensions and quantity. Now the question is how to pass my container to the data contract class? Ive seen on web the code like this:

public static void main(Args _args)
{
    SEDYSCreateSalesOrderController controller;
    SEDYSCreateSalesOrderContract   contract;

    controller = new SEDYSCreateSalesOrderController();
    contract   = controller.getDataContractObject("SEDYSCreateSalesOrderContract");
    controller.startOperation();
}

I`ve tried to put this getDataContractObject() method inside the getFromDialog() on controller which looks like next:

public void getFromDialog()
{
    SEDYSCreateSalesOrderContract   contract;
    container con;
    
    contract = this.getDataContractObject("SEDYSCreateSalesOrderContract");
    con = this.getFromForm();
    contract.parmConItemList(con);
    super();   
}

But the problem is that it isn`t getting initialized (neither in main() method, nor in getFromDialog()).

So where do I have to look next? I understand that if I just create new instance of my contract class llike “contract = new SEDYSCreateSalesOrderContract();” it won`t help.

So I`m looking forward on any suggestions and thanks again for attention!

If contracts aren’t initialized, they get initialized inside getDataContractObject(), therefore you should never get into the situation that they’re not initialized. It might mean that “SEDYSCreateSalesOrderContract” isn’t the right parameter value. What if you rather try to get the first contract object by omitting the parameter, i.e. calling just this.getDataContractObject().

I suspect that the right value of the parameter would be the name of the method parameter, not its type. I would have to debug a controller to verify it.

Thanks for reply, you are right - calling getDataContractObject() without parameter initializes my contract. But I still miss the way how I must set my input data to the contract. Ive already found the way framework does it. The getFromDialog() method on controller calls same method on SysOperationAutomaticUIBuilder class. Even If I override this method on my inherited class it doesnt get called - only parents method is called. This method sets my contract parm methods with values from form if type of data in these parm methods is supported type. Ive already debugged that I can`t create parm methods with table variables or containers.

I`ve debugged getFromDialogInterna() on SysOperationAutomaticUIBuilder class and found out that

 memberMap = _dataContractInfo.getMembers();
    if (!memberMap)
    {
        return;
    }

    memberEnumerator = memberMap.getEnumerator();
    
 while (memberEnumerator.moveNext())
    {
        memberInfo = memberEnumerator.currentValue();

        if (SysOperationAutomaticUIBuilder::isSupportedType(memberInfo))
  1. this memberMap doesnt include any parm methods with types like List or container at all (Ive genereted Incremental and Full CIL in order to exclude any occasions)

  2. validation in if() statement skips members with table types like they are not supported.

I`ve tried to hardcode write my packed container to the contract object like

if(this.getDataContractObject() is SEDYSCreateSalesOrderContract)
        {
            this.getDataContractObject().parmConItemList(con);
        }

i tried this both in getFromDialog() on controller before and after calling super and in closeOk() on my form (where the code was a bit different (this.controller().getDataContractObject().parmConItemList(this.getFromForm()))

In these 2 examples ‘con’ and ‘this.getFromForm()’ both was a container with packed list of records from form.

But on operation in service I still doesn`t get any data. I try to check it with next statement

if(_contract.parmConItemList())
    {
        info("in create order if container is initialized");
        ....
    }

Am I right that the execution must enter the block of code after if() statement if the container contains any data?

The problem wasn’t that the contract wasn’t initialized - it was, but you asked for a contract of a non-existing name.

Yes, the automatic UI builder doesn’t know how to render containers, therefore it ignores them.

I can’t say what problem you have in your code (or your debugging procedure) causing that your overridden getFromDialog() doesn’t seem to get called. But I can show you my own example that works:

[
    DataContract,
    SysOperationContractProcessingAttribute(classstr(MyUIBuilder))
]
class MyContract
{
    Name name;
    container packedData;

    [DataMember]
    public Name parmName(Name _name = name)
    {
        name = _name;
        return name;
    }

    [DataMember]
    public container parmPackedData(container _packedData = packedData)
    {
        packedData = _packedData;
        return packedData;
    }

}

class MyUIBuilder extends SysOperationAutomaticUIBuilder
{
    public void getFromDialog()
    {
        super();

        MyContract contract = this.dataContractObject();

        contract.parmPackedData(["abcd", 42]);
    }

}

class MyController extends SysOperationServiceController
{
    public static void main(Args _args)
    {
        MyController controller = new MyController();
        controller.parmClassName(classStr(MyController));
        controller.parmMethodName(methodStr(MyController, myOperation));
        controller.parmExecutionMode(SysOperationExecutionMode::Synchronous);

        controller.startOperation();
    }

    public void myOperation(MyContract _contract)
    {
        container data = _contract.parmPackedData();
    }

}

Thanks for your help, Martin. Yesterday at the evening I finally figured out how to fix my problem. I created new contract class, instead of the first one, and it worked. As I was told, sometimes the problem can be in CIL caches. or something. Since it was my first work with SysOperation, I built CIL a lot of times and probably had troubles with that.

So I created new contract class with container parm method and filled it in getFromDialog() method on controller class. The code looks like next:

class SEDYSCreateSalesOrderController extends SysOperationServiceController
{
}

public static void main(Args _args)
{
    SEDYSCreateSalesOrderController controller;

    controller = SEDYSCreateSalesOrderController::construct();

    controller.startOperation();
}

public void getFromDialog()
{
    super();

    if(this.getDataContractObject() is SEDYSCreateSalesOrderContract)
        {
            this.getDataContractObject().parmConItemList(this.getFromForm());
        }
}

protected container getFromForm()
{
    DialogForm          dialogForm;
    FormRun             formRun;
    FormDataSource      ds, dimDs;
    SEDYSsalesLineTemp  sEDYSsalesLineTemprecord;
    InventDim           inventDim;
    List                itemList;
    container           conItemList;

    itemList   = new List(Types::Record);
    dialogForm = this.dialog().dialogform();
    formRun    = dialogForm.formRun();
    ds         = formRun.dataSource("SEDYSsalesLineTemp");
    dimDs      = formRun.dataSource();

    sEDYSsalesLineTemprecord = ds.getFirst();
    inventDim                = dimDs.getFirst();

    while(sEDYSsalesLineTemprecord.ItemId)
    {
        inventDim                = InventDim::findOrCreate(inventDim);
        sEDYSsalesLineTemprecord.InventDimId = inventDim.inventDimId;
        itemList.addEnd(sEDYSsalesLineTemprecord);
        sEDYSsalesLineTemprecord = ds.getNext();
        inventDim                = dimDs.getNext();
    }
    conItemList = itemList.pack();

    return conItemList;
}

After that I finally got filled container in service class and was able to create lines in Sales order.