bpelchat

DOJO DGrid, Data grid In a Wab Widget

Blog Post created by bpelchat on Dec 23, 2017

This blog shares an implementation of the dojo dgrid

Here are a couple of reference links about this data grid:

dgrid, 

dgrid - A Dojo grid created by SitePen 

 

I've used this data grid in a couple of custom wab widgets this year and will share in this blog one of those implementations.

In this blog I'll explain the following:

  • how to add the dgrid to your widget html
  • display the dgrid populated with data returned from a map or feature service
  • how to turn on editing in a grid cell and apply those edits to a feature service.

The widget I'll be sharing is one we created for use in fire towers and fire dispatch centers.

In our state we have manned fire towers and we support maps for the osborne fire finder,  Osborne Fire Finder - Wikipedia  and its tower crews. This year we decided to provide a web application to enhance this capability.

Here is a screen shot of that application...

In short this application allows the ranger to enter the osborne readings into the dgrid from their tower, and instantly communicate that information via this web application. We have two versions running, a full editor version and a read only version for those who are not manning a tower but want to view the osborne readings from all contributing towers.

 

I'll now focus on how the dgrid is implemented in a wab widget, providing code snippets and also the full widget code for the view only version of this widget, (it will be minus the fires tab you see in the snippet above).

 

In the screen snippet you'll see the data grid , and that this data grid has only two columns one titled "Tower" and another titled "Bearing". This is a simple grid, its populated by a query to a service that returns all the tower records with two fields , the tower name and the towers current osborne finder bearings, (comma separated if more than one bearing).

The tower and bearing information is used to draw bearing lines and determine intersecting points, the potential fire points.

First I'll start by describing the widget.html shown below.

In the html you can see a div element with id "towersdgrid" and class "dgrid-div", along with the css style file that's all we need for this dgrid element.

  <div>
  <div data-dojo-attach-point="tabSearch">
    <div class="search-tab-node" data-dojo-attach-point="tabNode2">
      <div style="line-height:12px;">
        <span>Filter On Tower:</span>
        <br>
        <select style="height:20px;width:98%; margin-bottom:5px; margin-top:5px" data-dojo-attach-point="nhTowers" data-dojo-type="NHFireTowersComboBox"></select>
      </div>
      <div id="towersdgrid" class="dgrid-div" ></div>
      <div class="col-4-4" >
        <div class="jimu-btn" data-dojo-attach-point="btnGridUpdate" data-dojo-attach-event="onclick:onGridButtonClick">${nls.gridRefreshButtonLabel}</div>
        <div style="padding-left:10px;" data-dojo-type="jimu/dijit/CheckBox" data-dojo-attach-point="towerLabelsCbx"
                 data-dojo-props="label:'Show Labels'"/>

      </div>        
      <div class="firebearingradius" data-dojo-attach-point="radiusSlider" data-dojo-type="dijit/form/HorizontalSlider" showbuttons="true" value="68930" minimum="10000" maximum="300000" discretevalues="1000" intermediatechanges="false" data-dojo-attach-event="change: _onRadiusChanged">
     </div> 
  </div>  
  </div>

Next in the widget.js we need to import the dgrid (which by the way is provided in the esri library, and which you can read some more about in the link I gave at the top of the blog).

Here are the import statements you'll need for the dgrid in the widget.js.

    'dgrid/OnDemandGrid',
    'dgrid/Selection',
    'dgrid/Keyboard',
    'dgrid/extensions/ColumnHider',
    'dgrid/editor',
    'dgrid/extensions/ColumnResizer',

Along with dgrid imports, it will also require one for the memory store used by the dgrid...

'dojo/store/Memory'

In this widget.js there are two primary functions related to the dgrid, the first being the one to query a map service to fetch data used to populate the dgrid, and the second  being the result handler.

      refreshGridMetrics: function (){
        this.filter="";
        this.towerMetricsFeatureLayer.setDefinitionExpression("1=1");
        var query = new Query();
        query.outFields = [ "*" ];
        this.towerMetricsFeatureLayer.queryFeatures(query, lang.hitch(this, this.onTowerMetricsQueryComplete));
      },

On query complete, in our query features result handler, we will instantiate the data grid if its the first time, other wise we will just refresh the memory store with the new feature data.

      onTowerMetricsQueryComplete: function (featureSet){
        var data = [{ NAME: 'TEST1', BEARING: '000' }, { NAME: 'TEST2', BEARING: '001' }];
        //console.log("onTowerMetricsQueryComplete-evt");
        if (!this.towerGrid) {
          this.towerGrid = new (declare([DGrid, DGridSelection, DGridColumnHider, editor, ColumnResizer]))({
            bufferRows: Infinity,
            query: lang.hitch(this, this.filterQuery),
            columns: {
              NAME: {
                label: 'Tower',
                hidden: false,
                resizable: true
              },
             
              BEARING: editor({
                label: "Bearing",
                field: "BEARING"
              }, "text", has("touch") ? "click" : "dblclick"),
             
              id: {
                label: 'OID',
                hidden: true
              }
            },
            // for Selection; only select a single row at a time
            selectionMode: 'single'//,// for Keyboard; allow only row-level keyboard navigation //cellNavigation: false
          }, "towersdgrid");//attaches to dom id
          //store = new Memory({data: data});//for test
          //this.towerGrid.set("store", store);//for test
          //this.towerGrid.startup();
          this.towerGrid.on('dgrid-select', lang.hitch(this, 'selectedGridRow'));
          this.towerGrid.on('dgrid-refresh-complete', lang.hitch(this, 'dgridRefreshComplete'));
          this.towerGrid.on('dgrid-datachange', lang.hitch(this, 'selectedCellForEdit'));
        }
        this.fireBearingsLayer.clear();
        this.labelForTowersLayer.clear();
        var data=[];
        array.forEach(featureSet.features, lang.hitch(this, function(featVal){
          var featureObj = new Object;
          featureObj["geometry"] = featVal.geometry;
          for(var attribName in featVal.attributes){
            var attribValue = featVal.attributes[attribName];
            if(attribName === "BEARING" && !attribValue){
              attribValue = "NA";
            }
            var attribName = attribName;
            if(attribName === "OBJECTID"){//grid is very picky about that id, selection goes crazy
              attribName = "id";
            }
            featureObj[attribName]= attribValue;
          }
          data.push(featureObj);
          this.labelForTowersLayer.add(this._createTextLabelGraphic(featureObj));
          this.createFireBearingLineGraphic(featureObj);
          //console.log("firelinecreated");
        }));
        var memoryStore = new Memory({ data: data });
        this.towerGridCurrentMemStore = memoryStore;
        this.towerGrid.set("store", this.towerGridCurrentMemStore);
        this.towerGrid.resize();
        this.findFireBearingLineIntersct(data);
        this.towerMetricsCount++
        this.towerGrid.refresh();
        this.labelForTowersLayer.show();
        //console.log("grid-updated");
      },

 

Notes About Issues Encountered:

Beware of the id, at line 46-49, I had to use that name "id" for the id attribute of the table.

One other issue I've seen is when the dgrid is on a container tab its best to "startup" the container in widget postcreate and perform the dgrid initialization to follow that in widget startup, the issue is that the grid will not be shown, this could be a version specific bug. Using the tab theme when the widget tab is hidden on resize or switching widgets when opened back up the grid will draw the first row over the header row until you resize the browser window, again this may just in the version I am using.

 

 

When I first tried using this dgrid I found it helped to use some test data like you see on line 2, just to know the dgrid would render ok before implementing the query result data handling.

You might be wondering what the query attribute on line 7 is for? This is used to perform filtering, to a single row in the data grid. You'll see a line in the widget.js that sets the filter string and applies the filter, the purpose in this widget is to allow the user to select a tower from the drop down, pan to that tower and have the dgrid cleared (filtered) to only show that single row.

              this.filter = val.features[0].attributes.NAME;
              this.towerGrid.refresh();

In this case, on the tower selection event, the tower name is set in the filter and that value is applied on grid refresh to filter to that row only, and our dgrid will show only that single row.

In the onTowerMetricsQueryComplete function above you'll notice lines 15-18 define the "Bearing" column to be of type editor, which makes it editable. In order to figure out how to set the columns type I had to read the documentation, and it was a little hard at times to tell how to do some of this since its changed across versions, so you need to be aware of the version of dgrid you are using in order to reference the proper help docs. If you are trying to use other types like drop downs in a cell or check boxes you need to be referencing the correct documentation for the version of dgrid you are working with.

Just about everything works well in the dgrid version my web app was using, except I found the data grid would display the column header wrong on browser re-sizing, perhaps its just a bug in this version, I don't know.

 

The last part I'll describe is how this widget will apply edits to the feature service.

The only field in this example that is editable is the Bearing field, and on line 18 above, you can see I've made this cell respond to tap for mobile devices or double click if not. So on tap or d'click you will be able to enter text in this cell.

on line 33 above , we hook the data change event so we can call a function that will apply the edits on data change.

Here is what that function looks like...

      selectedCellForEdit: function (e){
        //console.log("new_cell_value:" + e.value);
        //console.log("old_cell_value:" + e.oldValue);
        var newValue = "";
        if(e.value === e.oldValue) return;//just leave if hasn't changed.
       
        var fieldName = e.cell.column.field;
        if(this.siteIsReadOnly){
          return;
        }
        var oid = 0;
        oid = e.cell.row.data.id;
        newValue = e.value;
        var updateFeature = this.getGraphicFromLayerByOID(oid);
        if(updateFeature){
            updateFeature.attributes[fieldName] = newValue;
            this.towerMetricsFeatureLayer.applyEdits(null, [updateFeature], null).then(lang.hitch(this, function() {
                      //this.emit('edit-tool-edits-complete', arguments);
                      var gFeature = this.getGraphicFromLayerByOID(oid);
                      this.onGridButtonClick();
                      var geomOfPt = gFeature.geometry;
                      this.map.centerAndZoom(geomOfPt, this.config.towersitezoom || 24);
                      //console.log("editok");
                    }), lang.hitch(this, function() {
                      //this.emit('edit-tool-edits-error', arguments);
                      console.log("editfail");
                    }));
            this.towerGrid.save();//applies changes to memory store backing grid, the whole memory store is refreshed with refresh button
            //console.log("saved:" + e.value);
        }
      },

In the code above we are passed an object that contains everything we need to build the apply edits, and to make it a little easier we call a helper function to get our feature by id and then just update the field value before calling applyedits.

 

This is the helper function to get the feature from the feature layer that we are going to be applying an edit to.

 

      getGraphicFromLayerByOID: function (oid){
        for (var i = 0, len = this.towerMetricsFeatureLayer.graphics.length; i < len; i++) {
          var feature = this.towerMetricsFeatureLayer.graphics[i];
          if(feature.attributes["OBJECTID"] == oid)
            return feature;
        }
        return null;
      },

 

dgrid css

.jimu-widget-firefinder .dgrid-div {
   height: 70%;
   margin-bottom: 5px;
}

/* dgrid css contr*/
.jimu-widget-firefinder .dgrid-grid {
    width: 97%;
}
.jimu-widget-firefinder .dgrid-row{
    height: 22px;
}
.jimu-widget-firefinder .dgrid-cell {
    width: 100px;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.jimu-widget-firefinder .field-NAME {
    width: 100px;
}

 

 

 

Please see the attached widget for the full implementation code.

Attachments

Outcomes