Skip to main content

How to add your personal PowerBI Dashboard as a system dashboard

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
  2. Hi,

    I was not able to map the OOB address fields but worked fine with custom fields. Am I missing something or is it a limitation ?

    ReplyDelete
  3. .NET Framework 4.6.2 Developer Pack is a requirement to build this in XRMToolKit. Just thought I would post this incase it helps someone. Didn't workw ith 4.8 version.

    ReplyDelete
  4. If you want to add Longitude and Latitude use .location like below

    _this.longitude = suggestionResult.location.longitude.toString();

    ReplyDelete
  5. This comment has been removed by the author.

    ReplyDelete

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…

Set Multi-OptionSet values with a custom workflow activity

As the relatively new multi-optionset field type arrived with v.9 of Dynamics 365 the need to set values via workflow is quite a common requirement. So I searched within the community to find some ideas on how to solve and create such a workflow activity and so I stumbled upon Demian Raschkovan’s Workflow Tools with can be found on his github repository: https://github.com/demianrasko/Dynamics-365-Workflow-Tools/blob/master/msdyncrmWorkflowTools/msdyncrmWorkflowTools/Class/MapMultiSelectOptionSet.cs

It gave me some basic ideas to reach my requirements which are: Should be generic for any type of entity Ability to specify the attribute name of the required multi-optionset for that entity Provide a list of multi-optionset values (comma-separated) Keep existing values (True/Yes => add provided values / False/No => replace all values with the provided values Remove specific value(s) from an existing set of values
My code sample (without other dependencies):
using Microsoft.Xrm.Sdk; using Micr…