I am currently working with ArcGIS API for Javascript 4.3 and I am having issues with creating dependent drop downs as a means of filtering multiple fields. For instance, in the following Feature Service: Layer: states (ID: 3) , I am wanting to have one drop down that is populated by SUB_REGION and then another dependent drop down that is populated by STATE_NAME based on the first drop down's selection. I am fairly new to this so any suggestions or samples would be greatly appreciated.
Solved! Go to Solution.
Hi Robert,
Thank you for your suggestion. My next question is; once the first value is chosen and you set the where clause for the second query, do you repeat the same steps for the second dropdown that you did for the first?
Below is my code:
require([
"esri/Map",
"esri/views/MapView",
"esri/layers/FeatureLayer",
"dojo/on",
"dojo/dom",
"dojo/dom-construct",
"dojo/domReady!"
],
function (
Map, MapView,
FeatureLayer,
on, dom, domConstruct
) {
var statesURL = "https://sampleserver6.arcgisonline.com/arcgis/rest/services/Census/MapServer/3"
var subRegionSelect = dom.byId("subRegion");
var stateNameSelect = dom.byId("stateName");
var statesLayer = new FeatureLayer({
url: statesURL,
outFields: ["SUB_REGION", "STATE_NAME"],
visible: false
});
var map = new Map({
basemap: "gray",
layers: [statesLayer]
});
var view = new MapView({
container: "viewDiv", // Reference to the scene div created in step 5
map: map, // Reference to the map object created before the scene
zoom: 4, // Sets the zoom level based on level of detail (LOD)
center: [-96.01956, 39.967514] // Sets the center point of view in lon/lat
});
view.then(function () {
return statesLayer.then(function () {
var subRegionQuery = statesLayer.createQuery();
return statesLayer.queryFeatures(subRegionQuery);
});
})
.then(getValues)
.then(getUniqueValues)
.then(addToSelect);
// return an array of all the values in the
// SUB_REGION field of the states layer
function getValues(response) {
var features = response.features;
var values = features.map(function (feature) {
return feature.attributes.SUB_REGION;
});
return values;
}
// return an array of unique values in
// the SUB_REGION field of the states layer
function getUniqueValues(values) {
var uniqueValues = [];
values.forEach(function (item, i) {
if ((uniqueValues.length < 1 || uniqueValues.indexOf(item) === -1) && (item !== "")) {
uniqueValues.push(item);
}
});
console.log("'" + uniqueValues + "'");
return uniqueValues;
}
// Add the unique values to the subregion
// select element. This will allow the user
// to filter states by subregion.
function addToSelect(values) {
values.sort();
values.forEach(function (value) {
var option = domConstruct.create("option");
option.text = value;
subRegionSelect.add(option);
});
return setSubRegionDefinitionExpression(subRegionSelect.value);
}
function setSubRegionDefinitionExpression(newValue) {
statesLayer.definitionExpression = "SUB_REGION = '" + newValue + "'";
if (!statesLayer.visible) {
statesLayer.visible = true;
}
console.log(newValue)
}
on(subRegionSelect, "change", function (evt) {
var type = evt.target.value;
setSubRegionDefinitionExpression(type)
//setStateNamenDefinitionExpression(type)
})
});
Jacob,
You would use a QueryTask and a Query using returnDistinctValues for populating the first dropdown and then once a selection was made in the first dropdown the value of the first dropdown would be used in the where clause of the second Query to populate the second dropdown.
Hi Robert,
Thank you for your suggestion. My next question is; once the first value is chosen and you set the where clause for the second query, do you repeat the same steps for the second dropdown that you did for the first?
Below is my code:
require([
"esri/Map",
"esri/views/MapView",
"esri/layers/FeatureLayer",
"dojo/on",
"dojo/dom",
"dojo/dom-construct",
"dojo/domReady!"
],
function (
Map, MapView,
FeatureLayer,
on, dom, domConstruct
) {
var statesURL = "https://sampleserver6.arcgisonline.com/arcgis/rest/services/Census/MapServer/3"
var subRegionSelect = dom.byId("subRegion");
var stateNameSelect = dom.byId("stateName");
var statesLayer = new FeatureLayer({
url: statesURL,
outFields: ["SUB_REGION", "STATE_NAME"],
visible: false
});
var map = new Map({
basemap: "gray",
layers: [statesLayer]
});
var view = new MapView({
container: "viewDiv", // Reference to the scene div created in step 5
map: map, // Reference to the map object created before the scene
zoom: 4, // Sets the zoom level based on level of detail (LOD)
center: [-96.01956, 39.967514] // Sets the center point of view in lon/lat
});
view.then(function () {
return statesLayer.then(function () {
var subRegionQuery = statesLayer.createQuery();
return statesLayer.queryFeatures(subRegionQuery);
});
})
.then(getValues)
.then(getUniqueValues)
.then(addToSelect);
// return an array of all the values in the
// SUB_REGION field of the states layer
function getValues(response) {
var features = response.features;
var values = features.map(function (feature) {
return feature.attributes.SUB_REGION;
});
return values;
}
// return an array of unique values in
// the SUB_REGION field of the states layer
function getUniqueValues(values) {
var uniqueValues = [];
values.forEach(function (item, i) {
if ((uniqueValues.length < 1 || uniqueValues.indexOf(item) === -1) && (item !== "")) {
uniqueValues.push(item);
}
});
console.log("'" + uniqueValues + "'");
return uniqueValues;
}
// Add the unique values to the subregion
// select element. This will allow the user
// to filter states by subregion.
function addToSelect(values) {
values.sort();
values.forEach(function (value) {
var option = domConstruct.create("option");
option.text = value;
subRegionSelect.add(option);
});
return setSubRegionDefinitionExpression(subRegionSelect.value);
}
function setSubRegionDefinitionExpression(newValue) {
statesLayer.definitionExpression = "SUB_REGION = '" + newValue + "'";
if (!statesLayer.visible) {
statesLayer.visible = true;
}
console.log(newValue)
}
on(subRegionSelect, "change", function (evt) {
var type = evt.target.value;
setSubRegionDefinitionExpression(type)
//setStateNamenDefinitionExpression(type)
})
});
Jacob,
Yes in your setSubRegionDefinitionExpression function you would create a query from the newValue and query the states layer based on that and add the results to the second drop down like you did the first.
BTW you can use returnDistintValues instead of they way you are manually processing unique values.
Thanks for your help, but I am now sure exactly where to insert the return distinct values code. Do you have a sample that I could use as a learning guide?
Jacob,
Something like this (still needs polishing):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<title>Get started with MapView - Create a 2D map - 4.3</title>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
.dds {
position: absolute;
right: 10px;
top: 10px;
}
</style>
<link rel="stylesheet" href="https://js.arcgis.com/4.3/esri/css/main.css">
<script src="https://js.arcgis.com/4.3/"></script>
<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/layers/FeatureLayer",
"esri/tasks/support/Query",
"dojo/on",
"dojo/dom",
"dojo/dom-construct",
"dojo/domReady!"
],
function(
Map, MapView,
FeatureLayer,
Query,
on, dom, domConstruct
) {
var statesURL = "http://sampleserver6.arcgisonline.com/arcgis/rest/services/Census/MapServer/3"
var subRegionSelect = dom.byId("subRegion");
var stateNameSelect = dom.byId("stateName");
var statesLayer = new FeatureLayer({
url: statesURL,
outFields: ["SUB_REGION", "STATE_NAME"],
visible: false
});
var map = new Map({
basemap: "gray",
layers: [statesLayer]
});
var view = new MapView({
container: "viewDiv", // Reference to the scene div created in step 5
map: map, // Reference to the map object created before the scene
zoom: 4, // Sets the zoom level based on level of detail (LOD)
center: [-96.01956, 39.967514] // Sets the center point of view in lon/lat
});
view.then(function() {
return statesLayer.then(function() {
var subRegionQuery = new Query();
subRegionQuery.where = "1=1";
subRegionQuery.outFields = ["SUB_REGION"];
subRegionQuery.returnDistinctValues = true;
subRegionQuery.orderByFields = ["SUB_REGION"];
return statesLayer.queryFeatures(subRegionQuery);
});
}).then(addToSelect)
.otherwise(queryError);
function queryError(error) {
console.error(error);
}
// Add the unique values to the subregion
// select element. This will allow the user
// to filter states by subregion.
function addToSelect(values) {
var option = domConstruct.create("option");
option.text = "";
subRegionSelect.add(option);
values.features.forEach(function(value) {
var option = domConstruct.create("option");
option.text = value.attributes.SUB_REGION;
subRegionSelect.add(option);
});
}
// Add the unique values to the state
// select element. This will allow the user
// to filter states by state and region.
function addToSelect2(values) {
var option = domConstruct.create("option");
option.text = "";
stateNameSelect.add(option);
values.features.forEach(function(value) {
var option = domConstruct.create("option");
option.text = value.attributes.STATE_NAME;
stateNameSelect.add(option);
});
}
function setDefinitionExpression() {
var strregion = subRegionSelect.options[subRegionSelect.selectedIndex].value;
var strstate = stateNameSelect.options[stateNameSelect.selectedIndex].value;
if (strregion != "" && strstate !== "") {
statesLayer.definitionExpression = "SUB_REGION = '" + strregion + "' AND STATE_NAME = '" + strstate + "'";
} else if (strregion != "") {
statesLayer.definitionExpression = "SUB_REGION = '" + strregion + "'";
} else if (strstate !== "") {
statesLayer.definitionExpression = "STATE_NAME = '" + strstate + "'";
}
if (!statesLayer.visible) {
statesLayer.visible = true;
}
}
on(subRegionSelect, "change", function(evt) {
var type = evt.target.value;
var i;
for (i = stateNameSelect.options.length - 1; i >= 0; i--) {
stateNameSelect.remove(i);
}
var subRegionQuery = new Query();
subRegionQuery.where = "SUB_REGION = '" + type + "'";
subRegionQuery.outFields = ["STATE_NAME"];
subRegionQuery.returnDistinctValues = true;
subRegionQuery.orderByFields = ["STATE_NAME"];
return statesLayer.queryFeatures(subRegionQuery).then(addToSelect2);
setDefinitionExpression();
})
on(stateNameSelect, "change", function(evt) {
var type = evt.target.value;
setDefinitionExpression();
})
});
</script>
</head>
<body>
<div id="viewDiv">
</div>
<div class="dds">
<select id="subRegion"></select>
<select id="stateName"></select>
</div>
</body>
</html>
Robert,
This work sample code is exactly what I needed. Thank you for your input and patience!
Don't forget to mark this question as answered by clicking on the "Correct Answer" link on the reply that answered your question.
Could you possibly tweek this for using in the 3.24 version? Is it possible? I am having trouble converting it over. the MapView function is not available in 3, and I am having trouble with the "view.then(function() {" part.
Robert
Thanks for the solution! I have a problem though when I add it to my Calcite Maps template. I get TypeError: Cannot read property 'create' of undefined on the first domConstruct.create for the addtoSelect. Then TypeError for the dojo on() watching for "changes". I cannot seem to nail down where dojo is failing. Is calcite using an older version of dojo? I think I have all the modules loaded correctly.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta name="description" content="ArcGIS JS v4, Calcite Maps and Bootstrap Example">
<title>Utah Plant Portal</title>
<!-- Calcite Maps Bootstrap -->
<link rel="stylesheet" href="https://esri.github.io/calcite-maps/dist/css/calcite-maps-bootstrap.min-v0.9.css">
<!--link rel="stylesheet" href="http://localhost/GitHub/calcite-maps/dist/css/calcite-maps-bootstrap.min-v0.9.css"-->
<!-- Calcite Maps -->
<link rel="stylesheet" href="https://esri.github.io/calcite-maps/dist/css/calcite-maps-arcgis-4.x.min-v0.9.css">
<!--link rel="stylesheet" href="http://localhost/GitHub/calcite-maps/dist/css/calcite-maps-arcgis-4.x.min-v0.9.css"-->
<!-- ArcGIS JS 4 -->
<link rel="stylesheet" href="https://js.arcgis.com/4.9/esri/css/main.css">
<style>
html,
body {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
}
.dds {
top: 295px;
left: 15px;
position: absolute;
display: inline-block;
}
</style>
</head>
<body class="calcite-maps calcite-nav-top">
<!-- Navbar -->
<nav class="navbar calcite-navbar navbar-fixed-top calcite-text-light calcite-bg-dark">
<!-- Menu -->
<div class="dropdown calcite-dropdown calcite-text-dark calcite-bg-light" role="presentation">
<a class="dropdown-toggle" role="menubutton" aria-haspopup="true" aria-expanded="false" tabindex="0">
<div class="calcite-dropdown-toggle">
<span class="sr-only">Toggle dropdown menu</span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>
</a>
<ul class="dropdown-menu" role="menu">
<li><a role="menuitem" tabindex="0" data-target="#panelInfo" aria-haspopup="true"><span class="glyphicon glyphicon-info-sign"></span> About</a></li>
<li><a role="menuitem" tabindex="0" href="#" data-target="#panelBasemaps" aria-haspopup="true"><span class="esri-icon-basemap"></span> Basemaps</a></li>
<li><a role="menuitem" tabindex="0" href="#" data-target="#panelLegend" aria-haspopup="true"><span class="glyphicon glyphicon-list-alt"></span> Legend</a></li>
<li><a role="menuitem" tabindex="0" href="#" id="calciteToggleNavbar" aria-haspopup="true"><span class="glyphicon glyphicon-fullscreen"></span> Full Map</a></li>
</ul>
</div>
<!-- Title -->
<div class="calcite-title calcite-overflow-hidden">
<span class="calcite-title-main">Utah Geological Survey</span>
<span class="calcite-title-divider hidden-xs"></span>
<span class="calcite-title-sub hidden-xs">Plant Portal</span>
</div>
<!-- Nav -->
<ul class="nav navbar-nav calcite-nav">
<li>
<div class="calcite-navbar-search calcite-search-expander">
<div id="searchWidgetDiv"></div>
</div>
</li>
</ul>
</nav>
<!--/.calcite-navbar -->
<!-- Map -->
<div class="calcite-map calcite-map-absolute">
<div id="mapViewDiv"></div>
</div>
<!-- /.calcite-map -->
<div class="dds">
<select id="waterShed"></select>
</div>
<!-- Panels -->
<div class="calcite-panels calcite-panels-right calcite-text-light calcite-bg-dark panel-group">
<!-- Panel - Info -->
<div id="panelInfo" class="panel collapse in">
<div id="headingInfo" class="panel-heading" role="tab">
<div class="panel-title">
<a class="panel-toggle" role="button" data-toggle="collapse" href="#collapseInfo" aria-expanded="true" aria-controls="collapseInfo"><span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span><span class="panel-label">About</span></a>
<a class="panel-close" role="button" data-toggle="collapse" tabindex="0" href="#panelInfo"><span class="esri-icon esri-icon-close" aria-hidden="true"></span></a>
</div>
</div>
<div id="collapseInfo" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="headingInfo">
<div class="panel-body">
<p>This is my map app!</p>
</div>
</div>
</div>
<!-- Basemaps Panel -->
<div id="panelBasemaps" class="panel collapse">
<div id="headingBasemaps" class="panel-heading" role="tab">
<div class="panel-title">
<a class="panel-toggle collapsed" role="button" data-toggle="collapse" href="#collapseBasemaps" aria-expanded="false" aria-controls="collapseBasemaps"><span class="glyphicon glyphicon-th-large" aria-hidden="true"></span><span class="panel-label">Basemaps</span></a>
<a class="panel-close" role="button" data-toggle="collapse" data-target="#panelBasemaps"><span class="esri-icon esri-icon-close" aria-hidden="true"></span></a>
</div>
</div>
<div id="collapseBasemaps" class="panel-collapse collapse" role="tabpanel" aria-labelledby="headingBasemaps">
<div class="panel-body">
<select id="selectBasemapPanel" class="form-control">
<option value="streets" data-vector="streets-vector">Streets</option>
<option value="satellite" data-vector="satellite" selected="">Satellite</option>
<option value="hybrid" data-vector="hybrid">Hybrid</option>
<option value="national-geographic" data-vector="national-geographic">National Geographic</option>
<option value="topo" data-vector="topo-vector">Topographic</option>
<option value="gray" data-vector="gray-vector">Gray</option>
<option value="dark-gray" data-vector="dark-gray-vector">Dark Gray</option>
<option value="osm" data-vector="osm">Open Street Map</option>
<option value="dark-gray" data-vector="streets-night-vector">Streets Night</option>
<option value="streets" data-vector="streets-navigation-vector">Streets Mobile</option>
</select>
</div>
</div>
</div>
<!-- Panel - Legend -->
<div id="panelLegend" class="panel collapse">
<div id="headingLegend" class="panel-heading" role="tab">
<div class="panel-title">
<a class="panel-toggle" role="button" data-toggle="collapse" href="#collapseLegend" aria-expanded="false" aria-controls="collapseLegend"><span class="glyphicon glyphicon-list-alt" aria-hidden="true"></span><span class="panel-label">Legend</span></a>
<a class="panel-close" role="button" data-toggle="collapse" tabindex="0" href="#panelLegend"><span class="esri-icon esri-icon-close" aria-hidden="true"></span></a>
</div>
</div>
<div id="collapseLegend" class="panel-collapse collapse" role="tabpanel" aria-labelledby="headingLegend">
<div class="panel-body">
<div id="legendDiv"></div>
</div>
</div>
</div>
</div>
<!-- /.calcite-panels -->
<script type="text/javascript">
var dojoConfig = {
packages: [{
name: "bootstrap",
location: "https://esri.github.io/calcite-maps/dist/vendor/dojo-bootstrap"
},
{
name: "calcite-maps",
location: "https://esri.github.io/calcite-maps/dist/js/dojo"
}]
};
</script>
<!-- ArcGIS JS 4 -->
<script src="https://js.arcgis.com/4.9/"></script>
<script>
require([
// ArcGIS
"esri/Map",
"esri/views/SceneView",
//Layers
"esri/layers/FeatureLayer",
//Tasks
"esri/tasks/support/Query",
// Widgets
"esri/widgets/Home",
"esri/widgets/Zoom",
"esri/widgets/Compass",
"esri/widgets/Search",
"esri/widgets/Legend",
"esri/widgets/BasemapToggle",
// Bootstrap
"bootstrap/Collapse",
"bootstrap/Dropdown",
// Calcite Maps
"calcite-maps/calcitemaps-v0.9",
// Calcite Maps ArcGIS Support
"calcite-maps/calcitemaps-arcgis-support-v0.9",
"dojo/on",
"dojo/dom",
"dojo/dom-construct",
"dojo/domReady!"
], function(Map, SceneView, FeatureLayer, Query, Home, Zoom, Compass, Search, Legend, BasemapToggle, ScaleBar, Attribution,Collapse, Dropdown, CalciteMaps, CalciteMapArcGISSupport, on, dom, domConstruct) {
/******************************************************************
*
* Create the map, view and widgets
*
******************************************************************/
var pointSymbol = {
type: "simple-marker", // autocasts as new SimpleMarkerSymbol()
color: "cyan",
size: "8px",
outline: { // autocasts as new SimpleLineSymbol()
color: "black",
width: 0.3
}
};
var renderSite = {
type: "simple", // autocasts as new SimpleRenderer()
symbol: pointSymbol,
};
var plantSites = new FeatureLayer({
url: "https://services.arcgis.com/ZzrwjTRez6FJiOq4/ArcGIS/rest/services/plantPortalTestV2_View/FeatureServ...",
title: "Plant Sites",
visibile: true,
outFields: ["huc8", "wetlandType"],
elevationInfo: [{
mode: "on-the-ground"
}],
renderer: renderSite
});
// Map
var map = new Map({
basemap: "hybrid",
layers: [plantSites],
ground: "world-elevation",
});
// View
var mapView = new SceneView({
container: "mapViewDiv",
map: map,
center: [-112, 40.7],
zoom: 11,
padding: {
top: 50,
bottom: 0
},
ui: {components: []}
});
// Popup and panel sync
mapView.when(function(){
CalciteMapArcGISSupport.setPopupPanelSync(mapView);
});
// Search - add to navbar
var searchWidget = new Search({
container: "searchWidgetDiv",
view: mapView
});
//CalciteMapArcGISSupport.setSearchExpandEvents(searchWidget);
// Map widgets
var home = new Home({
view: mapView
});
mapView.ui.add(home, "top-left");
var zoom = new Zoom({
view: mapView
});
mapView.ui.add(zoom, "top-left");
var compass = new Compass({
view: mapView
});
mapView.ui.add(compass, "top-left");
var basemapToggle = new BasemapToggle({
view: mapView,
secondBasemap: "satellite"
});
// Panel widgets - add legend
var legendWidget = new Legend({
container: "legendDiv",
view: mapView
});
var waterShedSelect = document.getElementById("waterShed");
mapView.when(function() {
console.log("querying");
return plantSites.when(function() {
var watershedQuery = new Query();
watershedQuery.where = "1=1";
watershedQuery.outFields = ["huc8"];
watershedQuery.returnDistinctValues = true;
watershedQuery.orderByFields = ["huc8"];
return plantSites.queryFeatures(watershedQuery);
});
}).then(addToSelect)
.otherwise(queryError);
function queryError(error) {
console.error(error);
}
// Add the unique values to the subregion
// select element. This will allow the user
// to filter states by subregion.
function addToSelect(values) {
console.log("adding to select");
var option = domConstruct.create("option");
option.text = "";
waterShedSelect.add(option);
values.features.forEach(function(value) {
var option = domConstruct.create("option");
option.text = value.attributes.huc8;
waterShedSelect.add(option);
});
}
function setDefinitionExpression() {
var strregion = waterShedSelect.options[waterShedSelect.selectedIndex].value;
if (strregion) {
plantSites.definitionExpression = "huc8 = '" + strregion + "'";
} else if (strregion != "") {
plantSites.definitionExpression = "huc8 = '" + strregion + "'";
}
if (!plantSites.visible) {
plantSites.visible = true;
}
}
on(waterShedSelect, "change", function(evt) {
var type = evt.target.value;
setDefinitionExpression();
})
});
</script>
</body>
</html>