Replacing the "Add Existing" Button with a Filtered Lookup in Dynamics 365

 This article demonstrates how to replace the standard "Add Existing" button in Dynamics 365 with a custom solution that opens a filtered lookup dialog. The solution leverages the Dynamics 365 Web API and FetchXML to dynamically retrieve a custom view's ID and use it to filter lookup results. Although the example associates system users with a Driver Group via an N:N relationship, the approach is flexible and can be adapted for other entities and scenarios.


Overview

In many scenarios, you may want to restrict the records available in a lookup dialog. The default "Add Existing" button shows all available records, which may not meet your business requirements. By dynamically retrieving a custom view’s ID (e.g., "Active Drivers") and applying it to the lookup options, you ensure that only records matching your criteria are displayed.

The solution involves three key functions:

  1. getViewIdByName – Retrieves the view ID for a given view name and table name from the savedquery or userquery entities using FetchXML.
  2. openFilteredUserLookup – Opens a filtered lookup dialog using the dynamically retrieved view and processes the selected records.
  3. associateUserToDriverGroup – Associates each selected record with the current Driver Group record using the Web API (this can be adapted as needed).

Function Breakdown

1. Generalized View ID Retrieval

The getViewIdByName function accepts a view name (e.g., "Active Drivers") and a table name (e.g., "systemuser") and uses FetchXML to retrieve the view record. It filters the results based on the returnedtypecode and returns the view ID wrapped in curly braces.

/**
 * Retrieves the view id for a given view name and table name from the savedquery or userquery entity using FetchXML.
 *
 * @param {string} viewName - The name of the view to retrieve (e.g., "Active Drivers").
 * @param {string} tableName - The logical name of the entity for which the view is defined (e.g., "systemuser").
 * @returns {Promise<string>} - A promise that resolves with the view id in curly braces.
 *
 * Usage: Call this function with the view name and table name.
 */
function getViewIdByName(viewName, tableName)
{
    return new Promise(function(resolve, reject)
    {
        var fetchXml = [
            "<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>",
            "  <entity name='savedquery'>",
            "    <attribute name='savedqueryid' />",
            "    <attribute name='returnedtypecode' />",
            "    <filter>",
            "      <condition attribute='name' operator='eq' value='" + viewName + "' />",
            "    </filter>",
            "  </entity>",
            "</fetch>"
        ].join("");
        Xrm.WebApi.retrieveMultipleRecords("savedquery", "?fetchXml=" + encodeURIComponent(fetchXml)).then(
            function(result)
            {
                var records = result.entities || result.value;
                if (records && records.length > 0)
                {
                    var filtered = records.filter(function(item)
                    {
                        return item.returnedtypecode && item.returnedtypecode.toLowerCase() === tableName.toLowerCase();
                    });
                    if (filtered.length > 0)
                    {
                        var viewId = filtered[0].savedqueryid;
                        resolve("{" + viewId + "}");
                        return;
                    }
                }
                // Fallback: query personal views in the userquery entity.
                var userQuery = [
                    "<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>",
                    "  <entity name='userquery'>",
                    "    <attribute name='userqueryid' />",
                    "    <attribute name='returnedtypecode' />",
                    "    <filter>",
                    "      <condition attribute='name' operator='eq' value='" + viewName + "' />",
                    "    </filter>",
                    "  </entity>",
                    "</fetch>"
                ].join("");
                Xrm.WebApi.retrieveMultipleRecords("userquery", "?fetchXml=" + encodeURIComponent(userQuery)).then(
                    function(result2)
                    {
                        var records2 = result2.entities || result2.value;
                        if (records2 && records2.length > 0)
                        {
                            var filtered2 = records2.filter(function(item)
                            {
                                return item.returnedtypecode && item.returnedtypecode.toLowerCase() === tableName.toLowerCase();
                            });
                            if (filtered2.length > 0)
                            {
                                var viewId = filtered2[0].userqueryid;
                                resolve("{" + viewId + "}");
                                return;
                            }
                        }
                        reject("View not found: " + viewName);
                    },
                    function(error2)
                    {
                        reject(error2.message);
                    }
                );
            },
            function(error)
            {
                reject(error.message);
            }
        );
    });
}


2. Opening a Filtered Lookup

The openFilteredUserLookup function is triggered via a custom ribbon button. It retrieves the current record's ID (e.g., Driver Group ID), calls getViewIdByName to obtain the custom view ID, and then opens the filtered lookup dialog using Xrm.Utility.lookupObjects. The selected records are then processed for association.


/**
 * Opens a filtered lookup for system users using a custom view and associates them with the current record.
 *
 * @param {Object} primaryControl - The form context of the current record (e.g., Driver Group form).
 * @param {string} viewName - The name of the custom view to use (e.g., "Active Drivers").
 * @returns {void}
 *
 * Usage: Attach this function to a custom ribbon button command to replace the default "Add Existing" button.
 */
function openFilteredUserLookup(primaryControl, viewName)
{
    var formContext = primaryControl;
    var driverGroupId = formContext.data.entity.getId();
    if (driverGroupId === null || driverGroupId === "")
    {
        return;
    }
    getViewIdByName(viewName, "systemuser").then(
        function(customViewId)
        {
            var lookupOptions = {
                defaultEntityType: "systemuser",
                entityTypes: ["systemuser"],
                allowMultiSelect: true,
                defaultViewId: customViewId,
                viewIds: [customViewId]
            };
            Xrm.Utility.lookupObjects(lookupOptions).then(
                function(selectedRecords)
                {
                    if (selectedRecords && selectedRecords.length > 0)
                    {
                        for (var i = 0; i < selectedRecords.length; i++)
                        {
                            var userId = selectedRecords[i].id;
                            if (userId)
                            {
                                userId = userId.replace("{", "").replace("}", "");
                                associateUserToDriverGroup(driverGroupId, userId);
                            }
                        }
                    }
                },
                function(error)
                {
                    console.error(error.message);
                }
            );
        },
        function(error)
        {
            console.error("Error retrieving view: " + error);
        }
    );
}


3. Associating Records

The associateUserToDriverGroup function uses the Dynamics 365 Web API to associate each selected system user with the current Driver Group record via a many-to-many relationship. You can adapt this function to perform different associations as required.


/**
 * Associates a system user with the current Driver Group using the N:N relationship.
 *
 * @param {string} driverGroupId - The ID of the current Driver Group.
 * @param {string} userId - The ID of the system user to associate.
 * @returns {void}
 */
function associateUserToDriverGroup(driverGroupId, userId)
{
    var relationshipName = "dsl_systemuser_DriverGroupId_dsl_drivergroup";
    var entityName = "systemuser";
    Xrm.WebApi.associate("dsl_drivergroup", driverGroupId, relationshipName, [{ id: userId, entityType: entityName }]).then(
        function()
        {
            console.log("User associated successfully.");
        },
        function(error)
        {
            console.error("Error associating user: " + error.message);
        }
    );
}

Conclusion

This solution demonstrates how to replace the standard "Add Existing" button in Dynamics 365 with a custom filtered lookup. By dynamically retrieving a custom view's ID using a generalized function that accepts both a view name and a table name, you can tailor the lookup dialog to display only the records that meet your business requirements. This approach not only enhances data integrity but also improves the user experience across your Dynamics 365 applications.


No comments:

Post a Comment