Adding a Custom Data Multi Select Form Control to an Existing Form

April 9, 2025

By: Jake Zenick

In this guide, Strabo walks through the process of associating multiple custom values with a customer's address, starting with the creation of necessary tables to store these values and their mappings. It delves into the implementation of methods for initializing, setting, and retrieving these values, ensuring seamless integration within the D365 environment. This how to guide also covers the extension of various forms and data sources to support the new multi-select control, providing a comprehensive solution for improved data management and user experience.

We want to add the ability to associate multiple custom values with a customer’s address.

  1. First thing we need is a table to store the custom values we will select from, we will use the table ‘StraboDisplayValues ‘ with fields called ‘Value’ and ‘Description’.
  2. Next we’ll need a table that stores the mappings of each value that is associated with each address, we will use the table ‘StraboAddressValueMapStorage’ with the fields ‘CustAccount’, ‘LogisticsPostalAddressRecId and ‘ValueRefRecId’.
  3. The StraboAddressValueMapStorage will need the following methods to enable the use of a multiselect form control:
    1. An init method to set the three fields used in the mapping storage, this will be used in our setter method.

      public void initFromCustAddressValue(
      CustAccount _custAccount,
      LogisticsPostalAddressRecId _address,
      refRecId _valueRefRecId)
      {
      this.CustAccount = _custAccount;
      this.LogisticsPostalAddressRecId = _address;
      this.Value = _valueRefRecId;
      }


    2. Setter method to store the mapping of values against the customer and address. To do this we will pass the party recid for the customer along with the address recid and container of values that have been assigned to the customer’s address.

      public static void setValuesForAddressParty(
      LogisticsPostalAddressRecId _address,
      DirPartyRecId _party,
      container _values)
      {
      CustAccount customer = CustTable::findByPartyRecId(_party).AccountNum;
      int conCount;

      StraboAddressValueMapStorage addressValueMapStorageDel;

      ttsbegin;
      delete_from addressValueMapStorageDel
      where addressValueMapStorageDel.CustAccount == customer
      && addressValueMapStorageDel.LogisticsPostalAddressRecId == _address;

      RecordInsertList addressValueMapList = new
      RecordInsertList(tableNum(StraboAddressValueMapStorage));

      for (conCount = 1; conCount <= conLen(_values); conCount++)
      {
      RefRecId valueRefRecId = conPeek(_values, conCount);

      if (valueRefRecId)
      {
      StraboAddressValueMapStorage addressValueMapStorage;
      addressValueMapStorage.initFromCustAddressValue(customer, _address, valueRefRecId);
      addressValueMapList.add(addressValueMapStorage);
      }
      }

      addressValueMapList.insertDatabase();
      ttscommit;
      }


    3. Getter method that returns a properly formatted container which we will use to initialize the multiselect form control and show which values have already been selected and stored for the given customer’s address.

      public static container getValuesContainer(
      LogisticsPostalAddressRecId _address,
      DirPartyRecId _party)
      {
      container valueRecIds, valueDescriptions;
      StraboAddressValueMapStorage valuesMap;
      CustAccount customer = CustTable::findByPartyRecId(_party).AccountNum;

      while select valuesMap
      where valuesMap.LogisticsPostalAddressRecId == _address
      && valuesMap.CustAccount == customer
      {
      StraboDisplayValues value = StraboDisplayValues::findRecId(valuesMap.value);

      valueRecIds += value.RecId;
      valueDescriptions += value.Description;
      }
      return conLen(valueRecIds) == 0 ? conNull() : [valueRecIds, valueDescriptions, valueDescriptions];
      }


    4. Getter method that returns a formatted string to display all the values associated with the customer’s address in a read-only format.

      public static str getDisplayStr(
      LogisticsPostalAddressRecId _address,
      DirPartyRecId _party)
      {
      str displayStr;
      StraboAddressValueMapStorage valuesMap;
      CustAccount customer = CustTable::findByPartyRecId(_party).AccountNum;
      while select valuesMap
      where valuesMap.LogisticsPostalAddressRecId == _address
      && valuesMap.CustAccount == customer
      {
      StraboDisplayValues value = StraboDisplayValues::findRecId(valuesMap.value);
      if (!displayStr)
      {
      displayStr = value.Description;
      }
      else
      {
      displayStr += (', ' + value.Description);
      }
      }
      return displayStr;
      }


Now we will need a simple query object that uses the ‘StraboDisplayValues’ table as a data source and includes the fields we want the user to see when opening the multiselect control, we will use both ‘Value’ and ‘Description’.

Query Object

Once all the objects are made we will make a form extension of the LogisticsPostalAddress form and add a new string control called ‘StraboValues’ with the following properties set:

Lookup String

Now we will add a code extension on the LogisticsPostalAddress form, and declare a multiselect lookup control.

[ExtensionOf(formStr(LogisticsPostalAddress))]
final class StraboLogisticsPostalAddressForm_Extension
{
public SysLookupMultiSelectCtrl multiSelectLookupCtrl;
}

In this class we’ll then add three methods:

  1. A new parm method to get and set the lookup control object.

    public SysLookupMultiSelectCtrl parmLookupCtrl(SysLookupMultiSelectCtrl _multiSelectLookupCtrl = multiSelectLookupCtrl)
    {
    multiSelectLookupCtrl = _multiSelectLookupCtrl;
    return multiSelectLookupCtrl;
    }

  2. A chain of command extension of our form init() method to initialize the multi select lookup object for the new string control using our StraboValuesLookupQuery and StraboDisplayValues table.

    public SysLookupMultiSelectCtrl multiSelectLookupCtrl;
    public void init()
    {
    next init();
    this.parmLookupCtrl(SysLookupMultiSelectCtrl::construct(this,
    this.StraboValues,
    queryStr(StraboValuesLookup),
    false,
    [tableNum(StraboDisplayValues),
    fieldNum(StraboDisplayValues, Description)]));
    }

  3. Last we will do another chain of command extension on the form’s closeOk() method to store off the values that were selected using our StraboAddressValueMapStorage setter method and the multiselect lookup control’s get method.

    public void closeOk()
    {
    next closeOK();

    SysLookupMultiSelectCtrl lookupCtrl = this.parmLookupCtrl();
    LogisticsPostalAddress postalAddress = this.LogisticsPostalAddress;
    DirPartyLocation dirPartyLocation = this.DirPartyLocation;
    StraboAddressValueMapStorage::setValuesForAddressParty(postalAddress.RecId, dirPartyLocation.Party, lookupCtrl.get());
    }

Now we will create another code extension but this time on the LogisticsPostalAddress form’s DirPartyLocation data source and do a chain of command extension on the executeQuery method so that we can retrieve the stored values associated with this party and location and pass them to our multiselect lookup control. To do this we will use our getter method on the StraboAddressValueMapStorage table that returns a container along with the multiselect lookup control’s set() method

[ExtensionOf(formDataSourceStr(LogisticsPostalAddress, DirPartyLocation))]
final class StraboLogisticsPostalAddressForm_DIrPartyLocationDS_Extension
{
void executeQuery()
{
next executeQuery();

SysLookupMultiSelectCtrl lookupCtrl = element.parmLookupCtrl();
LogisticsPostalAddress postalAddress = element.LogisticsPostalAddress;
DirPartyLocation dirPartyLocation = element.DirPartyLocation;

lookupCtrl.set(StraboAddressValueMapStorage::getValuesContainer(postalAddress.RecId, dirPartyLocation.Party));
}
}

Before our last step we just need to create a code extension on the DirPartyPostalAddressView view so that we can have an easy to read display string on our customer addresses grid.

[ExtensionOf(viewstr(DirPartyPostalAddressView))]
final class StraboDirPartyPostalAddressView_Extension
{
[SysClientCacheDataMethodAttribute(true)]
display str straboDisplayValues()
{
return StraboAddressValueMapStorage::getDisplayStr(this.PostalAddress, this.Party);
}
}

Finally we will just create a form extension of the LogisticsPostalAddressGrid form and add a new string control to the grid that will use our new display method as so:

Display String

Conclusion

By following the steps outlined in this guide, users can effectively add a custom multi-select form control to their D365 environment, enhancing the ability to associate multiple custom values with customer addresses and improving overall data management and user experience.

Need some expert help? Strabo Partners is here for your D365 need. Contact Strabo today!

Interested in learning more about how Strabo Partners can be your implementation partner? Contact us today!