Skip to main content

Yet Another Address Autocomplete PCF Control–powered by Bing

Yet Another Address Autocomplete PCF Control–powered by Bing

In this blog post I will not go into detail in how to install all the pre-requisites that are required to build and run PCF controls. My goal was to build a new PCF control and get into coding of PCF controls as fast as possible.
image
Here are a few links to articles that will help you installing the pre-requisites (Microsoft PowerApps CLI) https://docs.microsoft.com/en-us/powerapps/developer/component-framework/get-powerapps-cli
Other good references to get into this topic:
https://toddbaginski.com/blog/how-to-create-a-powerapps-pcf-control/
https://docs.microsoft.com/en-us/powerapps/developer/component-framework/create-custom-controls-using-pcf
I looked through the Guido Preite’s https://pcf.gallery/ which will help you find appropriate use cases / examples for your own needs. It did not take very long to find a simple example to start with: Andrew Butenko's https://pcf.gallery/address-autocomplete/
A few moments later I had the idea to create yet another address autocomplete control but this time powered by the Bing API.

Bing Api Key

Reason for that decision is that with a paid Customer Engagement plan or Field Service App license you get the ability to use the D365 instance's Bing API Key, which can be found under Resource Scheduling => Settings => Administration => Scheduling Parameters.
With the Field Service App license you receive the auto-geocode feature and the geocode button for entities like account or work order. This feature also relies on the Bing API. My idea - using this key for the new Bing auto complete control should do job to make it work without buying an extra license for a Google Maps API Key.
clip_image002
For the next step I researched on how to create a PCF control without doing too much typing in a command prompt console. A very nice and nifty solution is the XrmToolBox plugin "PCF Custom Control Builder" developed by Danish Naglekar. In his blog post he explains more details on his plugin:
https://danishnaglekar.wordpress.com/2019/10/07/pcf-custom-control-builder/
I entered in all the necessary data and followed the steps to build and create this new Bing address autocomplete control:
image
General
1. Control Location: Path of the control project with all dependencies, etc.
2. Visual Studio Command Prompt Location: Path to VS 2017 CMD

Component Details
  1. Namespace
  2. Control Name
  3. Template (optional): e.g. from an existing PCF Gallery’s control
  4. Version: Auto-Incremented
  5. Create Button: Creates initial control structure
  6. Open in VS Code: will open the project’s TypeScript source file in Visual Studio Code Editor
  7. Build: Builds the control
  8. Test will run and open a browser instance with a self-hosted test kit containing your PCF control
Solution Details
  1. Initialize CDS project: Define you Solution name, Publisher name and prefix. Currently, the plugin does not allow spaces in the names.
  2. Deploy to D365 CE: Once the custom control is ready, deploy the control directly in you D365 CE or CDS environment using XrmToolbox connection

Here is the log of my solution create, add and build statements…

Create the solution

image

Add a control reference to the solution

image

Rebuild and bundling of the control from the CDS project

image
image
image
image
image
image
image
image

Deployment & Configuration

Via the "Deploy" button you can easily deploy your freshly made CDS solution to your D365 instance.
Once deployed and published you can go to your entity of choice and configure the control similar to pictures shown below.

This is my test entity "Main Address"

image
The goal is to use the "Name" field to enter an address and to have the Bing autocomplete feature as support to resolve and propose a valid address.
So I open the change properties dialog for this field and configure to use the Bing Address Autocomplete PCF control:

Add control to the Name field

image

Configure the Bing Address Autocomplete control

The configuration allows you to control on which device you can see and use available controls.
Here I chose to use my new control for Web, Phone and Tablet.
Then I linked the entity’s address fields to my control properties.
Last step is to enter a static value for the Bing Api Key.
image
If done, save and publish the changes on the entity form and you are ready to go to try it out.

Final result

On change of the name field the autocomplete control will trigger the dropdown list with suggestions.
image
Once we select an address it will automatically fill in the previously mapped fields of this control and at the end it will look like this:
image

In Action

PCF.BingAddressAutocomple_Min2

The implementation of the control

 /// <reference path="types/MicrosoftMaps/Modules/Autosuggest.d.ts" />

import { IInputs, IOutputs } from "./generated/ManifestTypes";

export class BingAddressAutocomplete implements ComponentFramework.StandardControl<IInputs, IOutputs> {

    private notifyOutputChanged: () => void;
    private searchBox: HTMLInputElement;

    private value: string;
    private street: string;
    private city: string;
    private county: string;
    private state: string;
    private zipcode: string;
    private country: string;

    constructor() {

    }

    public init(context: ComponentFramework.Context<IInputs>,
        notifyOutputChanged: () => void,
        state: ComponentFramework.Dictionary,
        container: HTMLDivElement) {
        if (typeof (context.parameters.bingapikey) === "undefined" ||
            typeof (context.parameters.bingapikey.raw) === "undefined") {
            container.innerHTML = "Please provide a valid bing api key";
            return;
        }

        this.notifyOutputChanged = notifyOutputChanged;

        this.searchBox = document.createElement("input");
        this.searchBox.setAttribute("id", "searchBox");
        this.searchBox.className = "addressAutocomplete";
        this.searchBox.addEventListener("mouseenter", this.onMouseEnter.bind(this));
        this.searchBox.addEventListener("mouseleave", this.onMouseLeave.bind(this));
        if (typeof (context.parameters.value) !== "undefined" &&
            typeof (context.parameters.value.raw) !== "undefined" && context.parameters.value.raw != null) {
            this.searchBox.setAttribute("value", context.parameters.value.raw);
        }

  container.setAttribute("id", "searchBoxContainer");
        container.appendChild(this.searchBox);

        let bingApiKey = context.parameters.bingapikey.raw;
        let scriptUrl = "https://www.bing.com/api/maps/mapcontrol?callback=loadAutoSuggest&key=" + bingApiKey;

        let scriptNode = document.createElement("script");
        scriptNode.setAttribute("type", "text/javascript");
  scriptNode.setAttribute("src", scriptUrl);
  // scriptNode.setAttribute("async", "");
  // scriptNode.setAttribute("defer", "");

        document.head.appendChild(scriptNode);
        var _this = this;
        window.setTimeout(() => {

   Microsoft.Maps.loadModule('Microsoft.Maps.AutoSuggest', {
    callback: () => {
                    var options = {maxResults: 5};
                    var manager = new Microsoft.Maps.AutosuggestManager(options);
                    manager.attachAutosuggest('#searchBox', '#searchBoxContainer', (suggestionResult) => {

                        _this.street = suggestionResult.address.addressLine;
                        _this.city = suggestionResult.address.locality;
                        _this.county = suggestionResult.address.district;
                        _this.state = suggestionResult.address.adminDistrict;
                        _this.country = suggestionResult.address.countryRegion;
                        _this.zipcode = suggestionResult.address.postalCode;
                        
                        _this.value = suggestionResult.formattedSuggestion || "";
                        _this.notifyOutputChanged();
                    });
                },
    errorCallback: () =>{alert("Error with loading of module Microsoft.Maps.AutoSuggest.");}
   });
   

        },
            1000);
 }

 private selectedSuggestion(suggestionResult: Microsoft.Maps.ISuggestionResult): void {
  
  alert(suggestionResult.formattedSuggestion);

  this.value = "";
  this.street = "";
  this.city = "";
  this.county = "";
  this.state = "";
  this.country = "";
  this.zipcode = "";
  
  this.value = suggestionResult.formattedSuggestion || "";
  this.notifyOutputChanged();
 }

    private onMouseEnter(): void {
        this.searchBox.className = "addressAutocompleteFocused";
    }

    private onMouseLeave(): void {
        this.searchBox.className = "addressAutocomplete";
    }


 /**
  * Called when any value in the property bag has changed. This includes field values, data-sets, 
         * global values such as container height and width, 
         * offline status, control metadata values such as label, visible, etc.
  * @param context The entire property bag available to control via Context Object; 
         * It contains values as set up by the customizer mapped to names defined in the manifest, as well as utility functions
  */
    public updateView(context: ComponentFramework.Context<IInputs>): void {
        // Add code to update control view
    }

 /** 
  * It is called by the framework prior to a control receiving new data. 
  * @returns an object based on nomenclature defined in manifest, expecting object[s] for property marked as “bound” or “output”
  */
    public getOutputs(): IOutputs {
        return {
            value: this.value,
            street: this.street,
            city: this.city,
            county: this.county,
            state: this.state,
            country: this.country,
            zipcode: this.zipcode
        };
    }

 /** 
  * Called when the control is to be removed from the DOM tree. Controls should use this call for cleanup.
  * i.e. cancelling any pending remote calls, removing listeners, etc.
  */
    public destroy(): void {
        // Add code to cleanup control if necessary
    }
}

Source Code & Download

https://github.com/acieslik/PCF.BingAddressAutocomplete

Comments

  1. Hi, Great component.

    I'm trying to add this into a test instance and I'm able to compile it and import it successfully. After setting up the control, entering an address in the main field nothing happens. I being typing in the address and nothing occurs, no dropdown list or anything....

    When I run the component via XRMToolbox using "Test" - It loads locally and seems to work fine. I'm not sure whats happening. Any suggestions?

    Cheers

    ReplyDelete
    Replies
    1. Well that was quick - I tried one more thing, changed the layout from 3 columns to 2 and now it's working.

      It must have something to do with the narrow columns when using 3 and it won't show the dropdown list.

      Fantastic!

      Thanks again :)

      Delete

Post a Comment

Popular posts from this blog

Regarding SPFieldMultiLineText (Add HTML/URL content to a field) programmatically

I recently tried so set some HTML content in a SharePoint list column of type SPFieldMultiLineText.

My first approach was this piece of code:

SPFieldMultiLineText field = item.Fields.GetFieldByInternalName("Associated Documents") as SPFieldMultiLineText; StringBuilder docList = new StringBuilder(); docList.Append(""); foreach (DataRow docRow in addDocs) { DataRow[] parent = dr.Table.DataSet.Tables[0].Select("DOK_ID=" + docRow["DOK_MGD_ID"].ToString()); if (parent != null && parent.Length > 0) { docList.AppendFormat("{1}", parent[0]["FilePath"].ToString(), parent[0]["Title"].ToString()); } } if (docList.Length > 0) { // remove trailing tag docList.Remove(docList.Length-5, 5); } docList.Append(" "); string newValue = docList.ToString(); item[field.Title] = newValue;
What this code does is to get all associated documents to the main document and to add these document uri links to the "Assoc…

CRM 2016 SP1: Solution import failed with Solution With Id = 12ac16ec-41d5-1685-a230-0b1c31499250 Does Not Exist

Importing Solutions with the upgrade solution (holding solution) option is still not stable with CRM 2016 SP1 On-Premise.

Ronald Lemmon already posted the same issue on his blog:
http://ronaldlemmen.blogspot.de/2015/11/solution-with-id-86ac16ec-41d7-4685.html

I expected it to be fixed with SP1 but it is still happening every now and then.

With this query and the solution id (guid) of the error message you are able to find records in the ProcessTriggerBase table that reference the guid of a failed import from a holding solution that is not existing in the system anymore.

select * from ProcessTriggerBase where solutionid = 'YOUR_SOLUTION_GUID'
Due to this reference to a solution id not existing in the system you will not be able to import a new version of the target solution to be updated.

What I did and this is probably not a supported solution is to update the guid to the actual target solution that is currently deployed in the system.

To find out the id of the target solution y…

Dynamics 365 for Outlook: Waiting for a page to be retrieved from a CRM server…

Some customers as well as on my machine I had the problem that the Dynamics 365 for Outlook never showed the entity views in the right Outlook pane.Also the message “Waiting for a page to be retrieved from a CRM server…” appeared in the ribbon bar.I first activated the trace logging of the Outlook Add-in but it did not help to find any pointers.Finally the message of the ribbon bar entered in Google Search allowed me to find the link below:https://support.microsoft.com/en-hk/help/4049314/microsoft-dynamics-365-for-outlook-is-unable-to-render-webpages-afterIt will help you to resolve this issue.