Showing posts with label Event handler. Show all posts
Showing posts with label Event handler. Show all posts

Wednesday, July 8, 2020

Event handler: Get access from FormDataSource argument to other data sources and form controls

Kind a code template to accelerate our job:


    [FormDataSourceEventHandler(formDataSourceStr(<FormName>, <FormDataSourceName>), FormDataSourceEventType::Activated)]
    public static void FormDataSourceName_OnActivated(FormDataSource _sender, FormDataSourceEventArgs _e)
    {
        <FormDataSourceTable>       formDataSourceTable                 = _sender.cursor();
        FormRun                     formRun                             = _sender.formRun();
        FormDataSource              anyFormDataSource_ds                = formRun.dataSource(formDataSourceStr(<FormName>, <AnyFormDataSourceName>)) as FormDataSource;
        <AnyFormDataSourceTable>    anyFormDataSourceTable              = anyFormDataSource_ds.cursor();
        FormControl                 anyFormControl                      = formRun.design(0).controlName('AnyFormControlName');
        
        // your logic goes here, for example
        if(formDataSourceTable.enabled())
        {
            anyFormControl.visible(false);
            anyFormControl.enabled(!anyFormDataSourceTable.RecId);
        }
    }

Wednesday, September 12, 2018

Lookup and Modified methods for FormReferenceGroup fields in D365

Let's say we need to keep in Purchase line a manufacturer code for a particular product. So each Product/Manufacturer code combination is unique.





Three tables are referenced via RecId fields.









So once Manufacturer code field is placed in the form, we end up with a FormReferenceGroup.





We can easily override its lookup method by subscribing to the relevant event on it.

 [FormControlEventHandler(formControlStr(PurchTable, myEcoResManufacturerProduct_myEcoResManufacturerProductRecId), FormControlEventType::Lookup)]
    public static void myEcoResManufacturerProduct_myEcoResManufacturerProductRecId_OnLookup(FormControl sender, FormControlEventArgs e)
    {
        PurchLine                           purchLine = sender.formRun().dataSource(formDataSourceStr(PurchTable, PurchLine)).cursor() as PurchLine;
        FormControlCancelableSuperEventArgs cancelableArgs = e as FormControlCancelableSuperEventArgs;
        
        myEcoResManufacturerProduct::lookupByItem(sender, purchLine.itemId);
        
        cancelableArgs.CancelSuperCall();
    }

public client static Common lookupByItem(FormReferenceControl _formReferenceControl, ItemId _itemId)
    {
        SysReferenceTableLookup sysReferenceTableLookup;
        Query                   query;
        QueryBuildDataSource    myEcoResMan;

        sysReferenceTableLookup = SysReferenceTableLookup::newParameters(tableNum(myEcoResManufacturerProduct), _formReferenceControl);
        sysReferenceTableLookup.addLookupfield(fieldNum(myEcoResManufacturerProduct, EcoResManufacturerRecId));
        sysReferenceTableLookup.addLookupfield(fieldNum(myEcoResManufacturerProduct, EcoResManufacturerPartNbr));

        query = new Query();
        myEcoResMan = query.addDataSource(tableNum(myEcoResManufacturerProduct));
        myEcoResMan.addRange(fieldNum(myEcoResManufacturerProduct, EcoResProductRecId)).value(SysQuery::value(InventTable::find(_itemId).Product));

        sysReferenceTableLookup.parmQuery(query);

        return sysReferenceTableLookup.performFormLookup() as myEcoResManufacturerProduct;
    }






But what if the user wants to create new values in appropriate tables if them do not exist yet?




We can catch the modified event in order to create new values before failing the validation.
However, given that its content may be changed, it is impossible to get access to its fields at design time.

We can do it during run-time by means of getting sought field form controls by their names and overloading then their Modified() methods. (see the similar trick for AX 2012 https://alexvoy.blogspot.com/2014/01/how-to-set-properties-for-reference.html)





The code you need to add.

[ExtensionOf(formStr(PurchTable))]
final class myPurchTableForm_PurchTableManuf_Extension
{
    private const  str          myFieldNameDisplayProductNumber    = 'EcoResManufacturerPartNbr';
    private const  str          myFieldNameEcoResManufacturerName  = 'EcoResManufacturerName';
    private FormStringControl   myFSCDisplayProductNumber;
    private FormStringControl   myFSCEcoResManufacturerName;
    
    // the only way to change the standard modified method for a control inside of a dynamically populated reference group
    // is to get it by its name looping all form controls of this group during run-time. then to overload it
    [FormEventHandler(formStr(PurchTable), FormEventType::Initialized)]
    public void PurchTable_OnInitialized(xFormRun sender, FormEventArgs e)
    {
        FormDesign                      formDesign = sender.design();
        FormReferenceGroupControl       formReferenceGroupControl;
        formReferenceGroupControl = formDesign.controlName(formControlStr(PurchTable, myEcoResManufacturerProduct_myEcoResManufacturerProductRecId)) as formReferenceGroupControl;
        this.registerManufacturerNameOverload(formReferenceGroupControl);
    }

    private void registerManufacturerNameOverload(FormReferenceGroupControl _formReferenceGroupControl )
    {
        int                                 i;
        Object                              childControl;
        FormStringControl                   formStringControl;

        for (i = 1; i <= _formReferenceGroupControl.controlCount(); i++) // FilterCategory is of FormReferenceGroupControl type
        {
            childControl = _formReferenceGroupControl.controlNum( i );
            formStringControl = childControl as formStringControl;
            if(formStringControl.DataFieldName() == myFieldNameEcoResManufacturerName)
            {
                myFSCEcoResManufacturerName = formStringControl;
                formStringControl.registerOverrideMethod(methodStr(formStringControl, modified), formMethodStr(PurchTable, myEcoResManufacturerName_modified_overload),
                    this);
            }
            if(formStringControl.DataFieldName() == myFieldNameDisplayProductNumber)
            {
                myFSCDisplayProductNumber = formStringControl;
                formStringControl.registerOverrideMethod(methodStr(formStringControl, modified), formMethodStr(PurchTable, myDisplayProductNumber_modified_overload),
                this);
            }
        }
    }

    /// <summary>
    /// We have to allow the user to insert any value, even though such a value is not found in the referenced table;
    /// then we will ask whether this new value must be created in the table and set this new value for the current record
    /// </summary>
    /// <param name = "_sender">EcoResManufacturerName</param>
    public void myEcoResManufacturerName_modified_overload(FormStringControl _sender)
    {
        myEcoResManufacturer myEcoResManufacturer = myEcoResManufacturer::findOrCreateByName(_sender.text());
        _sender.modified();
    }

    /// <summary>
    /// We have to allow the user to insert any value, even though such a value is not found in the referenced table;
    /// then we will ask whether this new value must be created in the table and set this new value for the current record
    /// </summary>
    /// <param name = "_sender">EcoResManufacturerName</param>
    public void myDisplayProductNumber_modified_overload(FormStringControl _sender)
    {
        Common          comm = _sender.dataSourceObject().cursor();
        PurchLine       purchLine   =  _sender.parentControl().dataSourceObject().cursor() as PurchLine;

        myEcoResManufacturerProduct myEcoResManufacturerProduct = myEcoResManufacturerProduct::findOrCreateEcoResManufacturerProduct(
                                                                                                        purchLine.itemId, 
                                                                                                        myFSCEcoResManufacturerName.text(),
                                                                                                        _sender.text());
        _sender.modified();
    }

}

Table methods

public static myEcoResManufacturer findOrCreateByName(myEcoResManufacturerName       _manufacturerName)
    {
        myEcoResManufacturer           myEcoResManufacturer = myEcoResManufacturer::findByName(_manufacturerName);
        if(!avrEcoResManufacturer)
        {
            if(Box::confirm("Do you want to creare new manufacturer?", strFmt(myEcoResManufacturer::txtNotExist(), _manufacturerName)))
            {
                try
                {
                    myEcoResManufacturer.EcoResManufacturerName = _manufacturerName;
                    if(myEcoResManufacturer.validateWrite())
                    {
                        myEcoResManufacturer.insert();
                    }
                }
                catch
                {
                    Error("Failed to create new manufacturer");
                }
            }
        }
        return myEcoResManufacturer;
    }


public static myEcoResManufacturerProduct findOrCreateEcoResManufacturerProduct(ItemId                         _itemId,
                                                                                    myEcoResManufacturerName       _manufacturerName,
                                                                                    myEcoResManufacturerPartNbr    _manufacturerPartNbr)
    {
        myEcoResManufacturerProduct    myEcoResManufacturerProduct;
        myEcoResManufacturer           myEcoResManufacturer;
        InventTable                     inventTable = InventTable::find(_itemId);
        // item and part number are given and exist
        if(inventTable.Product && _manufacturerPartNbr)
        {
            // such a manufacturer exists, so just try to find it for given combination
            myEcoResManufacturer           = myEcoResManufacturer::findOrCreateByName(_manufacturerName);
            myEcoResManufacturerProduct    = myEcoResManufacturerProduct::find(inventTable.Product, myEcoResManufacturer.RecId);
            if(!myEcoResManufacturerProduct)
            {
                if(Box::confirm("Do you want to create new part number", strFmt(myEcoResManufacturerProduct::txtNotExist(), _manufacturerPartNbr)))
                {
                    try
                    {
                        myEcoResManufacturerProduct.EcoResProductRecId         = inventTable.Product;
                        myEcoResManufacturerProduct.EcoResManufacturerRecId    = myEcoResManufacturer.RecId;
                        myEcoResManufacturerProduct.EcoResManufacturerPartNbr  = _manufacturerPartNbr;
                        myEcoResManufacturerProduct.EcoResManufacturerDefault  = NoYes::Yes;
                        if(myEcoResManufacturerProduct.validateWrite())
                        {
                            myEcoResManufacturerProduct.insert();
                        }
                    }
                    catch
                    {
                        Error("Failed to create new part number");
                    }
                }
            }
        }
        return myEcoResManufacturerProduct;
    }


Tuesday, October 3, 2017

D365: passing through public method by means of Pre- and Post-event handlers

Let's say we need to change the logic of a standard public method in terms of Extensions approach in D367 (AX7).

The whole idea is basically in saving values provided by XppPrePostArgs parameter in Pre-event handler method in new parameters and then restoring them in Post- one from the latter.

pre()>Save
standard method()
post()>Restore

For example, our business scenario is to allow the user to select Default company without selecting a Project while creating a new Purchase requisition. (I added a new parameter to the module)



Therefore, we have to change the logic of validateCoexistenceOfProjectAndBuyingLegalEntity method, which is called inside of PurchReqTable.validateWrite().

Standard, it does not allow to have an empty Project once Default company is chosen.


First, we create Pre- and Post-event handlers.



Then we put them into a new class and add new "by-passing" logic.


class PurchReqTableHandler
{
    #define.CompanyInfoDefaultArgName('CompanyInfoDefaultArgName')
    
    [PreHandlerFor(tableStr(PurchReqTable), tableMethodStr(PurchReqTable, validateCoexistenceOfProjectAndBuyingLegalEntity))]
    public static void PurchReqTable_Pre_validateCoexistenceOfProjectAndBuyingLegalEntity(XppPrePostArgs _args)
    {
        RefRecId        companyInfoDefault;
        PurchReqTable   purchReqTable   = _args.getThis();

        if(PurchParameters::find().PurchReqAllowCmpInfoDefWithoutProjId)
        {
            
            // if the user opted for setting Company without a project
            // we have to save it and use after this standard validation process
            if ( !purchReqTable.ProjId && purchReqTable.CompanyInfoDefault)
            {
                companyInfoDefault                  = purchReqTable.CompanyInfoDefault;
                purchReqTable.CompanyInfoDefault    = 0;
            }
            // make it zero to pass through the standard validation
            _args.setArg(#CompanyInfoDefaultArgName, companyInfoDefault);
        }
    }

   
    [PostHandlerFor(tableStr(PurchReqTable), tableMethodStr(PurchReqTable, validateCoexistenceOfProjectAndBuyingLegalEntity))]
    public static void PurchReqTable_Post_validateCoexistenceOfProjectAndBuyingLegalEntity(XppPrePostArgs _args)
    {
        boolean         ret;
        RefRecId        companyInfoDefault;
        PurchReqTable   purchReqTable   = _args.getThis();

        if(PurchParameters::find().PurchReqAllowCmpInfoDefWithoutProjId)
        {
            ret                 = _args.getReturnValue();
            companyInfoDefault  = _args.getArg(#CompanyInfoDefaultArgName);
            purchReqTable       = _args.getThis();
            // restore it
            if (ret && companyInfoDefault && !purchReqTable.CompanyInfoDefault)
            {
                purchReqTable.CompanyInfoDefault = companyInfoDefault;
            }
        }
    }

}