AnsweredAssumed Answered

Filter Features By Two Fields

Question asked by jpilbeam Champion on Jun 30, 2020
Latest reply on Jul 1, 2020 by Gianna.Gandossi_BBSRC

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

Outcomes