Using addPreSearch with an asynchronous call to create the filter

Hello everyone;

Recently while answering questions on the Microsoft Forums a question was raised about how to filter the query of an addPreSearch lookup event based on values in a relationship.

The scenario we’re looking at is we have a table called “Collection”.  This table has a M:1 relationship with director and it has a 1:M relationship with a table called Movies. The Movies table has a M:1 relationship with the director table. 

The requirement is to have the Director lookup prefilter and only show a list of directors from movies related to the collection. In the picture below when we click on the Directors lookup we would expect to see 6 directors selectable based on the associated movies.

Collection Table Form

How we go about this is using three specific methods:

The addPreSearch allows you to specify what event will fire when a user clicks on the lookup. In this function you to pass in a function and it must then add a custom filter.
– formContext.getControl(arg).addPreSearch(myFunction)

The addCustomFilter function is an event that executes when you click on the lookup field and executes the filter or fetchxml filter passed into it.
– formContext.getControl(arg).addCustomFilter(filter, entityLogicaName)

The Xrm.WebApi.retrieveMultipleRecords is a method that allows us to retrieve multiple records but this is an asynchronous call which causes the issue of building the proper filter in this scenario.

What we need to do to achieve the goal of filtering the directors lookup based on the related movies to the collection are the following steps:

  1. Execute a Xrm.WebApi.retrieveMultipleRecords allowing us to build the filter statement with the proper directors.
  2. Once the filter statement has been completed we need to call the addCustomerFilter function.
  3. We need to have our function that calls the addPreSearch when the lookup event is changed using the filter statement.

Let’s dive into the code. I’ll post my full web resource file and we can discuss it in more detail below. This web resource file can also be found on github at https://github.com/jasoncosman/Collections/blob/master/jcmvp_collection.js.

 

    OnLoad: function (executionContext) {
        JCMVP.Collection.Form.getCollectionsMovies(executionContext);
    },


    filterMovieDirectors: function () {
        //https://docs.microsoft.com/en-us/powerapps/developer/model-driven-apps/clientapi/reference/controls/addcustomfilter
        formContext.getControl("jcmvp_director").addCustomFilter(fetchDirectorFilter, "jcmvp_director");
    },


    getCollectionsMovies: function (executionContext) {
        formContext = executionContext.getFormContext();

        var collectionID = formContext.data.entity.getId().replace("{", "").replace("}", "");

        //https://docs.microsoft.com/en-us/powerapps/developer/model-driven-apps/clientapi/reference/xrm-webapi/retrievemultiplerecords
        Xrm.WebApi.retrieveMultipleRecords("jcmvp_movie", "?$select=_jcmvp_director_value&$filter=(_jcmvp_collection_value eq " + collectionID + ")").then(
            function success(result) {
                debugger;

                if (result.entities.length > 0) {
                    fetchDirectorFilter = "<filter type='and'> <condition attribute = 'jcmvp_directorid' operator = 'in' uitype='jcmvp_director'>";
                }

                for (var i = 0; i < result.entities.length; i++) {
                    //_jcmvp_director_value
                    fetchDirectorFilter += "<value>" + result.entities[i]._jcmvp_director_value + "</value>";
                    console.log(result.entities[i]);
                }
                if (result.entities.length > 0) {
                    fetchDirectorFilter += "</condition ></filter >";
                    //https://docs.microsoft.com/en-us/powerapps/developer/model-driven-apps/clientapi/reference/controls/addcustomfilter
                    formContext.getControl("jcmvp_director").addPreSearch(JCMVP.Collection.Form.filterMovieDirectors);    
                }

            },
            function (error) {
                console.log(error.message);
                // handle error conditions
            }

            
        );

    }

We start with calling the getCollectionsMovies function on the form load event. This function will execute our query to get the related movies and director column filtering on the collection table id your currently on.
– Xrm.WebApi.retrieveMultipleRecords(“jcmvp_movie”, “?$select=_jcmvp_director_value&$filter=(_jcmvp_collection_value eq ” + collectionID + “)”).then(

This is an asynchronous call that when it completes it will work towards building our filter and storing it on our global variable “fetchDirectorFilter”. An example of the built filter statement looks like this:
“<filter type=’and’> <condition attribute = ‘jcmvp_directorid’ operator = ‘in’ uitype=’jcmvp_director’><value>889c9059-865d-eb11-a812-000d3a84f1a3</value><value>03fa2f78-865d-eb11-a812-000d3a84f1a3</value><value>889c9059-865d-eb11-a812-000d3a84f1a3</value><value>40d8e290-865d-eb11-a812-000d3a84f1a3</value><value>78d8e290-865d-eb11-a812-000d3a84f1a3</value><value>93034497-865d-eb11-a812-000d3a84f1a3</value><value>15920af3-6d5f-eb11-a812-000d3a84f1a3</value></condition ></filter >”.

After our filter statement has been created we’re going then go ahead and use the addPreSearch function. formContext.getControl(“jcmvp_director”).addPreSearch(JCMVP.Collection.Form.filterMovieDirectors);). This will allow for when the user clicks on the director lookup on the collection form it fires the filterMovieDirectors function.

When the user clicks on the director field it will execute the function filterMovieDiretors. This function will use our built filter and set it for the lookup. formContext.getControl(“jcmvp_director”).addCustomFilter(fetchDirectorFilter, “jcmvp_director”);

This code scenario is covering when the form loads or refreshes. What it doesn’t cover is updating the director list when a movie is added to the collection. I’ll work through this and post an additional blog post when I have that completed.

Managed Solutions V9 thoughts

The great debate between unmanaged and managed solutions rages on. This discussion started when solutions was first introduced in Dynamics CRM 2011. Many people go the route of unmanaged being burned by managed; each person with their own battle scars.

Personally I’ve been burned by unmanaged which makes on the managed side of the fence. It hasn’t been easy along the journey of these solutions but with the changes introduced in Dynamics CRM 2016 around patching and being able to actually remove technical debt or depreciated fields from a managed solution made the viability much higher in my opinion.

It’s worth considering Microsoft’s view point of managed solutions in every environment past development organizations. It can be easily viewed in the new v9 solution changes happening at the platform level. As the CRM product family has been expanding over the past few years we’ve seen the sales & service & marketing split into separately SKUs and available individually now. This has been done through managed solutions and Microsoft’s ability to layer them properly.

As an example of this I simply created a new solution and added the out of the box Account entity and exported it as unmanaged. When looking at the solution file it shows every dependency for the account entity and what solutions it comes from. A few sample are:

     <MissingDependency>
        <Required key="26" type="1" schemaName="territory" displayName="Territory" solution="msdynce_AppCommon (9.0.4.0022)" />
        <Dependent key="27" type="10" schemaName="msdyn_territory_account_ServiceTerritory" displayName="msdyn_territory_account_ServiceTerritory" parentSchemaName="account" parentDisplayName="Account" />
      </MissingDependency>
      <MissingDependency>
        <Required key="28" type="1" schemaName="bookableresource" displayName="Bookable Resource" solution="msdynce_Scheduling (9.0.0.0)" />
        <Dependent key="29" type="10" schemaName="msdyn_bookableresource_account_PreferredResource" displayName="msdyn_bookableresource_account_PreferredResource" parentSchemaName="account" parentDisplayName="Account" />
      </MissingDependency>
      <MissingDependency>
        <Required key="30" type="1" schemaName="msdyn_taxcode" displayName="Tax Code" solution="FieldService (7.4.0.74)" />
        <Dependent key="31" type="10" schemaName="msdyn_msdyn_taxcode_account_SalesTaxCode" displayName="msdyn_msdyn_taxcode_account_SalesTaxCode" parentSchemaName="account" parentDisplayName="Account" />
      </MissingDependency>
      <MissingDependency>
        <Required key="32" type="2" schemaName="name" displayName="Territory Name" parentSchemaName="territory" parentDisplayName="Territory" solution="msdynce_AppCommon (9.0.4.0022)" />
        <Dependent key="27" type="10" schemaName="msdyn_territory_account_ServiceTerritory" displayName="msdyn_territory_account_ServiceTerritory" parentSchemaName="account" parentDisplayName="Account" />
      </MissingDependency>

This gives us a bit of an inside view on solutions being used by Microsoft like “msdynce_Scheduling (9.0.0.0)” and “msdynce_AppCommon (9.0.4.0022)”. It’s really interesting how we’re able to see how they are splitting up the platform to enable different product offerings. It also shows the complexities in doing this with all of the out of the box entities. Needless to say V9 seems to be the first time we’re able to look around a bit more and understand where the platform team is coming from.

Hope you found this interesting because I sure do.