How do I select multiple rows from my dGrid?

5656
9
09-02-2014 10:33 AM
TracySchloss
Frequent Contributor

I'm having a hard time managing my selected rows in my dGrid.  I read that the default selection mode is already set to extended. I have an event on dgrid-select that fire my chart function and that works.  I'd to be able to select mutiple rows from the grid to generate a chart with the information from multiple rows, but I'm having a hard time managing the selection.  As soon as I select one row, my chart gets generated.

I am creating my grid based on a featureLayer,queryFeatures.  Here's the result handler.

function gridHandler(results) {

  var rateTest, countTest;

  var returnObj = {};

  domConstruct.empty(dom.byId('gridDiv'));

    var gridColumns = buildColumns();

    var data = arrayUtils.map(results.featureSet.features, function(feature){    

      return feature.attributes;

   });

  grid = new (declare([Grid, Selection]))({

        id:'grid',

        columns: gridColumns

        }, "gridDiv");

       grid.renderArray(data);

       grid.sort('County'); 

     grid.on("dgrid-select", function(event){

        var rows = event.rows;

        var row = event.rows[0];

        var gridQuery = new Query();                                         

        gridQuery.objectIds = [row.data.OBJECTID];

        featureLayer.selectFeatures(gridQuery, FeatureLayer.SELECTION_NEW, function(results) {

          if ( results.length > 0 ) {

           // createLineChart(results);

           createLineChart(results[0]);

            var feature = results[0];

            feature.setInfoTemplate(infoTemplate);

            var resultGeometry = results[0].geometry;

            map.infoWindow.setFeatures(results);

      //    map.infoWindow.show(resultGeometry.getExtent().getCenter());      //assume geometry is a polygon     

          } else {

            console.log("error in grid.on click function");

          }

        });

        }); 

    dom.byId("gridText").innerHTML = title+ " - " + currentYear;

}

//creates a column definition for the grid based on the attributes returned from the query

  function buildColumns() {

       var columns = [];

//qryOutFields is an array of fieldNames defined earlier

    arrayUtils.forEach(qryOutFields, function(field){

            var objects = {};

            objects.label = field;

            objects.field = field;

            if (field === 'OBJECTID') {//assumed name for internal ID from map service

                objects.hidden = true;

            }

            columns.push(objects);

        });

       

    return columns;

}

0 Kudos
9 Replies
KenBuja
MVP Esteemed Contributor

It seems to me that if the dgrid-select event will be emitted anytime you click on a row (whether it's one or multiple rows), you'd have to change the logic in your script. Have you considered something like a "Generate a chart" button that would run your function only after one or more rows were selected?

0 Kudos
TracySchloss
Frequent Contributor

I actually used to have a 'generate a chart' button.  The way my project is laid out, the map is in the center and the left pane has the grid with the div for the chart below it. Whether the user selects a county from the dGrid or from the map, it generates a chart.  That comes across so seamlessly, the 'make a chart' button felt like I was making them do an extra step, so I took it out. 

That gives me an idea, though.  Maybe I could have a check box or something for Chart multiple counties'.  I was planning on limiting the number of counties they could select and I have to fit that somewhere as a dialog anyway.   I could start out with selection set to single and switch it if the box was checked.  That could also control a chart button, whether or not it was available depending on the mode. 

I tried adding a selector, because I thought having checkboxes next to the counties made it more clear which they were selecting.  But a selector column wasn't displaying just be adding it to the grid declaration.  I wondered if maybe it was being particular about the idProperty.  I didn't explicitly set it anywhere.

0 Kudos
KenBuja
MVP Esteemed Contributor

This adds a checkbox to a dGrid using the dGrid sample in the JS API samples.

<!DOCTYPE html>

<html>

<head>

  <meta charset="utf-8">

  <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">

  <title>Map with a Dojo dGrid</title>

  <link rel="stylesheet" href="http://js.arcgis.com/3.10/js/dojo/dojo/resources/dojo.css">

  <link rel="stylesheet" href="http://js.arcgis.com/3.10/js/dgrid/css/dgrid.css">

  <link rel="stylesheet" href="http://js.arcgis.com/3.10/js/dgrid/css/skins/tundra.css">

  <link rel="stylesheet" href="http://js.arcgis.com/3.10/js/dojo/dijit/themes/tundra/tundra.css">

  <link rel="stylesheet" href="http://js.arcgis.com/3.10/js/esri/css/esri.css">

  <style>

    html, body {

      height: 100%;

      width: 100%;

      margin: 0;

      padding: 0;

      overflow: hidden;

    }

    #container {

      height: 100%;

      visibility: hidden;

    }

    #bottomPane { height: 200px; }

    #grid { height: 100%; }

    .dgrid { border: none; }

    .field-id { cursor: pointer; }

  </style>

  <script src="http://js.arcgis.com/3.10/"></script>

  <script>

    // load dgrid, esri and dojo modules

    // create the grid and the map

    // then parse the dijit layout dijits

    require([

      "dojo/ready",

      "dgrid/OnDemandGrid",

      "dgrid/Selection",

      "dgrid/selector",

      "dojo/store/Memory",

      "dojo/_base/array",

      "dojo/dom-style",

      "dijit/registry",

      "esri/map",

      "esri/layers/FeatureLayer",

      "esri/symbols/SimpleFillSymbol",

      "esri/tasks/QueryTask",

      "esri/tasks/query",

      "dojo/_base/declare",

      "dojo/number",

      "dojo/on",

      "dojo/parser",

      "dijit/layout/BorderContainer",

      "dijit/layout/ContentPane"

      ], function(

        ready,

        Grid,

        Selection,

        selector,

        Memory,

        array,

        domStyle,

        registry,

        Map,

        FeatureLayer,

        SimpleFillSymbol,

        QueryTask,

        Query,

        declare,

        dojoNum,

        on,

        parser

      ) {

        ready(function() {

          parser.parse();

          // create the dgrid

          window.grid = new (declare([Grid, Selection]))({

            // use Infinity so that all data is available in the grid

            bufferRows: Infinity,

            columns: {

              "selection": selector({ label: "Select"}),

              "id": "ID",

              "stateName": "State Name",

              "median": { "label": "Median Net Worth", "formatter": dojoNum.format },

              "over1m": { "label": "Households Net Worth > $1M", "formatter": dojoNum.format }

            }

          }, "grid");

          // add a click listener on the ID column

          grid.on(".field-id:click", selectState);

          window.map = new Map("map", {

            basemap: "gray",

            center: [-101.426, 42.972],

            zoom: 4

          });

          window.statesUrl = "http://server.arcgisonline.com/ArcGIS/rest/services/Demographics/USA_Median_Net_Worth/MapServer/4";

          window.outFields = ["OBJECTID", "NAME", "MEDNW_CY"];

         

          var fl = new FeatureLayer(window.statesUrl, {

            id: "states",

            mode: 1, // ONDEMAND, could also use FeatureLayer.MODE_ONDEMAND

            outFields: window.outFields

          });

          fl.on("load", function() {

            fl.maxScale = 0; // show the states layer at all scales

            fl.setSelectionSymbol(new SimpleFillSymbol().setOutline(null).setColor("#AEC7E3"));

          });

          fl.on("click", selectGrid);

          // change cursor to indicate features are click-able

          fl.on("mouse-over", function() {

            map.setMapCursor("pointer");

          });

          fl.on("mouse-out", function() {

            map.setMapCursor("default");

          });

          map.addLayer(fl);

          map.on("load", function( evt ){

            // show the border container now that the dijits

            // are rendered and the map has loaded

            domStyle.set(registry.byId("container").domNode, "visibility", "visible");

            populateGrid(Memory); // pass a reference to the MemoryStore constructor

          });

          function populateGrid(Memory) {

            var qt = new QueryTask(window.statesUrl);

            var query = new Query();

            query.where = "1=1";

            query.returnGeometry = false;

            query.outFields = window.outFields;

            qt.execute(query, function(results) {

              var data = array.map(results.features, function(feature) {

                return {

                  // property names used here match those used when creating the dgrid

                  "id": feature.attributes[window.outFields[0]],

                  "stateName": feature.attributes[window.outFields[1]],

                  "median": feature.attributes[window.outFields[2]],

                  "over1m": feature.attributes[window.outFields[3]]

                }

              });

              var memStore = new Memory({ data: data });

              window.grid.set("store", memStore);

            });

          }

          // fires when a row in the dgrid is clicked

          function selectState(e) {

            // select the feature

            var fl = map.getLayer("states");

            var query = new Query();

            query.objectIds = [parseInt(e.target.innerHTML)];

            fl.selectFeatures(query, FeatureLayer.SELECTION_NEW, function(result) {

              if ( result.length ) {

                // re-center the map to the selected feature

                window.map.centerAt(result[0].geometry.getExtent().getCenter());

              } else {

                console.log("Feature Layer query returned no features... ", result);

              }

            });

          }

          // fires when a feature on the map is clicked

          function selectGrid(e) {

            var id = e.graphic.attributes.OBJECTID;

            // select the feature that was clicked

            var query = new Query();

            query.objectIds = [id];

            var states = map.getLayer("states");

            states.selectFeatures(query, FeatureLayer.SELECTION_NEW);

            // select the corresponding row in the grid

            // and make sure it is in view

            grid.clearSelection();

            grid.select(id);

            grid.row(id).element.scrollIntoView();

          }

        }

      );

    });

  </script>

</head>

<body class="tundra">

  <div id="container" data-dojo-type="dijit/layout/BorderContainer" data-dojo-props="design: 'headline', gutters: false">

    <div id="map" data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region: 'center'"></div>

    <div id="bottomPane" data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region: 'bottom'"> <div id="grid"></div>

    </div>

  </div>

</body>

</html>

TracySchloss
Frequent Contributor

The grid click event is still tied to the field-id.  When I tried to switch it to field-selection, it didn't work.  It seems like that's all I needed to change. 

0 Kudos
JohnGrayson
Esri Regular Contributor

Tracy, your code seems to only use the OBJECTID of the first selected item in the grid.  Try building a list of OBJECTIDs from all the selected items in the grid instead and then send those to 'selectFeatures', and also modify your charting code to use all of the selected features as well.  Actually, if you are just charting values based on the feature attributes, then you should already have those values directly from the grid selection, so you'd only need to call 'selectFeatures' if you want to visually see the features selected in the map.

0 Kudos
TracySchloss
Frequent Contributor

Ken, since I have a function already for building my columns (the fields will vary), I'd need to figure out how to add that selector into my buildColumns function.  Or is there somehow I can add it into my Grid declaration.  At the moment I just have a column that says Object Object.

//creates a column definition for the grid based on the attributes returned from the query

  function buildColumns() {

       var columns = [];

//qryOutFields is an array of fieldNames defined earlier

var sel = {

   "selection": selector({

     label: "Select"

   })

};

columns.push(sel);

    arrayUtils.forEach(qryOutFields, function(field){

            var objects = {};

            objects.label = field;

            objects.field = field;

            if (field === 'OBJECTID') {//assumed name for internal ID from map service

                objects.hidden = true;

            }

            columns.push(objects);

        });

       

    return columns;

}

0 Kudos
KenBuja
MVP Esteemed Contributor

According to a page that show using a selector, this is the syntax when setting the columns using an array

columns:  [ selector({ label: 'radio'}, "radio"),

            { label: "Username", field: "username"}]

This is when the dGrid has the store property set at instantiation.

I tried using the same code to build the columns from a feature (having the same issue as you with variable field names possible)

function buildColumns(feature) {

  var attributes = feature.attributes;

  var columns = [];

  for (var attribute in attributes) {

    if (attributes.hasOwnProperty(attribute)) {

      var objects = {};

      objects.label = attribute;

      objects.field = attribute;

      if (attribute === "Shape") {

        objects.hidden = true;

      }

      columns.push(objects);

    }

  }

  var sel = selector({ label: '', sortable: false });

  columns.splice(0, 0, sel);

  return columns;

}

However, when I try this using the renderArray method on a dGrid, I get the error

Unable to get property 'getIdentity' of undefined or null reference

0 Kudos
TracySchloss
Frequent Contributor

I have other code that has the store as part of the grid definition somewhere.  I don't remember anymore the difference between the way I have it and the one where I declare a memory store instead.  Probably something to do with the id property or something.  I'll have to go through other places I used grids.  I end up using them quite a bit.

In the meantime, I changed the default selectionMode to single and added dojo/keys to my code and have a listener for the gridDiv for whether the user has the Ctrl key held down or not.  If it is, the selectionMode is set to extended.  It seems to work OK, although I don't have much code changed to manage the multiple selections.  At least I didn't break what I already had.   It doesn't seem like I should have had to do this, but it wasn't much code, so I guess I'll go with it for now.

0 Kudos
TracySchloss
Frequent Contributor

I was able to get a selector added to my grid my changing the data to a store, so that's sorted out.  That was exactly the problem there, but I won't mark it correct, since that's only one aspect of my overall question.   Now I'm trying to figure out how to manage the individual list of attributes for each of the selected rows in the grid.  I'm getting lost in how to manage my array variable. 

I'd still like to have the user generate a single chart automatically with a row click, but for now I have a Make Chart button, which uses the grid.selection to get the objectIds of the rows and pass those to a featureLayer.queryFeatures.

I realized I could start with what is essentially an empty chart to start and add a series to the chart for each row.  All a series is is an array of values, there's nothing fancy about it.

function createMultiChart(){

  var rowid,gQuery;

  var idList = [];

  var countyList = [];

  maxList.length = 0;

  for (var rowid in grid.selection) {

    var row = grid.row(rowid);  

    idList.push(parseFloat(rowid));//a list of objectIds

    countyList.push(row.data.County);// a list of selected counties

  }

  console.log("county list = " + countyList);

      lineChart = new Chart2D("chartDiv");

      gQuery = new Query();                                     

      gQuery.objectIds = idList; 

        featureLayer.queryFeatures(gQuery, function(results) { 

         arrayUtils.forEach(results.features, function (result){

           createRateList(result);

           });

       });

   var maxRate = findMax(maxList);

       finishLineChart(maxRate);

}

function createRateList(results){

  var atts = results.attributes;

  var county = atts.County;

  var rateList = [];

  //prefer to see chart in ascending order, changing the order the yearly rate fields

        for (var i = rateFields.length - 1; i >= 0; i--) {

            reverseRateFields.push(rateFields);

        }

    //gets the rate values for selected county for all years

        arrayUtils.forEach(reverseRateFields, function (field){

           rateList.push(atts[field]);

        });  

     maxList.push(findMax(rateList));

       lineChart.addSeries(county, rateList, {

          stroke: { color: "red", width: 2 }

     });

     rateList.length = 0;

}

I can see that each time I execute the createRateList, that I am creating a list of rates for that county, which becomes a new series in the chart.  The problem is that the first series has just the rates I expect, the 2nd series has all those rates AGAIN, plus the additional ones from the 2nd county (etc etc).  Obviously I'm not using my rateList variable properly, but I can seem to get it sorted out.  I've tried setting the length to 0, but maybe this is in the wrong spot?

I also need to determine the maximum value of all the rates from the selected counties.  This will be used to determine the Y axis on the chart.  That will be used later in my finishLineChart function that will add the axes, title etc. 

0 Kudos