/*Create a geocoder widget
Will display multiple locators if organization has more than one locator defined
Adds support for info window that allows users to find different search results */
define(["dojo/_base/declare", "dojo/Deferred", "esri/dijit/Geocoder", "esri/dijit/PopupTemplate", "esri/layers/FeatureLayer", "esri/geometry/Extent", "esri/geometry/Point", "esri/lang", "dojo/dom-construct", "dojo/dom", "dojo/dom-style", "dojo/on", "dijit/registry", "dojo/query", "dojo/_base/lang", "dojo/_base/array"
], function (
declare, Deferred, Geocoder, PopupTemplate, FeatureLayer, Extent, Point, esriLang, domConstruct, dom, domStyle, on, registry, query, lang, array
) {
return declare(null, {
map: null,
geocoder: null,
allResults: null,
config: null,
content: null,
geocoders: [],
geocodeFeatureLayers: {},
constructor: function (args) {
this.map = args.map;
this.config = args.config;
var options = this._createGeocoderOptions();
this.geocoder = new Geocoder(options, this.geocoderDiv);
this.geocoder.startup();
this.geocoder.on("find-results", lang.hitch(this, this.checkResults));
this.geocoder.on("select", lang.hitch(this, this.showGeocodingResult));
this.geocoder.on("auto-complete", lang.hitch(this, this.clearGeocodeResults));
this.geocoder.on("clear", lang.hitch(this, this.clearGeocodeResults));
},
checkResults: function (geocodeResults) {
this.allResults = null;
if (geocodeResults && geocodeResults.results && geocodeResults.results.results) {
geocodeResults.results = geocodeResults.results.results;
}
if ((!geocodeResults || !geocodeResults.results || !geocodeResults.results.length)) {
console.log("No results found");
} else if (geocodeResults) {
this.allResults = geocodeResults.results;
}
},
clearGeocodeResults: function () {
if (this.map.infoWindow.isShowing) {
this.map.infoWindow.hide();
}
this.allResults = null;
},
showGeocodingResult: function (result, pos) {
this.map.infoWindow.hide();
var bestView, anchorPoint;
var geocodeResult = result.result || result;
anchorPoint = geocodeResult.feature.geometry;
if (anchorPoint.type === "polygon") {
anchorPoint = anchorPoint.getCentroid();
bestView = geocodeResult.feature.geometry.getExtent().expand(1.1);
} else if (anchorPoint.type === "polyline") {
anchorPoint = anchorPoint.getPoint(0, 0);
bestView = geocodeResult.feature.geometry.getExtent().expand(1.1);
} else {
bestView = this.map.extent.centerAt(anchorPoint).expand(0.0625);
}
//Add feature search support
var featureSearch = false;
if (result.target && result.target.activeGeocoder) {
var activeGeocoder = result.target.activeGeocoder;
if (esriLang.isDefined(activeGeocoder.type)) {
if (activeGeocoder.type === "query") {
//get the layer
if (activeGeocoder.layerId) {
var layer = this.map.getLayer(activeGeocoder.layerId);
if (activeGeocoder.subLayerId) {
if (layer.infoTemplates && layer.infoTemplates[activeGeocoder.subLayerId]) {
geocodeResult.feature.infoTemplate = layer.infoTemplates[activeGeocoder.subLayerId].infoTemplate;
featureSearch = true;
this._getPopupFeatureLayer(layer, activeGeocoder.subLayerId, geocodeResult.feature.infoTemplate).then(lang.hitch(this, function (layerResult) {
//Info template defined so create a feature layer and display the popup
geocodeResult.feature._layer = layerResult;
this.map.infoWindow.setFeatures([geocodeResult.feature]);
this.map.infoWindow.show(anchorPoint);
this.map.setExtent(bestView);
}));
} else {
//No info template defined so just show popup with info"
this.map.infoWindow.setTitle(activeGeocoder.name);
this.map.infoWindow.setContent(geocodeResult.name);
this.map.infoWindow.show(anchorPoint);
this.map.setExtent(bestView);
featureSearch = true;
}
} else {
//Feature layer so get the popup info, associate it with the result, then display
if (layer.infoTemplate) {
geocodeResult.feature.infoTemplate = layer.infoTemplate;
geocodeResult.feature._layer = layer;
this.map.infoWindow.setFeatures([geocodeResult.feature]);
this.map.infoWindow.show(anchorPoint);
this.map.setExtent(bestView);
featureSearch = true;
}
}
}
}
}
}
if (featureSearch) {
return;
}
if (!esriLang.isDefined(pos)) {
pos = 0;
}
//Locator based geocode results handled here
this.setupInfoWindowAndZoom(geocodeResult.name, geocodeResult.feature.geometry, bestView, geocodeResult, pos);
},
_getPopupFeatureLayer: function (mapLayer, subLayerId, popupInfo) {
var deferred = new Deferred();
if (this.geocodeFeatureLayers[mapLayer.id] && this.geocodeFeatureLayers[mapLayer.id][subLayerId]) {
//already have it
deferred.resolve(this.geocodeFeatureLayers[mapLayer.id][subLayerId]);
}
var url = mapLayer.url + "/" + subLayerId;
if (mapLayer.dynamicLayerInfos) {
array.some(mapLayer.dynamicLayerInfos, lang.hitch(this, function (dynLayerInfo) {
if (dynLayerInfo.id === subLayerId) { //don't have this info but leaving for now
url = mapLayer.url + "/" + dynLayerInfo.source.mapLayerId;
return true;
}
}));
}
var params = {
mode: FeatureLayer.MODE_SELECTION,
outFields: ["*"],
infoTemplate: popupInfo && new PopupTemplate(popupInfo)
};
var layer = new FeatureLayer(url, params);
//save the layer for later
this.geocodeFeatureLayers[mapLayer.id] = this.geocodeFeatureLayers[mapLayer.id] || {};
this.geocodeFeatureLayers[mapLayer.id][subLayerId] = layer;
deferred.resolve(layer);
return deferred.promise;
},
setupInfoWindowAndZoom: function (content, geocodeLocation, newExtent, geocodeResult, pos) {
this.map.infoWindow.clearFeatures();
//Show info window
if (this.allResults && this.allResults.length > 1) {
//let's update the content to show additional results
var currentLocationName = content;
var attr = this.allResults[pos].feature.attributes;
content = "<div id='geocodeCurrentResult' style='display:none;'><span style='font-weight:bold;'>";
content += "Current Location"; //this.config.i18n.viewer.main.search.currentLocation;
content += "</span></div>";
content += "<span>";
if (!attr.Match_addr) {
content += currentLocationName;
} else {
content += attr.Match_addr;
if (attr.stAddr && attr.City) {
content += " - " + attr.stAddr + ", " + attr.City;
} else if (attr.stAddr) {
content += " - " + attr.stAddr;
}
}
content += "</span>";
content += "<div id='geocodeWantOtherResults'>";
content += "<a id='results' style='cursor:pointer'>";
content += "Not what you wanted?"; //this.config.i18n.viewer.main.search.notWhatYouWanted;
content += "</a>";
content += "</div>";
content += "<div id='geocodeOtherResults' style='display:none;'><span style='font-weight:bold;'>";
content += "Select another location"; //this.config.i18n.viewer.main.search.selectAnother;
content += "</span><br/>";
for (var i = 0; i < this.allResults.length; i++) {
if (i !== pos) {
var result = this.allResults[i];
attr = result.feature.attributes;
content += "<a style='cursor:pointer' class='li_item' id=" + i + ">";
if (!attr.Match_addr) {
content += result.name;
} else {
//content += result.feature.attributes.Place_addr ? (" - " + result.feature.attributes.Place_addr) : ""
content += attr.Match_addr;
if (attr.stAddr && attr.City) {
content += " - " + attr.stAddr + ", " + attr.City;
} else if (attr.stAddr) {
content += " - " + attr.stAddr;
}
}
content += "</a><br/>";
}
}
content += "</div>";
}
//display a popup for the result
//this.config.i18n.viewer.main.search.popupTitle
this.map.infoWindow.setTitle("Location");
this.map.infoWindow.setContent(content);
query(".li_item").forEach(lang.hitch(this, function (node) {
on(node, "click", lang.hitch(this, function () {
if (node.id >= 0) {
this.selectAnotherResult(node.id);
}
}));
}));
var resDiv = dom.byId("results");
if (resDiv) {
on(resDiv, "click", lang.hitch(this, function () {
this.showOtherResults();
}));
}
var location = new Point(geocodeLocation.x, geocodeLocation.y, geocodeLocation.spatialReference);
on.once(this.map, "extent-change", lang.hitch(this, function () {
this.map.infoWindow.show(location);
}));
this.map.setExtent(newExtent);
},
showOtherResults: function () {
domStyle.set(dom.byId("geocodeWantOtherResults"), "display", "none");
domStyle.set(dom.byId("geocodeCurrentResult"), "display", "block");
domStyle.set(dom.byId("geocodeOtherResults"), "display", "block");
},
selectAnotherResult: function (pos) {
this.showGeocodingResult(this.allResults[pos], pos);
},
_createGeocoderOptions: function () {
//Check for multiple geocoder support and setup options for geocoder widget.
var hasEsri = false,
geocoders = lang.clone(this.config.helperServices.geocode);
array.forEach(geocoders, function (geocoder, index) {
if (geocoder.url.indexOf(".arcgis.com/arcgis/rest/services/World/GeocodeServer") > -1) {
hasEsri = true;
geocoder.name = "Esri World Geocoder";
geocoder.outFields = "Match_addr, stAddr, City";
geocoder.singleLineFieldName = "SingleLine";
geocoder.esri = geocoder.placefinding = true;
}
});
//only use geocoders with a singleLineFieldName that allow placefinding
geocoders = array.filter(geocoders, function (geocoder) {
return (esriLang.isDefined(geocoder.singleLineFieldName) && esriLang.isDefined(geocoder.placefinding) && geocoder.placefinding);
});
var esriIdx;
if (hasEsri) {
for (var i = 0; i < geocoders.length; i++) {
if (esriLang.isDefined(geocoders[i].esri) && geocoders[i].esri === true) {
esriIdx = i;
break;
}
}
}
var options = {
map: this.map,
autoNavigate: false,
minCharacters: 0,
maxLocations: 5,
searchDelay: 100,
theme: "simpleGeocoder",
autoComplete: hasEsri
};
//If there is a valid search id and field defined add the feature layer to the geocoder array
var searchLayers = [];
if (this.config.response.itemInfo.itemData && this.config.response.itemInfo.itemData.applicationProperties && this.config.response.itemInfo.itemData.applicationProperties.viewing && this.config.response.itemInfo.itemData.applicationProperties.viewing.search) {
var searchOptions = this.config.response.itemInfo.itemData.applicationProperties.viewing.search;
array.forEach(searchOptions.layers, lang.hitch(this, function (searchLayer) {
var operationalLayers = this.config.itemInfo.itemData.operationalLayers;
var layer = null;
array.some(operationalLayers, function (opLayer) {
if (opLayer.id === searchLayer.id) {
layer = opLayer;
return true;
}
});
var url = layer.url;
var field = searchLayer.field.name;
var name = layer.title;
if (esriLang.isDefined(searchLayer.subLayer)) {
url = url + "/" + searchLayer.subLayer;
array.some(layer.layerObject.layerInfos, function (info) {
if (info.id == searchLayer.subLayer) {
name += " - " + layer.layerObject.layerInfos[searchLayer.subLayer].name;
return true;
}
});
}
searchLayers.push({
"name": name,
"url": url,
"field": field,
"exactMatch": (searchLayer.field.exactMatch || false),
"placeholder": searchOptions.hintText,
"outFields": "*",
"type": "query",
"layerId": searchLayer.id,
"subLayerId": parseInt(searchLayer.subLayer) || null
});
}));
}
if (hasEsri && esriIdx === 0) { // Esri geocoder is primary
options.arcgisGeocoder = false;
if (geocoders.length > 0) {
options.geocoders = searchLayers.length ? searchLayers.concat(geocoders) : geocoders;
} else if (searchLayers.length > 0) {
options.geocoders = searchLayers;
}
} else { // Esri geocoder is not primary
options.arcgisGeocoder = false;
options.geocoders = searchLayers.length ? searchLayers.concat(geocoders) : geocoders;
}
return options;
}
});
});