Custom widget settings - layerInfos class help

4251
6
Jump to solution
10-07-2015 06:34 AM
AndyE
by
New Contributor II

Hi,

I'm new to the web app builder and JavaScript and I'm having a small problem with the settings page of my custom widget.

I have 2 selection boxes listing the feature layers that are in the map, allowing the user to pick two of the layers in the map for use in my widget. The problem I have is on first loading the settings page, the chosen layers from the widgets config file are not being selected in the selection boxes.

When opening the settings page a second time it displays the chosen layers in the selection boxes with no problems.

I'm assuming this has something to do with timing as the layerInfos class is deferred?

I think that means the selection boxes don't have any options when the settings from the config file are applied, so there are no options to select. How can I wait for the selection boxes to be fully populated before applying the settings from the config file?

If it helps, my current code is run in postCreate:

postCreate: function() {
  //var opts = this._getLayerList();
  //this._makeSelects(opts);
  this._getLayerList();
  },

Which gets a list of feature layers:

_getLayerList: function() {
  // Get the feature layer names...
  var lyrOptions = [];

  LayerInfos.getInstance(this.map, this.map.itemInfo).then(lang.hitch(this,function(layerInfosObject) {
       layerInfosObject.traversal(lang.hitch(this,function(layerInfo) {
            layerInfo.getLayerType().then(lang.hitch(this,function(type) {
                 if (type == "FeatureLayer") {
                      lyrOptions.push( { value: layerInfo.title, label: layerInfo.title } );
                 }
            }));
       }));
  }));

  //return lyrOptions;
  this._makeSelects(lyrOptions);
  },

And the list of layers in then used to populate the selection boxes:

_makeSelects: function(lyrOptions) {
  // Create selection lists
  var sel1 = new Select({
       id: "selectOpportunity",
       emptyLabel: " -- Choose a Map layer -- ",
       options: lyrOptions
       }).placeAt(this.opportunityFcList).startup();
  var sel2 = new Select({
  id: "selectRestricted",
  emptyLabel: " -- Choose a Map layer -- ",
       options: lyrOptions
       }).placeAt(this.restrictedFcList).startup();
  }

The problem is, setConfig is happening before the seletion boxes have any options, so my chosen options are not being selected the first time the settings page is opened:

setConfig: function(config) {
  // Load settings from the config file
  this.config = config;

  // Set select value from config
  if (config.opportunityLayer !== undefined) {
  dijit.byId('selectOpportunity').set('value', config.opportunityLayer);
  }
  if (config.restrictedLayer !== undefined) {
  dijit.byId('selectRestricted').set('value', config.restrictedLayer);
  }
  },

Thanks,

Andy

0 Kudos
1 Solution

Accepted Solutions
AndyE
by
New Contributor II

Hi Robert,

Thanks for your help, the code you posted didn't resolve the issue, but it pointed me in the right direction. The final solution was to ditch layerInfosObject.traversal, and instead generate 2 arrays of layerInfo objects and layerTypes, wait for all the types to resolve, and then set the selected values.

This is the working code:

postCreate: function() {
  var layerTypes = [];
  var lyrOptions = [];

  // Get the map layers
  this.getLayerList().then(lang.hitch(this, function(myLayerInfos){
  array.forEach(myLayerInfos,lang.hitch(this,function(layerInfo){
  // Get an array of map layer types
  layerTypes.push(layerInfo.getLayerType());
  }));

  // When all the type have been retrieved...
  all(layerTypes).then(lang.hitch(this,function(layerTypes) {
  for (var j=0; j<layerTypes.length;j++) {
  // ... build an options list of just the feature layers
  if (layerTypes == "FeatureLayer"){
  lyrOptions.push({ value: myLayerInfos.title, label: myLayerInfos.title });
  }
  }
  this.makeSelects(lyrOptions);
  }));
  }));
},


getLayerList: function() {
  // Get an array of layerInfo for every layer in the map
  var def = new Deferred();  
  var myLayerInfos = [];

  LayerInfos.getInstance(this.map, this.map.itemInfo).then(lang.hitch(this, function(layerInfosObj) {
  myLayerInfos.push.apply(myLayerInfos,this.iterLayers(layerInfosObj.getLayerInfoArray()));
  def.resolve(myLayerInfos);
  }), lang.hitch(this, function(err) {
  console.error(err);
  def.reject(err);
  }));

  return def.promise;
},


iterLayers: function(layerInfos){
  // Iterate through layers and sublayers to get an array of all the layers
  var result = [];

  array.forEach(layerInfos,lang.hitch(this,function(layerInfo){
  result.push(layerInfo);
  result.push.apply(result,this.iterLayers(layerInfo.getSubLayers()));
  }));

  return result;
},


makeSelects: function(lyrOptions) {
  // Create selection lists
  var sel1 = new Select({
  id: "selectOpportunity",
  emptyLabel: " -- Choose a Map layer -- ",
  options: lyrOptions
  }).placeAt(this.opportunityFcList).startup();

  var sel2 = new Select({
  id: "selectRestricted",
  emptyLabel: " -- Choose a Map layer -- ",
  options: lyrOptions
  }).placeAt(this.restrictedFcList).startup();

  if (this.config.opportunityLayer !== undefined) {
  dijit.byId('selectOpportunity').set('value', this.config.opportunityLayer);
  }
  if (this.config.restrictedLayer !== undefined) {
  dijit.byId('selectRestricted').set('value', this.config.restrictedLayer);
  }
},

Thanks for your time, I didn't think it would take so long to resolve this one!

View solution in original post

6 Replies
RobertScheitlin__GISP
MVP Emeritus

Andy,

Here are my suggested changes:

      getLayerList: function() {
        // Get the feature layer names...
        var lyrOptions = [];

        LayerInfos.getInstance(this.map, this.map.itemInfo).then(lang.hitch(this,function(layerInfosObject) {
          layerInfosObject.traversal(lang.hitch(this,function(layerInfo) {
            layerInfo.getLayerType().then(lang.hitch(this,function(type) {
              if (type == "FeatureLayer") {
                lyrOptions.push( { value: layerInfo.title, label: layerInfo.title } );
              }
             }));
          }));
          //return lyrOptions;
          this._makeSelects(lyrOptions);
        })); 
      },
      
      _makeSelects: function(lyrOptions) {  
        // Create selection lists
        var sel1 = new Select({
          id: "selectOpportunity",
          emptyLabel: " -- Choose a Map layer -- ",
          options: lyrOptions
        }).placeAt(this.opportunityFcList).startup();
        var sel2 = new Select({
          id: "selectRestricted",
          emptyLabel: " -- Choose a Map layer -- ",
          options: lyrOptions
        }).placeAt(this.restrictedFcList).startup();
        if (this.config.opportunityLayer !== undefined) {  
          sel1.set('value', this.config.opportunityLayer);  
        }  
        if (this.config.restrictedLayer !== undefined) {  
          sel2.set('value', this.config.restrictedLayer);  
        }
      },
      
      setConfig: function(config) {  
        // Load settings from the config file
        this.config = config;
      },
AndyE
by
New Contributor II

Hi Robert,

Thanks for the suggestion, I've tried that and the end result is the same, the config is not loaded when first opening the settings page, but it is on subsequent opening of the settings.

I've also been experimenting with Deferred/Promise and the "all" function but the end result always ends up the same (apart from the occasional error where).

I've tried to unpick how the attribute table widget (the only widget I can think of that displays a list of map layers) is able to get a list of layers before loading its config but my JavaScript knowledge isn't quite there yet.

0 Kudos
RobertScheitlin__GISP
MVP Emeritus

Andy,

  Ok try this:

      getLayerList: function() {  
        // Get the feature layer names...  
        var lyrOptions = [];  
  
        LayerInfos.getLayerInfoArrayByType(this.map, "FeatureLayer").then(lang.hitch(this,function(layerInfosArray) {  
          array.forEach(layerInfosArray, lang.hitch(this, function(layerInfo){
            lyrOptions.push( { value: layerInfo.title, label: layerInfo.title } );
          }));
          //return lyrOptions;  
          this._makeSelects(lyrOptions);  
        }));   
      },  
        
      _makeSelects: function(lyrOptions) {    
        // Create selection lists  
        var sel1 = new Select({  
          id: "selectOpportunity",  
          emptyLabel: " -- Choose a Map layer -- ",  
          options: lyrOptions  
        }).placeAt(this.opportunityFcList).startup();  
        var sel2 = new Select({  
          id: "selectRestricted",  
          emptyLabel: " -- Choose a Map layer -- ",  
          options: lyrOptions  
        }).placeAt(this.restrictedFcList).startup();  
        if (this.config.opportunityLayer !== undefined) {    
          sel1.set('value', this.config.opportunityLayer);    
        }    
        if (this.config.restrictedLayer !== undefined) {    
          sel2.set('value', this.config.restrictedLayer);    
        }  
      },  
        
      setConfig: function(config) {    
        // Load settings from the config file  
        this.config = config;  
      },
AndyE
by
New Contributor II

Thanks Robert,

I'm getting an error in the console now:

TypeError: Cannot read property 'itemData' of undefined
    at Function.<anonymous> (https://localhost/webappbuilder/stemapp/jimu.js/LayerInfos/LayerInfos.js:755:55)
    at https://js.arcgis.com/3.14/init.js:167:296
    at k (https://js.arcgis.com/3.14/init.js:189:431)
    at m (https://js.arcgis.com/3.14/init.js:189:357)
    at resolve (https://js.arcgis.com/3.14/init.js:191:441)
    at null.<anonymous> (https://localhost/webappbuilder/stemapp/jimu.js/LayerInfos/LayerInfoFactory.js:69:18)
    at https://js.arcgis.com/3.14/init.js:167:296
    at ha (https://js.arcgis.com/3.14/init.js:22:473)
    at https://js.arcgis.com/3.14/init.js:23:202
    at ia (https://js.arcgis.com/3.14/init.js:23:89)
    ----------------------------------------
    rejected at a (https://js.arcgis.com/3.14/init.js:190:337)
    at k (https://js.arcgis.com/3.14/init.js:190:89)
    at m (https://js.arcgis.com/3.14/init.js:189:357)
    at resolve (https://js.arcgis.com/3.14/init.js:191:441)
    at null.<anonymous> (https://localhost/webappbuilder/stemapp/jimu.js/LayerInfos/LayerInfoFactory.js:69:18)
    at https://js.arcgis.com/3.14/init.js:167:296
    at ha (https://js.arcgis.com/3.14/init.js:22:473)
    at https://js.arcgis.com/3.14/init.js:23:202
    at ia (https://js.arcgis.com/3.14/init.js:23:89)
    at fa (https://js.arcgis.com/3.14/init.js:23:144)
    ----------------------------------------
Error
    at then.b.then (https://js.arcgis.com/3.14/init.js:192:253)
    at Function.clazz.getInstance (https://localhost/webappbuilder/stemapp/jimu.js/LayerInfos/LayerInfos.js:754:48)
    at Function.clazz.getLayerInfoArrayByType (https://localhost/webappbuilder/stemapp/jimu.js/LayerInfos/LayerInfos.js:712:11)
    at declare._getLayerList (https://localhost/webappbuilder/apps/2/widgets/EditOpportunity/setting/Setting.js:88:14)
    at declare.postCreate (https://localhost/webappbuilder/apps/2/widgets/EditOpportunity/setting/Setting.js:41:8)
    at l.create (https://js.arcgis.com/3.14/init.js:1124:207)
    at l.postscript (https://js.arcgis.com/3.14/init.js:1122:464)
    at new <anonymous> (https://js.arcgis.com/3.14/init.js:98:192)
    at null.<anonymous> (https://localhost/webappbuilder/stemapp/jimu.js/WidgetManager.js:338:33)
    at https://js.arcgis.com/3.14/init.js:167:296"n @ init.js:199(anonymous function) @ init.js:199g.filter @ init.js:220k @ init
.js:199

I tried putting in a check on layerInfo to see if it was undefined in the array.forEach but the result was the same.

I'm using the Developer Web App Builder 1.2, which may be an issue?

(This function isn't in any of the documents, though looking at it seems similar to what I've tried with dojo/promise/all to collect all the layer types together, I may have been doing it wrong but it was still proceeding with the settings before getting all the promised results)

RobertScheitlin__GISP
MVP Emeritus

Andy,

  OK I see that they have an error in the getLayerInfoArrayByType function. So you would need to revert back to they way you were doing it.

OK here is my latest attempt:

      getLayerList: function() {
        var def = new Deferred();
        LayerInfos.getInstance(this.map, this.map.itemInfo).then(lang.hitch(this, function(layerInfosObj) {
          var lyrOptions = [];
          layerInfosObj.traversal(lang.hitch(this, function(layerInfo) {
            layerInfo.getLayerType().then(lang.hitch(this,function(type) {
              if (type == "FeatureLayer") {
                lyrOptions.push( { value: layerInfo.title, label: layerInfo.title } );
              }
             }));
          }));

          def.resolve(lyrOptions);
        }), lang.hitch(this, function(err) {
          console.error(err);
          def.reject(err);
        }));

        return def.promise;  
      },
      
      this.getLayerList().then(lang.hitch(this, function(lyrOptions){
        this._makeSelects(lyrOptions);
      }),

      _makeSelects: function(lyrOptions) {    
        // Create selection lists  
        var sel1 = new Select({  
          id: "selectOpportunity",  
          emptyLabel: " -- Choose a Map layer -- ",  
          options: lyrOptions  
        }).placeAt(this.opportunityFcList).startup();  
        var sel2 = new Select({  
          id: "selectRestricted",  
          emptyLabel: " -- Choose a Map layer -- ",  
          options: lyrOptions  
        }).placeAt(this.restrictedFcList).startup();  
        if (this.config.opportunityLayer !== undefined) {    
          sel1.set('value', this.config.opportunityLayer);    
        }    
        if (this.config.restrictedLayer !== undefined) {    
          sel2.set('value', this.config.restrictedLayer);    
        }  
      },  
        
      setConfig: function(config) {    
        // Load settings from the config file  
        this.config = config;  
      },
AndyE
by
New Contributor II

Hi Robert,

Thanks for your help, the code you posted didn't resolve the issue, but it pointed me in the right direction. The final solution was to ditch layerInfosObject.traversal, and instead generate 2 arrays of layerInfo objects and layerTypes, wait for all the types to resolve, and then set the selected values.

This is the working code:

postCreate: function() {
  var layerTypes = [];
  var lyrOptions = [];

  // Get the map layers
  this.getLayerList().then(lang.hitch(this, function(myLayerInfos){
  array.forEach(myLayerInfos,lang.hitch(this,function(layerInfo){
  // Get an array of map layer types
  layerTypes.push(layerInfo.getLayerType());
  }));

  // When all the type have been retrieved...
  all(layerTypes).then(lang.hitch(this,function(layerTypes) {
  for (var j=0; j<layerTypes.length;j++) {
  // ... build an options list of just the feature layers
  if (layerTypes == "FeatureLayer"){
  lyrOptions.push({ value: myLayerInfos.title, label: myLayerInfos.title });
  }
  }
  this.makeSelects(lyrOptions);
  }));
  }));
},


getLayerList: function() {
  // Get an array of layerInfo for every layer in the map
  var def = new Deferred();  
  var myLayerInfos = [];

  LayerInfos.getInstance(this.map, this.map.itemInfo).then(lang.hitch(this, function(layerInfosObj) {
  myLayerInfos.push.apply(myLayerInfos,this.iterLayers(layerInfosObj.getLayerInfoArray()));
  def.resolve(myLayerInfos);
  }), lang.hitch(this, function(err) {
  console.error(err);
  def.reject(err);
  }));

  return def.promise;
},


iterLayers: function(layerInfos){
  // Iterate through layers and sublayers to get an array of all the layers
  var result = [];

  array.forEach(layerInfos,lang.hitch(this,function(layerInfo){
  result.push(layerInfo);
  result.push.apply(result,this.iterLayers(layerInfo.getSubLayers()));
  }));

  return result;
},


makeSelects: function(lyrOptions) {
  // Create selection lists
  var sel1 = new Select({
  id: "selectOpportunity",
  emptyLabel: " -- Choose a Map layer -- ",
  options: lyrOptions
  }).placeAt(this.opportunityFcList).startup();

  var sel2 = new Select({
  id: "selectRestricted",
  emptyLabel: " -- Choose a Map layer -- ",
  options: lyrOptions
  }).placeAt(this.restrictedFcList).startup();

  if (this.config.opportunityLayer !== undefined) {
  dijit.byId('selectOpportunity').set('value', this.config.opportunityLayer);
  }
  if (this.config.restrictedLayer !== undefined) {
  dijit.byId('selectRestricted').set('value', this.config.restrictedLayer);
  }
},

Thanks for your time, I didn't think it would take so long to resolve this one!