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.
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.
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>
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 |
