Filter Features By Two Fields

89
3
Jump to solution
06-30-2020 02:28 PM
Highlighted
MVP Regular Contributor

This question is a continuation of my last one: Query Features by Category and Keyword 

The app so far has two drop-downs. The user can query by categories and query by keywords.

<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<title>Recycling Map</title>

<link rel="stylesheet" href="https://js.arcgis.com/4.15/esri/themes/light/main.css" />
<script src="https://js.arcgis.com/4.15/"></script>

<style>
html,
body,
#viewDiv {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
}

#infoDiv {
background-color: white;
color: black;
padding: 6px;
width: 440px;
}

#titleDiv {
padding: 10px;
}

#titleText {
font-size: 20pt;
font-weight: 60;
padding-bottom: 10px;
}

#results {
font-weight: bolder;
padding-top: 10px;
}

#category,
#keyword {
margin-top: 8px;
margin-bottom: 8px;
}

</style>

<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/layers/FeatureLayer",
"esri/layers/GraphicsLayer",
"esri/geometry/geometryEngine",
"esri/Graphic"

], function (
Map,
MapView,
FeatureLayer,
GraphicsLayer,
geometryEngine,
Graphic
) {

var catTypeSelect = document.getElementById("category");
var keyTypeSelect = document.getElementById("keyword");
var filterButton = document.getElementById("SelectBtn");

// Recycling Locations View
var recycleLayer = new FeatureLayer({
portalItem: {
// autocasts as new PortalItem()
id: "227061be60a14cc89946a978b440d227"
},
outFields: ["*"],
visible: false
});



// GraphicsLayer for displaying results
var resultsLayer = new GraphicsLayer();

var map = new Map({
basemap: "dark-gray",
layers: [recycleLayer, resultsLayer]
});

var view = new MapView({
container: "viewDiv",
map: map,
center: [-87.95, 41.47],
zoom: 10
});
view.ui.add("infoDiv", "top-left");

// query all features from the Recycle layer
view
.when(function () {
return recycleLayer.when(function () {
var query = recycleLayer.createQuery();
return recycleLayer.queryFeatures(query);
});
})
.then(getValues)
.then(getUniqueValues)
.then(addToSelect)

// return an array of all the values in the
// USER_Categ and USER_Keyword fields of the Recycle layer
function getValues(response) {
var features = response.features;
var values = features.map(function (feature) {
return {
USER_Categ: feature.attributes.USER_Categ,
USER_Keywo: feature.attributes.USER_Keywo
}
});
return values;
}

// return an array of unique values in
// the keyword and category fields of the Recycle layer
function getUniqueValues(values) {
var uniqueKeyValues = [];
var uniqueCatValues = [];

values.forEach(function (item, i) {
var keyVal = item.USER_Keywo.split(";");
var catVal = item.USER_Categ.split(";");
catVal.map(function (val1) {
if (
(uniqueCatValues.length < 1 || uniqueCatValues.indexOf(val1) === -1) &&
val1 !== ""
) {
uniqueCatValues.push(val1);
}
});
keyVal.map(function (val2) {
if (
(uniqueKeyValues.length < 1 || uniqueKeyValues.indexOf(val2) === -1) &&
val2 !== ""
) {
uniqueKeyValues.push(val2);
}
});
});
return {
uKeyVals: uniqueKeyValues,
uCatVals: uniqueCatValues
};
}

// Add the unique values to the category type
// select element. This will allow the user
// to filter categorys by type.
function addToSelect(values) {
values.uCatVals.sort();
values.uCatVals.forEach(function (value) {
var option = document.createElement("option");
option.text = value;
catTypeSelect.add(option);
});

values.uKeyVals.sort();
values.uKeyVals.forEach(function (value) {
var option = document.createElement("option");
option.text = value;
keyTypeSelect.add(option);
});

return setDefinitionExpression();
}

// set the definition expression on the recycle
// layer to reflect the selection of the user
function setDefinitionExpression() {
var sqlExp = "";
if (catTypeSelect.selectedIndex > 0) {
sqlExp += "USER_Categ LIKE '%" + catTypeSelect.options[catTypeSelect.selectedIndex].value + "%'";
}
if (keyTypeSelect.selectedIndex > 0) {
if (sqlExp === "") {
sqlExp += "USER_Keywo LIKE '%" + keyTypeSelect.options[keyTypeSelect.selectedIndex].value + "%'";
} else {
sqlExp += " AND USER_Keywo LIKE '%" + keyTypeSelect.options[keyTypeSelect.selectedIndex].value + "%'";
}
}
recycleLayer.definitionExpression = sqlExp;

if (!recycleLayer.visible) {
recycleLayer.visible = true;
}

return queryForGeometries();
}

// Get all the geometries of the recycle layer
// the createQuery() method creates a query
// object that respects the definitionExpression
// of the layer
function queryForGeometries() {
var rQuery = recycleLayer.createQuery();

return recycleLayer.queryFeatures(rQuery).then(function (response) {
rGeometries = response.features.map(function (feature) {
return feature.geometry;
});

return rGeometries;
});
}

filterButton.addEventListener("click", function () {
setDefinitionExpression();
});

// set a new definitionExpression on the recycle layer
// catTypeSelect.addEventListener("change", function () {
// var type = event.target.value;
// setDefinitionExpression(type, "cat");
// });

// set a new definitionExpression on the recycle layer
// keyTypeSelect.addEventListener("change", function () {
// var type = event.target.value;
// setDefinitionExpression(type, "key");
// });
view.ui.add("titleDiv", "top-right");
});
</script>
</head>

<body>
<div id="viewDiv"></div>
<div id="infoDiv" class="esri-widget">
<div id="drop-downs">
Select Category (leave blank if searching by Keyword):
<select id="category" class="esri-widget"></select>
Select Keyword:
<select id="keyword" class="esri-widget"></select>
<button id="SelectBtn" class="esri-button esri-button--secondary">Search</button>
</div>
<div id="results" class="esri-widget"></div>
</div>
<div id="titleDiv" class="esri-widget">
<div id="titleText">Green Guide</div>
<div>Easy Ways To Be More Green</div>
</div>
</body>

</html>

Now I'd like to filter the categories and keywords based on two fields in the attribute table, USER_IsRes and USER_IsBus (residential and business). The values are either TRUE or FALSE-- Feature Layer View. I'm able to use a sample and loosely get that to function with the USER_IsRes field.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="initial-scale=1,maximum-scale=1,user-scalable=no"
/>
<title>Filter features by attribute - 4.15</title>

<link
rel="stylesheet"
href="https://js.arcgis.com/4.15/esri/themes/light/main.css"
/>
<script src="https://js.arcgis.com/4.15/"></script>

<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}

#res-filter {
height: 160px;
width: 100%;
visibility: hidden;
}

.res-item {
width: 100%;
padding: 12px;
text-align: center;
vertical-align: baseline;
cursor: pointer;
height: 40px;
}

.res-item:focus {
background-color: dimgrey;
}

.res-item:hover {
background-color: dimgrey;
}

#titleDiv {
padding: 10px;
}

#titleText {
font-size: 20pt;
font-weight: 60;
padding-bottom: 10px;
}
</style>
<script>
require([
"esri/views/MapView",
"esri/Map",
"esri/layers/FeatureLayer",
"esri/widgets/Expand"
], function (MapView, Map, FeatureLayer, Expand) {

let floodLayerView;

// flash flood warnings layer
const layer = new FeatureLayer({
portalItem: {
id: "227061be60a14cc89946a978b440d227"
},
outFields: ["USER_IsRes"]
});

const map = new Map({
basemap: "gray-vector",
layers: [layer]
});

const view = new MapView({
map: map,
container: "viewDiv",
center: [-98, 40],
zoom: 4
});

const resNodes = document.querySelectorAll(`.res-item`);
const resElement = document.getElementById("res-filter");

// click event handler for res choices
resElement.addEventListener("click", filterByres);

// User clicked on Winter, Spring, Summer or Fall
// set an attribute filter on flood warnings layer view
// to display the warnings issued in that res
function filterByres(event) {
const selectedres = event.target.getAttribute("data-res");
floodLayerView.filter = {
where: "USER_IsRes = '" + selectedres + "'",
};
}

view.whenLayerView(layer).then(function(layerView) {
// flash flood warnings layer loaded
// get a reference to the flood warnings layerview
floodLayerView = layerView;

// set up UI items
resElement.style.visibility = "visible";
const resExpand = new Expand({
view: view,
content: resElement,
expandIconClass: "esri-icon-filter",
group: "top-left"
});
//clear the filters when user closes the expand widget
resExpand.watch("expanded", function() {
if (!resExpand.expanded) {
floodLayerView.filter = null;
}
});
view.ui.add(resExpand, "top-left");
view.ui.add("titleDiv", "top-right");
});
});
</script>
</head>

<body>
<div id="res-filter" class="esri-widget">
<div class="res-item visible-res" data-res="TRUE">True</div>
<div class="res-item visible-res" data-res="False">False</div>
</div>
<div id="viewDiv"></div>
<div id="titleDiv" class="esri-widget">
<div id="titleText">Flash Floods by res</div>
<div>Flash Flood Warnings (2002 - 2012)</div>
</div>
</body>
</html>

But, how do I incorporate the filter (preferably client side since they say it's faster) into the app? Once the user has either a keyword or category selected I want there to be a way to filter with either Residential or Business.

In the end, the selection methods should more or less mirror this page: Will County EEC Mobile Site

Reply
0 Kudos
1 Solution

Accepted Solutions
Highlighted
MVP Esteemed Contributor

Jared,

   You just have to add those to the sqlExp.

<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<title>Recycling Map</title>

<link rel="stylesheet" href="https://js.arcgis.com/4.15/esri/themes/light/main.css" />
<script src="https://js.arcgis.com/4.15/"></script>

<style>
html,
body,
#viewDiv {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
}

#viewDiv {
position: absolute;
height: calc(100% - 67px);
top: 67px;
}

#filterDiv {
position: relative;
background-color: white;
color: black;
padding: 6px;
top: 0;
background: #d4e1c0;
border-bottom: 1px dotted #91dca1;
}

#category,
#keyword {
margin-top: 8px;
margin-bottom: 8px;
display: inline-block;
}

.drop-downs {
width: 100%;
}

.oneline {
display: inline-block;
margin-right: 6px;
}

#filterBtn {
position: fixed;
right: 6px;
top: 6px;
width: 50px;
font: bold 12px Trebuchet MS;
color: #fff;
background-color: #098941;
border: 1px solid #1c9c52;
text-decoration: none;
cursor: pointer;
padding: 2px 4px;
}

#filterBtn:hover {
background-color: #098dd1;
border: 1px solid #0ab0ed;
}
</style>

<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/layers/FeatureLayer",
"esri/layers/GraphicsLayer",
"esri/geometry/geometryEngine",
"esri/Graphic",

], function (
Map,
MapView,
FeatureLayer,
GraphicsLayer,
geometryEngine,
Graphic,
) {


var catTypeSelect = document.getElementById("category");
var keyTypeSelect = document.getElementById("keyword");
var filterButton = document.getElementById("filterBtn");
var resRb = document.getElementById("ResidentialRB");

// oil and gas wells
var recycleLayer = new FeatureLayer({
portalItem: {
// autocasts as new PortalItem()
id: "227061be60a14cc89946a978b440d227"
},
outFields: ["*"],
visible: false
});



// GraphicsLayer for displaying results
var resultsLayer = new GraphicsLayer();

var map = new Map({
basemap: "dark-gray",
layers: [recycleLayer, resultsLayer]
});

var view = new MapView({
container: "viewDiv",
map: map,
center: [-87.95, 41.47],
zoom: 10
});

// query all features from the wells layer
view
.when(function () {
return recycleLayer.when(function () {
var query = recycleLayer.createQuery();
return recycleLayer.queryFeatures(query);
});
})
.then(getValues)
.then(getUniqueValues)
.then(addToSelect)

// return an array of all the values in the
// STATUS2 field of the wells layer
function getValues(response) {
var features = response.features;
var values = features.map(function (feature) {
return {
USER_Categ: feature.attributes.USER_Categ,
USER_Keywo: feature.attributes.USER_Keywo
}
});
return values;
}

// return an array of unique values in
// the STATUS2 field of the wells layer
function getUniqueValues(values) {
var uniqueKeyValues = [];
var uniqueCatValues = [];

values.forEach(function (item, i) {
var keyVal = item.USER_Keywo.split(";");
var catVal = item.USER_Categ.split(";");
catVal.map(function (val1) {
if (
(uniqueCatValues.length < 1 || uniqueCatValues.indexOf(val1) === -1) &&
val1 !== ""
) {
uniqueCatValues.push(val1);
}
});
keyVal.map(function (val2) {
if (
(uniqueKeyValues.length < 1 || uniqueKeyValues.indexOf(val2) === -1) &&
val2 !== ""
) {
uniqueKeyValues.push(val2);
}
});
});
return {
uKeyVals: uniqueKeyValues,
uCatVals: uniqueCatValues
};
}

// Add the unique values to the wells type
// select element. This will allow the user
// to filter wells by type.
function addToSelect(values) {
values.uCatVals.sort();
values.uCatVals.forEach(function (value) {
var option = document.createElement("option");
option.text = value;
catTypeSelect.add(option);
});

values.uKeyVals.sort();
values.uKeyVals.forEach(function (value) {
var option = document.createElement("option");
option.text = value;
keyTypeSelect.add(option);
});

return setDefinitionExpression();
}

// set the definition expression on the recycle
// layer to reflect the selection of the user
function setDefinitionExpression() {
var sqlExp = "";
if (catTypeSelect.selectedIndex > 0) {
sqlExp += "USER_Categ LIKE '%" + catTypeSelect.options[catTypeSelect.selectedIndex].value + "%'";
}
if (keyTypeSelect.selectedIndex > 0) {
if (sqlExp === "") {
sqlExp += "USER_Keywo LIKE '%" + keyTypeSelect.options[keyTypeSelect.selectedIndex].value + "%'";
} else {
sqlExp += " AND USER_Keywo LIKE '%" + keyTypeSelect.options[keyTypeSelect.selectedIndex].value + "%'";
}
}
if (resRb.checked) {
if (sqlExp !== "") {
sqlExp += " AND USER_IsRes = 'TRUE'";
}

} else {
if (sqlExp !== "") {
sqlExp += " AND USER_IsBus = 'TRUE'";
}
}
console.info(sqlExp);
recycleLayer.definitionExpression = sqlExp;

if (!recycleLayer.visible) {
recycleLayer.visible = true;
}

return queryForGeometries();
}

// Get all the geometries of the recycle layer
// the createQuery() method creates a query
// object that respects the definitionExpression
// of the layer
function queryForGeometries() {
var rQuery = recycleLayer.createQuery();

return recycleLayer.queryFeatures(rQuery).then(function (response) {
rGeometries = response.features.map(function (feature) {
return feature.geometry;
});

return rGeometries;
});
}

filterButton.addEventListener("click", function () {
setDefinitionExpression();
});
});
</script>
</head>

<body>
<div id="viewDiv"></div>
<div id="filterDiv">
<div class="drop-downs">
<div class="oneline">Select Category:</div>
<select id="category" class="oneline"></select>
<div class="oneline">Select Keyword:</div>
<select id="keyword" class="oneline"></select>
<div>
<input id="ResidentialRB" name="resorbus" type="radio" value="Residential" checked="checked"><label
for="ResidentialRB">Residential</label>
<input id="BusinessRB" name="resorbus" type="radio" value="Business"><label for="BusinessRB">Business</label>
</div>
<button id="filterBtn">Filter</button>
</div>
</div>
</body>

</html>

View solution in original post

Reply
0 Kudos
3 Replies
Highlighted
MVP Esteemed Contributor

Jared,

   You just have to add those to the sqlExp.

<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<title>Recycling Map</title>

<link rel="stylesheet" href="https://js.arcgis.com/4.15/esri/themes/light/main.css" />
<script src="https://js.arcgis.com/4.15/"></script>

<style>
html,
body,
#viewDiv {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
}

#viewDiv {
position: absolute;
height: calc(100% - 67px);
top: 67px;
}

#filterDiv {
position: relative;
background-color: white;
color: black;
padding: 6px;
top: 0;
background: #d4e1c0;
border-bottom: 1px dotted #91dca1;
}

#category,
#keyword {
margin-top: 8px;
margin-bottom: 8px;
display: inline-block;
}

.drop-downs {
width: 100%;
}

.oneline {
display: inline-block;
margin-right: 6px;
}

#filterBtn {
position: fixed;
right: 6px;
top: 6px;
width: 50px;
font: bold 12px Trebuchet MS;
color: #fff;
background-color: #098941;
border: 1px solid #1c9c52;
text-decoration: none;
cursor: pointer;
padding: 2px 4px;
}

#filterBtn:hover {
background-color: #098dd1;
border: 1px solid #0ab0ed;
}
</style>

<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/layers/FeatureLayer",
"esri/layers/GraphicsLayer",
"esri/geometry/geometryEngine",
"esri/Graphic",

], function (
Map,
MapView,
FeatureLayer,
GraphicsLayer,
geometryEngine,
Graphic,
) {


var catTypeSelect = document.getElementById("category");
var keyTypeSelect = document.getElementById("keyword");
var filterButton = document.getElementById("filterBtn");
var resRb = document.getElementById("ResidentialRB");

// oil and gas wells
var recycleLayer = new FeatureLayer({
portalItem: {
// autocasts as new PortalItem()
id: "227061be60a14cc89946a978b440d227"
},
outFields: ["*"],
visible: false
});



// GraphicsLayer for displaying results
var resultsLayer = new GraphicsLayer();

var map = new Map({
basemap: "dark-gray",
layers: [recycleLayer, resultsLayer]
});

var view = new MapView({
container: "viewDiv",
map: map,
center: [-87.95, 41.47],
zoom: 10
});

// query all features from the wells layer
view
.when(function () {
return recycleLayer.when(function () {
var query = recycleLayer.createQuery();
return recycleLayer.queryFeatures(query);
});
})
.then(getValues)
.then(getUniqueValues)
.then(addToSelect)

// return an array of all the values in the
// STATUS2 field of the wells layer
function getValues(response) {
var features = response.features;
var values = features.map(function (feature) {
return {
USER_Categ: feature.attributes.USER_Categ,
USER_Keywo: feature.attributes.USER_Keywo
}
});
return values;
}

// return an array of unique values in
// the STATUS2 field of the wells layer
function getUniqueValues(values) {
var uniqueKeyValues = [];
var uniqueCatValues = [];

values.forEach(function (item, i) {
var keyVal = item.USER_Keywo.split(";");
var catVal = item.USER_Categ.split(";");
catVal.map(function (val1) {
if (
(uniqueCatValues.length < 1 || uniqueCatValues.indexOf(val1) === -1) &&
val1 !== ""
) {
uniqueCatValues.push(val1);
}
});
keyVal.map(function (val2) {
if (
(uniqueKeyValues.length < 1 || uniqueKeyValues.indexOf(val2) === -1) &&
val2 !== ""
) {
uniqueKeyValues.push(val2);
}
});
});
return {
uKeyVals: uniqueKeyValues,
uCatVals: uniqueCatValues
};
}

// Add the unique values to the wells type
// select element. This will allow the user
// to filter wells by type.
function addToSelect(values) {
values.uCatVals.sort();
values.uCatVals.forEach(function (value) {
var option = document.createElement("option");
option.text = value;
catTypeSelect.add(option);
});

values.uKeyVals.sort();
values.uKeyVals.forEach(function (value) {
var option = document.createElement("option");
option.text = value;
keyTypeSelect.add(option);
});

return setDefinitionExpression();
}

// set the definition expression on the recycle
// layer to reflect the selection of the user
function setDefinitionExpression() {
var sqlExp = "";
if (catTypeSelect.selectedIndex > 0) {
sqlExp += "USER_Categ LIKE '%" + catTypeSelect.options[catTypeSelect.selectedIndex].value + "%'";
}
if (keyTypeSelect.selectedIndex > 0) {
if (sqlExp === "") {
sqlExp += "USER_Keywo LIKE '%" + keyTypeSelect.options[keyTypeSelect.selectedIndex].value + "%'";
} else {
sqlExp += " AND USER_Keywo LIKE '%" + keyTypeSelect.options[keyTypeSelect.selectedIndex].value + "%'";
}
}
if (resRb.checked) {
if (sqlExp !== "") {
sqlExp += " AND USER_IsRes = 'TRUE'";
}

} else {
if (sqlExp !== "") {
sqlExp += " AND USER_IsBus = 'TRUE'";
}
}
console.info(sqlExp);
recycleLayer.definitionExpression = sqlExp;

if (!recycleLayer.visible) {
recycleLayer.visible = true;
}

return queryForGeometries();
}

// Get all the geometries of the recycle layer
// the createQuery() method creates a query
// object that respects the definitionExpression
// of the layer
function queryForGeometries() {
var rQuery = recycleLayer.createQuery();

return recycleLayer.queryFeatures(rQuery).then(function (response) {
rGeometries = response.features.map(function (feature) {
return feature.geometry;
});

return rGeometries;
});
}

filterButton.addEventListener("click", function () {
setDefinitionExpression();
});
});
</script>
</head>

<body>
<div id="viewDiv"></div>
<div id="filterDiv">
<div class="drop-downs">
<div class="oneline">Select Category:</div>
<select id="category" class="oneline"></select>
<div class="oneline">Select Keyword:</div>
<select id="keyword" class="oneline"></select>
<div>
<input id="ResidentialRB" name="resorbus" type="radio" value="Residential" checked="checked"><label
for="ResidentialRB">Residential</label>
<input id="BusinessRB" name="resorbus" type="radio" value="Business"><label for="BusinessRB">Business</label>
</div>
<button id="filterBtn">Filter</button>
</div>
</div>
</body>

</html>

View solution in original post

Reply
0 Kudos
Highlighted
MVP Regular Contributor

Robert,

You make it seem so easy! Thanks for the constant help.

Reply
0 Kudos
Highlighted
New Contributor III

Hi Jared,

What you can do is add a checkbox in your HTML:

<label id="isResLabel">
<input id="isResInput" type="checkbox" checked="yes" />
<span class="isResToggle"></span>
</label>

and then add a listener function which changes the definition expression on the layer:

// click event handler for res choice
view.when(function() {
document
.getElementById('isResInput')
.addEventListener('change', updateIsRes);
});

function updateIsRes(event) {
if (this.checked) {
recycleLayer.definitionExpression = "USER_IsRes = 'TRUE'"
} else {
recycleLayer.definitionExpression = "USER_IsRes = 'FALSE'"
}
};

I'd suggest you have one checkbox for residential and one for business and the user can toggle to their hearts delight:

Hope this helps. 

Cheers, 

Gianna