I am looking for some sample code to create a custom pop-up for search results. I have found an old thread to override the default search results pop-up, but I would like it to be dynamically populated based on attributes of a intersected features. I thought I had seen something like this before, but for the life of me cannot find it.
For example, let's say I have a census tracts feature layer with basic demographic information. When I enter an address into the search widget textbox, I would like the pop-up box to show these attributes from the feature layer for the census tract that the entered address intersects.
Added bonus, if the custom text box also has the search string, so, for instance, the searched address or coordinates or whatever. E.G. 100 E. Main St. is in Census Tract XX with a Population of X or whatever.
Thanks in advance.
Todd
Solved! Go to Solution.
Maybe this will get you going. (Written for 4.x)
searchWidget.on('select-result', function(evt){
let query = sections.createQuery();
query.geometry = evt.result.feature.geometry;
query.spatialRelationship = 'within';
query.outFields = ['*'];
query.returnGeometry = true;
query.outSpatialReference = view.spatialReference;
query.maxAllowableOffset = 0;
sections.queryFeatures(query).then(function(result){
view.popup.open({
features: result.features,
location: result.features[0].geometry.centroid
});
});
});
Hi @ToddFagin, have you seen this sample?
https://developers.arcgis.com/javascript/latest/sample-code/widgets-search-multiplesource/
This sample uses a LayerSearchSource with the Search widget, and opens the pop-up on the featureLayer based on the features' attributes. It does have the caveat that if you're using a layer as the source, the same layer cannot be added to the map. I'm sure there are clever ways to work around this if necessary (e.g. a duplicate layer).
Thank you. I have, indeed, seen this and it is what I am using to search on multiple data sources.
Here's my quandary. Say someone searches on a census tract layer by some value. This isn't a problem--the maps zooms to that tract and the popup appears. However, let's say someone just enters a street address or a lat,lon. The maps will zoom to that location, but there is a default popup with the entered search (e.g. address or lat, lon). I want the popup to show the census tract information at that location. Of course, the user can get it with an additional click, I just want it to be seamless.
I am not actually using census tract data, that was just my example. I am actually using PLSS data. So, if someone searches by a PLSS layer, say Section-Township-Range (STR), they will be returned with various attributes in a popup associated with that STR. However, they may not know the STR information, but have coordinates and search by that. If so, I want the popup to show the STR attributes associated with that set of coordinates.
Hopefully, that makes sense.
Many thanks.
As I continued to search for a solution to this issue, I came across an older post and a solution by @RobertScheitlin__GISP that does, more or less, what I want. However, it was written for 3.23. I am trying to get it to week with 4.22, though, because it will be part of a larger project that is otherwise functioning well.
In a nutshell, I would like to use the search widget to perform a search and query a feature layer based on the results of this search. For instance, say I have a PLSS layer and when one enters an address or set of coordinates, the PLSS layer is queried at that search location and the pop-up returns the PLSS attributes associated with that location.
I hope to solve this first, but to add a little more complexity to the issue, I am actually searching multiple sources. Searching by multiple layer sources works great when the appropriate search term is entered (e.g. township, range, and section) information, but I essentially want a reverse geocode type solution in which someone types an address or enters coordinates and the PLSS layer is queried at that location and those results are returned.
At issue (among many other things) is I am not particularly savvy when it comes to JS. I can take others' code and modify it to my needs, but that is the extent of it--so I basically need a lot of handhold.
Below is the code I have working that includes everything except the spatial query. The feature layers I am using in it are just place holders and are not the ones I will actually be using going forward (so the attributes that pop up are a bit nonsensical at the moment).
<html>
<head>
<meta name="description" content="DevLav: Query a feature layer">
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<title>PLSS Coordinate Calculator</title>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
.esri-view:fullscreen {
background-color: #fff;
}
</style>
<link rel="stylesheet" href="https://js.arcgis.com/4.22/esri/css/main.css">
<script src="https://js.arcgis.com/4.22/"></script>
</head>
<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/widgets/CoordinateConversion",
"esri/layers/GroupLayer",
"esri/layers/FeatureLayer",
"esri/layers/MapImageLayer",
"esri/widgets/Home",
"esri/widgets/Search",
"esri/widgets/BasemapGallery",
"esri/widgets/LayerList",
"esri/widgets/Expand",
"esri/renderers/SimpleRenderer",
"esri/geometry/Extent",
"esri/geometry/SpatialReference",
"esri/tasks/support/Query",
"esri/tasks/QueryTask",
"esri/Graphic",
"esri/widgets/Fullscreen"
],
function(
Map,
MapView,
CoordinateConversion,
GroupLayer,
FeatureLayer,
MapImageLayer,
Home,
Search,
BasemapGallery,
LayerList,
Expand,
SimpleRenderer,
Extent,
SpatialReference,
Query,
QueryTask,
Graphic,
Fullscreen
) {
//map extent: Need this since no basemap; otherwise extent is pretty wonky
var bounds = new Extent({
"xmin":-103.5,
"ymin":33.0,
"xmax":-93.5,
"ymax":37.5,
"spatialReference":{"wkid":4326} //this is for the extent only; need to set map spatial reference in view.
});
// Oklahoma Counties Layer
var okcounties = new FeatureLayer({
url: "https://obsgis.csa.ou.edu:6443/arcgis/rest/services/ONHI/ArcGISServer_Counties/FeatureServer",
title: "Oklahoma Counties",
});
var trtemplate = {
// autocasts as new PopupTemplate()
title: "<strong>{label2} Centroid</strong>:",
content: "DD Centroid: {label2}<br> Level II Ecoregion: {label2}<br> Level I Ecoregion: {label2}"
};
// Township/Range
var townships = new FeatureLayer({
url: "https://obsgis.csa.ou.edu:6443/arcgis/rest/services/ONHI/PLSS/MapServer/0/",
outFields: ["*"],
title: "Township/Range"
//popupTemplate: template
});
// Sections
var sections = new FeatureLayer({
url: "https://obsgis.csa.ou.edu:6443/arcgis/rest/services/ONHI/PLSS/MapServer/1/",
outFields: ["*"],
title: "Section"
//popupTemplate: template
});
// Create GroupLayer for PLSS data
var PLSS = new GroupLayer({
title: "PLSS Data",
visible: false,
visibilityMode: "independent",
layers: [townships, sections]
});
var map = new Map({
//basemap: "satellite",
layers: [PLSS, okcounties]
});
var view = new MapView({
container: "viewDiv",
map: map,
//center: [-98.762150, 35.287798],
//zoom: 8,
extent: bounds,
spatialReference: 3857 //spatial reference of map; different from the extent
});
//Home button
var homeBtn = new Home({
view: view
});
// Add the home button to the top left corner of the view
view.ui.add(homeBtn, "top-left");
// create a search widget
var searchWidget = new Search({
view: view,
sources: [{
layer: new FeatureLayer({ //Notice the property is called layer Not featureLayer new to 4.11
url: "https://obsgis.csa.ou.edu:6443/arcgis/rest/services/ONHI/PLSS/MapServer/0/",
popupTemplate: trtemplate,
//{ // autocasts as new PopupTemplate()
//title: "{label2}",
// overwriteActions: true
//}
}),
searchFields: ["label2"],
displayField: "label2",
exactMatch: false,
outFields: ["label2"],
name: "Township/Range",
placeholder: "example: 12N 10W IM",
},
{
layer: new FeatureLayer({ //Notice the property is called layer Not featureLayer new to 4.11
url: "https://obsgis.csa.ou.edu:6443/arcgis/rest/services/ONHI/PLSS/MapServer/1/",
popupTemplate: { // autocasts as new PopupTemplate()
title: "{str_label2}",
overwriteActions: true
}
}),
searchFields: ["str_label2"],
displayField: "str_label2",
exactMatch: false,
outFields: ["str_label2"],
name: "Section Township/Range",
placeholder: "example: Sec. 15 12N 10W IM",
},
{
layer: new FeatureLayer({ //Notice the property is called layer Not featureLayer new to 4.11
url: "https://obsgis.csa.ou.edu:6443/arcgis/rest/services/ONHI/ArcGISServer_Counties/FeatureServer/0",
popupTemplate: { // autocasts as new PopupTemplate()
title: "{name} County",
overwriteActions: true
}
}),
searchFields: ["name"],
displayField: "name",
exactMatch: false,
outFields: ["name"],
name: "Counties",
placeholder: "example: Adair",
}]
});
// Add the search widget to the top right corner of the view
view.ui.add(searchWidget, {
position: "top-right"
});
// Create a BasemapGallery widget instance and set
// its container to a div element
var basemapGallery = new BasemapGallery({
view: view,
container: document.createElement("div")
});
// Create an Expand instance for basemap gallery
var bgExpand = new Expand({
view: view,
expandTooltip: "Select basemap",
content: basemapGallery
});
// Add the expand instance to the ui
view.ui.add(bgExpand, "top-left");
// Add a legend instance to the panel of a
// ListItem in a LayerList instance
const layerList = new LayerList({
view: view,
listItemCreatedFunction: function(event) {
const item = event.item;
if (item.layer.type != "group")
{ // don't show legend twice
item.panel = {
content: "legend",
open: false
};
}
}
});
// Create an Expand instance for legend gallery
var lgExpand = new Expand({
view: view,
expandTooltip: "Expand Layer List",
content: layerList
});
// Add the expand instance to the ui
view.ui.add(lgExpand, "top-left");
fullscreen = new Fullscreen({
view: view
});
view.ui.add(fullscreen, "top-right");
//Coordinate Conversion Widget
const ccWidget = new CoordinateConversion({
view: view
});
view.ui.add(ccWidget, "bottom-left");
});
</script>
</head>
<body>
<div id="viewDiv"></div>
</body>
</html>
Maybe this will get you going. (Written for 4.x)
searchWidget.on('select-result', function(evt){
let query = sections.createQuery();
query.geometry = evt.result.feature.geometry;
query.spatialRelationship = 'within';
query.outFields = ['*'];
query.returnGeometry = true;
query.outSpatialReference = view.spatialReference;
query.maxAllowableOffset = 0;
sections.queryFeatures(query).then(function(result){
view.popup.open({
features: result.features,
location: result.features[0].geometry.centroid
});
});
});
Thank you, Robert. I love it that you are listed as "MVP Esteemed Contributor." I can attest to this.
Cheers.
Hi @ToddFagin ,
Would it be possible for you to post your working code? I have a similar situation.
I apologize, I just saw this (over a year late). Do you still need this?
In case the target layer is part of the map, You can also use the fetchFeatures parameter to open the popup immediately without a separate query.
I just saw a buggy behavior, where the popup wouldn't open when the search result is not visible in the current extent of the view. It seems like there is a race condition with some pending promises from updating the view (panning/zooming) accordingly. When I delay the view.popup.open() with setTimeout(), the popup opens properly after updating the view. It's ugly but works in the tested cases. I did not find a better solution. I know that I can pass pending promises to view.popup.promises, but I don't know how to retrieve the open promises from the view.
const searchWidget = new Search({
view: view,
sources: [
{
layer: new FeatureLayer({
url: "..."
}),
outFields: ["*"],
name: "...",
exactMatch: false,
placeholder: "",
searchFields: ["..."],
displayField: "...",
suggestionsEnabled: true,
maxSuggestions: 5,
popupEnabled: false
}
],
enableSearchingAll: true
});
searchWidget.on("select-result", function(evt) {
const geom = (evt.result.feature.geometry.type === "point") ? evt.result.feature.geometry : evt.result.feature.geometry.centroid;
setTimeout(function() {
view.popup.open({
location: geom, // location of the click on the view
fetchFeatures: true // display the content for the selected feature if a popupTemplate is defined.
});
}, 500);
});
view.ui.add(searchWidget, {
position: "top-right"
});