Skip to main content

🚀 How to Dynamically Copy Matching Fields Between Tables in X++ using DictTable

 In Microsoft Dynamics AX (X++), there are many scenarios where you need to duplicate data from one table to another—especially during custom import, duplication, or versioning processes. Traditionally, this is done by manually assigning each field: targetTable.Field1 = sourceTable.Field1; targetTable.Field2 = sourceTable.Field2; ... But what if the tables have many fields ? Or maybe you’re dealing with multiple similar table pairs? That’s where the powerful DictTable class comes in. Let’s walk through how to use it to copy matching fields dynamically between two tables. 💡 Use Case: Copy Customer Templates Let’s assume you have these tables: CustTemplateHeader – stores predefined customer templates. CustTemplateHeaderHistory – a historical copy of templates for versioning. You want to copy records from CustTemplateHeader to CustTemplateHeaderHistory , but only for fields that exist in both tables. ✅ The Solution Using DictTable SalesHeaderTemplate   ...

How to Open a Form with Filtered Data Using a Button in X++ – A Step-by-Step Guide

 In Dynamics 365 for Finance and Operations (D365FO), a common requirement is to open a form dynamically from another form and pass filtered data based on a specific condition. This functionality can enhance user experience by allowing them to interact with multiple forms seamlessly, while keeping the data relevant and focused.

In this blog, we’ll explore how to implement such a solution using X++, where a user clicks a button on Form 1 (such as a list of sales orders), and based on a selected record, Form 2 (such as invoice details) opens with only the relevant filtered data.

Scenario Overview

Let’s assume the following scenario:

  • Form 1: Displays a list of sales orders, and each order has an OrderID, CustomerID, and OrderAmount.
  • Form 2: Displays details of invoices (from the InvoiceDetails table) that are linked to the selected OrderID from Form 1.

The goal is to click a button on Form 1, pass the OrderID to Form 2, and display only the relevant invoice records related to that OrderID.

Step 1: Set Up the Button Logic on Form 1

The first part of this solution involves creating a button on Form 1 (the sales order form). When the button is clicked, it will pass the selected OrderID to Form 2, filtering the data based on that ID.

Code for Button Logic on Form 1:


class InvoiceDetailsButton
{
    public void clicked()
    {
        super();
        SalesOrder salesOrder;
        MenuFunction menuFunction;
        InvoiceDetails invoiceDetails;
        Args args = new Args();
        FormRun formRun = this.formRun();
        int64 orderID;
        List filteredInvoices = new List(Types::Record); // List to store filtered invoice records

        // Validate that the form has a valid data source
        if (formRun && formRun.dataSource(formDataSourceStr(SalesOrderForm, SalesOrder)))
        {
            salesOrder = formRun.dataSource(formDataSourceStr(SalesOrderForm, SalesOrder)).cursor();

            if (salesOrder)
            {
                orderID = salesOrder.OrderID; // Retrieve the selected OrderID
            }
            else
            {
                warning("No order selected in SalesOrder.");
                return;
            }
        }
        else
        {
            warning("SalesOrder data source not found on the form.");
            return;
        }

        // Query to filter data from InvoiceDetails
        Query query = new Query();
        QueryBuildDataSource qbds;
        QueryBuildRange qbr;

        qbds = query.addDataSource(tableNum(InvoiceDetails));
        qbr = qbds.addRange(fieldNum(InvoiceDetails, OrderID)); // Add a range filter for OrderID
        qbr.value(queryValue(orderID)); // Use the OrderID as the filter value

        QueryRun queryRun = new QueryRun(query);
        while (queryRun.next())
        {
            invoiceDetails = queryRun.get(tableNum(InvoiceDetails));
            filteredInvoices.addEnd(invoiceDetails); // Add record to the list
        }

        if (filteredInvoices.elements() == 0)
        {
            warning("No matching invoice details found for the selected order.");
        }
        else
        {
            // Pass all filtered data to the target form
            args.parmObject(filteredInvoices); // Pass filtered invoices to Form 2
            args.caller(formRun); // Pass the original form context

            menuFunction = new MenuFunction(menuItemDisplayStr(InvoiceDetailsForm), MenuItemType::Display);
            menuFunction.run(args); // Launch Form 2 with filtered invoices
        }
    }
}



Explanation of the Code for Form 1:

  1. SalesOrder salesOrder: Represents the selected sales order from the SalesOrder data source.
  2. orderID: The OrderID of the selected sales order is extracted, which will be used to filter the invoice records.
  3. Query: We create a query to filter data from the InvoiceDetails table based on the OrderID.
  4. filteredInvoices: The filtered invoice records are stored in a list and passed to Form 2.
  5. args.parmObject(filteredInvoices): This method passes the filtered invoices to Form 2.
  6. menuFunction.run(args): This function launches Form 2, passing the filtered data along with it.

Step 2: Handle the Data in Form 2

Now, let's set up Form 2 (InvoiceDetailsForm) to receive the filtered data and display it accordingly.

Objective: When Form 2 is opened, it should receive the filtered list of invoice details (based on OrderID), and apply the necessary filter to the data source.

Code for Form 2 (InvoiceDetailsForm):


public class InvoiceDetailsFormRun extends FormRun
{
    public void init()
    {
        super();

        List filteredInvoices;
        int64 orderID = 0;

        // Check if the calling form has passed filtered data (InvoiceDetails)
        if (this.args().parmObject())
        {
            filteredInvoices = this.args().parmObject() as List;

            if (filteredInvoices)
            {
                ListEnumerator enumerator = filteredInvoices.getEnumerator();

                // Iterate through the filtered list and extract the OrderID
                while (enumerator.moveNext())
                {
                    InvoiceDetails invoiceDetails = enumerator.current() as InvoiceDetails;
                    if (invoiceDetails)
                    {
                        orderID = invoiceDetails.OrderID; // Get the OrderID from each record
                    }
                }
            }
            else
            {
                warning("No records found in the filtered list.");
            }
        }
        else
        {
            warning("No data received from the caller form.");
        }

        // Apply filter to the form's data source (InvoiceDetails)
        if (orderID)
        {
            FormDataSource formDatasource = this.dataSource();

            if (formDatasource)
            {
                QueryBuildDataSource qds = formDatasource.queryBuildDataSource();
                qds.addRange(fieldNum(InvoiceDetails, OrderID)).value(SysQuery::value(orderID)); // Filter by OrderID
            }
            else
            {
                warning("DataSource not found.");
            }
        }
        else
        {
            warning("No filter value (OrderID) found.");
        }
    }
}


Step 3: Testing the Solution

Once the button logic is set up on Form 1 and the filtering mechanism is implemented on Form 2, you can proceed to test the solution:

  1. Form 1: Select a sales order from the list, and click the button.
  2. Form 2: After clicking the button, Form 2 should open and display only the invoice records related to the selected OrderID.

Tips for Effective Implementation:

  • Error Handling: Always ensure proper error handling is in place to handle scenarios where data is not passed correctly or records are not found.
  • Optimizing Queries: If the dataset is large, consider optimizing your queries to improve performance, using indices and limits where applicable.
  • User Feedback: Use informative messages (e.g., warning()) to notify users if no records are found, or if there are issues with the data passed.

Conclusion

In this blog, we’ve demonstrated how to dynamically open and filter forms in Dynamics 365 FO using X++. We used a practical example where a user selects a sales order on Form 1, and upon clicking a button, Form 2 opens with filtered invoice details related to the selected OrderID.

By following this pattern, you can pass filtered data between forms seamlessly, improving the user experience and making the data more relevant and accessible.

Comments

Popular posts from this blog

How to Refresh a Form or Data Source in D365FO Using X++

  Introduction In Microsoft Dynamics 365 Finance & Operations (D365FO), refreshing the form after an action (like inserting, updating, or deleting a record) is essential for keeping the UI updated with the latest data. In this blog, we’ll explore two ways to refresh the form in X++: ✅ Refreshing the entire form using taskRefresh ✅ Refreshing a specific data source using research Let's dive into the best practices for implementing these refresh methods! 🔄 Refreshing the Entire Form If you need to refresh the whole form , use the taskRefresh method. This method is useful when multiple data sources are involved, and you want to reload everything. 📌 X++ Code for Full Form Refresh public void refreshForm() {     // Get the current form instance     FormRun formRun = this.formRun();     // Check if formRun is valid before refreshing     if (formRun)     {         info("Refreshing the form...");     ...

Sorting Data in X++ (D365FO) Grids Using Form Data Source Events

  Introduction : In Dynamics 365 Finance and Operations, form grids often display data retrieved from a table or query. However, the default sorting applied may not always align with business requirements. Customizing sorting behavior at runtime ensures that the grid data is presented in a meaningful order for users. This blog post demonstrates how to use the Initialized event of a form data source to apply custom sorting to a grid. We will sort the grid rows based on a specific field ( DisplayOrder ) in ascending order. Understanding the Code : Here’s the complete code snippet: --------------------------------------------------------------------------------------------------------------- [FormDataSourceEventHandler(formDataSourceStr(MyFormDataSource,  MyTable), FormDataSourceEventType::Initialized)] public static void MyFormDataSource_OnInitialized(FormDataSource sender, FormDataSourceEventArgs e) {     // It will clear if any other sorting is applied on the grid ...