This blog has been written by Sankar Madhappan from the Oracle Cloud Engineering team.

In this blog we'll learn how to develop a reusable UI component that can be integrated into pages in your application, provide an address search field and a map to display the address specified. Bing Map Component


This component can be used for multiple purposes 

  • Uniquely identify a location (Search can search for a location without knowing the exact street address and Bing Maps will return the actual address stored on the system. Users can pick the right address from the list.
  • We can see an address on the map to confirm if the address we are referring to is the correct one.
  • An address validation tool – When a user enters an address on multiple fields to validate the address, we can call Bing map API and it will return the address list. Users can select the specific address by following this process and avoid adding duplicate addresses with small deviations. 

Pre-requisites

The component requires us to get a Bing Maps API key to consume Bing Maps API Services. Here are the steps to get this key:

  • Create a Microsoft Account ( https://www.bingmapsportal.com/)
  • Once Account is created go to https://www.bingmapsportal.com/ in the top navigation choose MyAcccount > keys
  • Fill Application details and then click create 
  • Yours key will be created, and you'll be able to use it in the component.
     

Component Functionality and Flow

  • This component exposes Search Box where the user can enter the address.
  • As soon as the user starts typing the address, the backend listener is invoked which calls the Bing Maps API to fetch addresses that matches the typed address.
  • Users can keep on typing until they see the address they are looking for.
  • Once the user sees their address, they can select the address. 
  • If displaying the map is enabled the selected address is shown in the map or the selected address stored in the variable is mapped to the search box.

 

Creating the component

In the resources->components section of your application click to create a new component and provide the name, for example "bing-map" – this will create several files in the directory. These files construct the component.

Create component

Here is the content to get into each file:

bing-map-view.html

<oj-bind-if test=[[$properties.searchLocation]]>
  <div id='searchBoxContainer' class="oj-flex oj-sm-flex-direction-column">
    <label for="searchBox" class="oj-flex-item oj-sm-flex-initial">Search Location</label>
    <input type="text" id="searchBox">
  </div>
</oj-bind-if>
<div class="oj-flex">
  <div id="bingmap-auto-suggestion" class="oj-flex-item oj-sm-flex-initial" style="height: 300px; width: 100%x"></div>
</div>

component.js

{
  "name": "bing-map",
  "displayName": "bing-map",
  "description": "A description of bing-map.",
  "version": "1.0.0",
  "jetVersion": ">= 9.0.0",
  "properties": {
    "apikey": {
      "type": "string",
      "description": "apikey to load the map",
      "writeback": false
    },
    "latitude": {
      "type": "number",
      "description": " of the map",
      "writeback": true
    },
    "longitude": {
      "type": "number",
      "description": "longitude of the map",
      "writeback": true
    },
    "maxresults": {
      "type": "number",
      "description": "maxResults",
      "writeback": true
    },
    "searchLocation": {
      "type": "boolean",
      "description": "maxResults",
      "writeback": false
    },
    "searchmap": {
      "type": "boolean",
      "description": "maxResults",
      "writeback": false
    }
  },
  "methods": {},
  "events": {},
  "slots": {}
}

bing-map-viewModel.js

define(['knockout','ojs/ojcontext'], (ko,ojContext) => {
  'use strict';

  var url = 'https://www.bing.com';
  function isScriptLoaded(url) {
    var scripts = document.getElementsByTagName('script');
    for (var i = scripts.length; i--;) {
      if (scripts[i].src == url) return true;
    }
    return false;
  }

  class BingMapComponentModel {
    constructor(context) {
      var apiKey = context.properties.apikey;//'Ajwb91VqywmyUKDAnDNVbL4NFjEjVD_9oqdBlxvLXz_UXzR7mBZg0gnmdMB6amBJ';
      var scriptUri = url + '/api/maps/mapcontrol?key=' + apiKey;

      if (!isScriptLoaded(scriptUri)) {
        var script = document.createElement('script');
        script.async = false;
        script.defer = false;
        script.src = scriptUri;
        document.body.appendChild(script);
        console.log('script loaded...');
      }

      var self = this;
      self.properties = context.properties;
      console.log("properties", self.properties);
      self.latitude = ko.observable(0);
      self.longitude = ko.observable(0);
      self.searchText = ko.observable('Kanpur');
      // At the start of your viewModel constructor.
      var busyContext = ojContext.getContext(context.element).getBusyContext();
      var options = { 'description': 'Component Startup - Waiting for data' };
      self.busyResolve = busyContext.addBusyState(options);
      self.composite = context.element;
      // self.properties = context.properties;
      self.updateMap = function () {
        let searchText = self.searchText();
        Microsoft.Maps.loadModule('Microsoft.Maps.Search', function () {
          var searchManager = new Microsoft.Maps.Search.SearchManager(self.map);
          var requestOptions = {
            bounds: self.map.getBounds(),
            where: searchText,
            callback: function (answer, userData) {
              self.map.setView({ bounds: answer.results[0].bestView });
              self.map.entities.push(new Microsoft.Maps.Pushpin(answer.results[0].location));
            }
          };
          searchManager.geocode(requestOptions);
        });
      };

      self.getMap = function () {
        console.log("loading map...");
        self.map = new Microsoft.Maps.Map(document.getElementById('bingmap-auto-suggestion'), {
          /* No need to set credentials if already passed in URL */
          center: new Microsoft.Maps.Location(self.properties.latitude, self.properties.longitude),
          //center: new Microsoft.Maps.Location(self.latitude, self.longitude),
          zoom: 12
        });

        if (self.properties.searchmap) {
          Microsoft.Maps.loadModule('Microsoft.Maps.AutoSuggest', {
            callback: function () {
              var options = {
                maxResults: self.properties.maxResults,
                useMapView: true,
                autoDetectLocation: true,
                map: self.map
              };
              var manager = new Microsoft.Maps.AutosuggestManager(options);
              manager.attachAutosuggest('#searchBox', '#searchBoxContainer',
                function (suggestionResult) {
                  self.map.entities.clear();
                  self.latitude(suggestionResult.location.latitude);
                  self.longitude(suggestionResult.location.longitude);
                  self.searchText(suggestionResult.formattedSuggestion);
                  self.updateMap();
                });
            },
            errorCallback: function (message) {
              //context.properties.error = message;
            }
          });
        }

      };

      // Once all startup and async activities have finished, relocate if there are any async activities.
      self.busyResolve();
    }
    // Lifecycle methods - uncomment and implement if necessary.

    // activated(context) {
    // };

    connected(context) {
      let handle = setInterval(() => {
        if (typeof Microsoft != 'undefined') {
          clearInterval(handle);
          this.getMap();
        }
      }, 1000);
    };

    // bindingsApplied(context) {
    // };

    // propertyChanged(propertyChangedContext) {
    // };

    // BingMapComponentModel.prototype.disconnected = function(element) {
    // };
  }

  return BingMapComponentModel;
});

Using the component

The component will show up in your component palette (use the search at the top to find it), and you can drag it into any page. Once on the page you'll need to set some components properties, including the API Key, and initial longitude and latitude coordinates for the map, you can also specify true for the property that will show the search box. Then the component will become visible and you can start using it.
 

  <bing-map class="oj-flex-item oj-sm-12 oj-md-12"
    apikey="Ajwb91VqywmyUKDAnDNVbL4NFjEjVD_9oqdBlxvLXz_UXzR7" latitude="30.243071958441103"
    longitude="-97.72160968798383" search-location="true"></bing-map>

 

Component at design time

Here are the other attributes of the component that you can use to fine tune the behavior:

Name Type Writeback
APIKey
APIKey of the Bing Map to consume it’s api
String False
Latitidue
The latitude of searched address
String True
Longtitude
The longtitude of searched address
String True
MaxResults 
The number of results shown on the screen
Number False
SearchLocation
Enable search functionality
Boolean False
SearchMap
Enable map when user selects an address
Boolean False
SelectedAddress
Store the address  selected 
String True
DisableComponet
Enable/disable the component
Boolean True