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.
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.
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.
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:
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
Solution Details
Here is the log of my solution create, add and build statements…
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.
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:
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.
If done, save and publish the changes on the entity form and you are ready to go to try it out.
On change of the name field the autocomplete control will trigger the dropdown list with suggestions.
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:
/// <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 } }
https://github.com/acieslik/PCF.BingAddressAutocomplete
Original Post https://code2life.blogspot.com/2019/11/yet-another-address-autocomplete-pcf.html