Issues in generating a ComboBox within WAB Widget

4657
15
Jump to solution
10-07-2015 05:06 AM
BenHahn
New Contributor II

Hi everyone,

I am pretty new to WAB and Javacript development with Dojo. My plan is to generate a dynamic Combobox out of feature-layer attributes. On click the widget should add that specific feature-layer with the chosen layerexpression. e.g. Country selection ComboBox: "USA" -> Zoom on USA.

I got this to work in a standalone Javascript api, but now i'd like to include this into my WAB1.2 functionality set as a widget and I ran into several Dojo issues which I am not familiar with.

Basically I started a new widget from scratch:

Widget.js

define([
    'dojo/_base/declare', 
    'jimu/BaseWidget', 
    'jimu/ConfigManager', 
    "jimu/WidgetManager",    
    'esri/layers/FeatureLayer',  
    "dijit/form/Select",
    "dojo/query", 
    "dojo/ready", 
    "dijit/form/ComboBox", 
    "dojo/data/ItemFileReadStore", 
    "dojo/_base/array", 
    "dojo/on"
    ],
    
function(
    declare, 
    BaseWidget,
    ConfigManager,  
    WidgetManager,
    FeatureLayer, 
    Select,
    query, 
    ready,
    ComboBox, 
    ItemFileReadStore, 
    array, 
    on
    
) {
  return declare([BaseWidget], {
        baseClass: 'definition_query',  
        onOpen: function () {  

            function init() {
               queryTask = new esri.tasks.QueryTask
                        ("https://.../MapServer/0");

               query = new esri.tasks.Query();
               query.returnGeometry = false;
               query.outFields = ["NAME"];
               query.where = "NAME<> ''";
               queryTask.execute(query,populateList);
            }

            function populateList(results) {
                //Populate the ComboBox with unique values
                var zone;
                var values = [];
                var testVals={};

                //Add option to display all zoning types to the ComboBox
                values.push({name:"ALL"})

                //Loop through the QueryTask results and populate an array
                //with the unique values
                var features = results.features;
                dojo.forEach (features, function(feature) {
                  zone = feature.attributes.NAME;
                  if (!testVals[zone]) {
                    testVals[zone] = true;
                    values.push({name:zone});
                  }
                });
                //Create a ItemFileReadStore and use it for the
                //ComboBox's data source
                var dataItems = {
                       identifier: 'name',
                       label: 'name',
                       items: values
                };
            
                var store = new dojo.store.Memory({data:dataItems});//tried to work with ItemFileReadStore before, but couldn't get it to work either 
                console.log(dataItems);
                console.log(store);
                dijit.byId("mySelect").set('store', store);    
            }

        dojo.addOnLoad(init);    

         },  
  });
});

TypeError: dijit.byId(...) is undefined

dijit.byId("mySelect").set('store', store);

My Issue is dijit.byId won't be interpreted. What kind of modules am I missing? I am unsure how to figure out which of the modules are necessary for this job.

As mentioned, pretty much the same code works on plain javascript api. There are several working examples on JS but I couldn't manage to convert them into WAB. Maybe there is already a widget that has similar functionality?

HTML:

<select id="mySelect" dojoType="dijit.form.ComboBox" style="width:300px;font-size:18px;" value="Select Zoning Type"></select>

Thanks for advice

1 Solution

Accepted Solutions
RobertScheitlin__GISP
MVP Emeritus

Ben,

  I can't believe that I missed it earlier. But here it is working:

  1. There were a couple of issues but the main one was line 35. You have to have _WidgetsInTemplateMixin in the declare when using widgets in a template.
  2. Line 51. Adding lang.hitch so that the function was in scope and knew what "this" was.
  3. Line 89. You use this.mySelect.set not this.mySelect.setData

define([
  "dojo/_base/declare",
  "jimu/BaseWidget",
  "dijit/_WidgetsInTemplateMixin",
  "dojo/aspect",
  "jimu/ConfigManager",
  "jimu/WidgetManager",
  "esri/layers/FeatureLayer",
  "dojo/query",
  "esri/tasks/query",
  "esri/tasks/QueryTask",
  "dojo/store/Memory",
  "dojo/_base/array",
  "dojo/on",
  "dojo/_base/lang",
  "dijit/form/ComboBox"
  ],

  function (
    declare,
    BaseWidget,
    _WidgetsInTemplateMixin,
    aspect,
    ConfigManager,
    WidgetManager,
    FeatureLayer,
    dojoQuery,
    Query,
    QueryTask,
    Memory,
    array,
    on,
    lang
  ) {
    return declare([BaseWidget, _WidgetsInTemplateMixin], {

      baseClass: "definition_query",

      queryTask: null,
      query: null,

      postCreate: function () {
        this.queryTask = new QueryTask("http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/PublicSafety/PublicSafetyOperationalLayer...");
        this.query = new Query();
        this.query.returnGeometry = false;
        this.query.outFields = ["FDNAME"];
        this.query.where = "FDNAME <> ''";
      },

      onOpen: function () {
        this.queryTask.execute(this.query, lang.hitch(this, this.populateList));
      },

      populateList: function (results) {
        //Populate the ComboBox with unique values
        var zone;
        var values = [];
        var testVals = {};

        //Add option to display all zoning types to the ComboBox
        values.push({
          name: "ALL"
        });

        //Loop through the QueryTask results and populate an array
        //with the unique values
        var features = results.features;
        array.forEach(features, function (feature) {
          zone = feature.attributes.FDNAME;
          if (!testVals[zone]) {
            testVals[zone] = true;
            values.push({
              name: zone
            });
          }
        });
        //Create a ItemFileReadStore and use it for the
        //ComboBox's data source
        var dataItems = {
          identifier: "name",
          label: "name",
          items: values
        };
        //console.log(dataItems);
        var store = new Memory({
          data: dataItems
        });
        //console.log(store);
        this.mySelect.set("store", store);
      }
    });
  });

View solution in original post

15 Replies
TimWitt2
MVP Alum

If "mySelect" is in your widget you would use this.mySelect to reference it.

BenHahn
New Contributor II

Thanks Tim for clarification in this case,

even though I changed my code to

dijit.byId(this.mySelect).set('store', store);

dijit.byId still remains undefined and will be thrown

0 Kudos
RobertScheitlin__GISP
MVP Emeritus

Ben,

  You are using a mixture of Legacy and AMD in your code (bad practice).

You need to have 'dijit/_WidgetsInTemplateMixin',  and _WidgetsInTemplateMixin in your requires and vars in order to use dojo dijits/widgets in your template html.

Here is your js updated to all AMD and adding necessary requires:

define([
    "dojo/_base/declare",
    "jimu/BaseWidget",
    "dijit/_WidgetsInTemplateMixin",
    "jimu/ConfigManager",
    "jimu/WidgetManager",
    "esri/layers/FeatureLayer",
    "dijit/form/Select",
    "dojo/query",
    "esri/tasks/query",
    "esri/tasks/QueryTask",
    "dijit/form/ComboBox",
    "dojo/store/Memory",
    "dojo/_base/array",
    "dojo/on"
    ],

  function (
    declare,
    BaseWidget,
    _WidgetsInTemplateMixin,
    ConfigManager,
    WidgetManager,
    FeatureLayer,
    Select,
    dojoQuery,
    Query,
    QueryTask,
    ready,
    ComboBox,
    Memory,
    array,
    on
  ) {
    return declare([BaseWidget], {
      baseClass: "definition_query",
      
      queryTask: null,
      query: null,
      
      postCreate: function () {
        this.queryTask = new QueryTask("https://.../MapServer/0");
        this.query = new Query();
        this.query.returnGeometry = false;
        this.query.outFields = ["NAME"];
        this.query.where = "NAME<> ''";
      },
      
      onOpen: function () {
        this.queryTask.execute(this.query, this.populateList);
      },
      
      populateList: function (results) {
          //Populate the ComboBox with unique values
          var zone;
          var values = [];
          var testVals = {};

          //Add option to display all zoning types to the ComboBox
          values.push({
            name: "ALL"
          });

          //Loop through the QueryTask results and populate an array
          //with the unique values
          var features = results.features;
          array.forEach(features, function (feature) {
            zone = feature.attributes.NAME;
            if (!testVals[zone]) {
              testVals[zone] = true;
              values.push({
                name: zone
              });
            }
          });
          //Create a ItemFileReadStore and use it for the
          //ComboBox's data source
          var dataItems = {
            identifier: "name",
            label: "name",
            items: values
          };

          var store = new Memory({
            data: dataItems
          }); //tried to work with ItemFileReadStore before, but couldn't get it to work either
          console.log(dataItems);
          console.log(store);
          this.mySelect.set("store", store);
        }
    });
  });
BenHahn
New Contributor II

Hi Robert, thanks alot for your work correcting my code! This gives me a huge plus in understanding the differences of Legacy and AMD. Until now it used to be pretty confusing to me and gave me plenty of headaches.

Still I struggle with a few things in this code,

I had to change Line67 from your suggested

array.forEach(features, function (feature) { 

to

features.forEach(function (feature) {

on Line 84

var store = new Memory({data: dataItems});

I get "Memory is not a constructor" with dojo.store.Memory it works well (though its bad style), I dont understand why your code does not work - since the dojo/store/memory is properly declared.

and finally on Line 89

this.mySelect.set("store", store); 

even if store is properly defined, it throws me off with

detailed error: TypeError: this.mySelect is undefined

0 Kudos
RobertScheitlin__GISP
MVP Emeritus

Ben,

  I should have included the html as well. When working with templated widgets you don't use IDs you use data-dojo-attach-point:

<div>
  <select data-dojo-attach-point="mySelect" dojotype="dijit/form/ComboBox" style="width:300px;font-size:18px;" value="Select Zoning Type"></select>
</div>

Sorry I also see that I left "ready" in the vars and this throws off the require and vars sync. Removing ready should fix the array and memory issue:

define([
    "dojo/_base/declare",
    "jimu/BaseWidget",
    "dijit/_WidgetsInTemplateMixin",
    "jimu/ConfigManager",
    "jimu/WidgetManager",
    "esri/layers/FeatureLayer",
    "dijit/form/Select",
    "dojo/query",
    "esri/tasks/query",
    "esri/tasks/QueryTask",
    "dijit/form/ComboBox",
    "dojo/store/Memory",
    "dojo/_base/array",
    "dojo/on"
    ],

  function (
    declare,
    BaseWidget,
    _WidgetsInTemplateMixin,
    ConfigManager,
    WidgetManager,
    FeatureLayer,
    Select,
    dojoQuery,
    Query,
    QueryTask,
    ComboBox,
    Memory,
    array,
    on
  ) {
BenHahn
New Contributor II

Thanks for clarifying to use the data-dojo-attach-point rather then Id. This didn't cross my way so far.

As expected aligning require/vars fixed the memory and array issues.

Now the only problem is getting the store back into the combobox. I still get the

TypeError: this.mySelect is undefined

0 Kudos
RobertScheitlin__GISP
MVP Emeritus

Ben,

   Do you have your widgets html named ExAcTlY "Widget.html" in your widgets folder?

0 Kudos
BenHahn
New Contributor II

Robert,

yes its properly named like that, I followed the sample naming conventions. The combobox is shown, but without any content.

0 Kudos
RobertScheitlin__GISP
MVP Emeritus

Ben,

   Can you zip-up your whole widget folder and attach it for me to look at?

0 Kudos