bpelchat

DGRID LayerToggleTable Widget

Blog Post created by bpelchat on Jan 2, 2018

Recently we created a new widget in wab based upon our layer control widget in flex. In one of our flex viewer apps we

created a widget our users requested that would allow them to quickly toggle on and off a few important layers, ones that also are populous and usually would be off until needed.They did not want to have to search for these in the Layer List widget, which in our app had a plethora of map layers each with many sub layers. The layer in this example would be our 911 address points and landmarks.

 

In the flex viewer we used the grid control and this is what it looks like in flex.

 

In wab we have now implemented the same widget , using the dojo dgrid.

Here is what that new widget looks like in wab ...

The widget needed to do the following

  • Be configurable as to sub-layers the widget will control
  • Allow the user to set the visibility of those pre-configured layers on and off, and parent when turning on visibility
  • The widget should reflect the visibility state when the same layers are toggled on or off from the Layer List widget
  • The name of the layer should be clearly visible in the widgets layer list along with its visible state

I wasn't sure about what event to use or was available for layer visibility and after searching around the javascript api and wab api , I then looked at the wab layer list widget and found the events it was hooking onto from layers, one of the four it used seemed have enough information and was fired whenever either the top parent or sub-layer was toggled in the Layer List.

       /*******************************************
       * Events to capture from operational layers
       ********************************************/

      bindOperLayerEvents: function() {
        this.own(on(this.webMapOperLayers,'layerInfosChanged',lang.hitch(this, this._onLayerInfosChanged)));
        this.own(on(this.webMapOperLayers,'tableInfosChanged',lang.hitch(this, this._onLayerInfosChanged)));
        this.own(this.webMapOperLayers.on('layerInfosIsVisibleChanged',lang.hitch(this, this._onLayerInfosIsVisibleChanged)));
        this.own(on(this.webMapOperLayers,'updated',lang.hitch(this, this._onLayerInfosObjUpdated)));
      },
      /***************************************************************************
       * Captures any change for parent and sublayers
       * Will iterate over them all, see if any layers have been changed that we control,
       * if so will refresh our data store.
       */

      _onLayerInfosIsVisibleChanged: function(changedLayerInfos) {
        this.dirty = false;
        array.forEach(this.ourParentControlledLayerInfos, lang.hitch(this, function(li){
            if (changedLayerInfos[0].id === li.id) {//expecting parent to always be at index of 0.
              changedLayerInfos[0].traversal(lang.hitch(this, function(layerInfo) {
                 this.log(layerInfo.title + "," + layerInfo.id);
                 array.forEach(this.config.layersById, lang.hitch(this, function(confIdObj){
                      if(confIdObj.id === layerInfo.id){
                        if(this.dirty === true){
                          this.updateDataStoreOnDiff(layerInfo);
                        }else{
                          this.dirty = this.updateDataStoreOnDiff(layerInfo);
                        }
                      }
                  }));
               }));
               if(this.dirty === true){
                  this.layerToggleDgrid.refresh();
               }
            }
        }));
      },

 

For the table I set one field in the data grid as type editor checkbox, and named it "rowcheckbox", and used the cell change event to catch the user action. I change the visibility of the sub-layer using the layer id which I keep in the id field of the data grid row along with a third field for the layer title.

Since we are catching the layer event visibility change and we are also triggering that event when we set the visibility, the order we save to the data store and set the visibility is important.

 

When the widget first opens , it creates and starts up the tab container in post create, and initializes and populates the grid and backing memory store by getting the parent and sub layer information using web appbuilder layer infos and its Traverse Depth-First-Search, from which we also  identify and store layer infos that are to be controlled and monitored by the widget.

      /*********************************************************************
       * Traverses all the webmap layers:
       * With DEBUG variable set to true will dump to browser console
       * all names and ids for every layer and sublayers in the webmap.
       * You can use that information to help configure the widget!
       *********************************************************************/

      getLayerInfosObj: function(){
        LayerInfos.getInstance(this.map, this.map.itemInfo).then(lang.hitch(this,function(layerInfosObject){
          this.webMapOperLayers = layerInfosObject;
          this.bindOperLayerEvents();
          layerInfosObject.getLayerInfoArray().forEach(lang.hitch(this,function(li) {
             var isParent = (li.parentLayerInfo === null);//could use leaf or root see wab api
             this.log("webOperLayer: Title=" + li.title + ", Id=" + li.id + " , Parent=" + isParent);
             if(isParent){
               this.savedParent=false;
               this.log("sublayers:" + li.getSubLayers().length);
               var configLayersArr = this.config.layersById;
               li.traversal(lang.hitch(this, function(layerInfo) {
                 this.log(layerInfo.title + "," + layerInfo.id);
                 array.forEach(this.config.layersById, lang.hitch(this, function(confIdObj){
                      if(confIdObj.id === layerInfo.id){
                        if(!this.savedParent){
                          this.savedParent = true;
                          this.ourParentControlledLayerInfos.push(li);
                        }
                        this.ourControlledLayerInfos.push(layerInfo);//layers for the grid and to control, we'll use the layer info name and id in the grid fields
                      }
                  }));
               }));
             }
          }));
          if(this.ourControlledLayerInfos.length > 0) this.populateGrid();
        }));
      },
     

To configure the widget you need to add the layer id to the widget config file for each sub layer to control in the widget.

Just look at the widget config.json file with the widget and you'll see its very simple array of layer ids. I tend not to write settings code for widgets mainly due to the extra time it takes to right them and if they aren't complicated its quite easy to just edit the json config file your self.

To make this easier though, inside the widget if you set the debug class variable to true, all the webmap layer id's are dumped out to the console along with parent and sub layer information.

Just add the widget to a web app, one of course that is using the webmap that contains the layers you will be using, and run it with this debug variable set to true. Open the debug window in your browser and look at the console dump you'll see all the web map layer information and you can copy and paste out the layer ids you want in your widget configuration from that dump. (This is the part that could be put into a setup interface and perhaps will in time).

This is an example of the console dump shown from my firefox browser, each section of web map operational layers is delineated with the "webOperLayer" tag and followed with the "sublayers" count title and ids....

 

In our flex viewer we titled the widget, in nls strings file, for the layers it controlled in this case 911Addresses and gave it what we considered a relevant widget icon for those map layers, we did the same in our wab web application for this widget, but anyone else may want to change the icon.png to a more generic one, and also the widget title.

 

Hope others will find this helpful, I've attached the widget to this blog .

Attachments

Outcomes