API 4.9 - How do I populate a <select> element in a Widget?

1299
2
Jump to solution
10-25-2018 09:50 AM
DavidSolari
Occasional Contributor III

I'm writing a simple widget that pulls unique values from a feature service's field and zooms to all features with the selected value. I figured the best way to do this was populate a HTML select element with the field values as options. Unfortunately, I've hit a roadblock and both of the detours I can think of don't seem to work.

  • If if map my choices to a function that returns option elements inside the render function (as shown here) only the first element is rendered
  • If I construct the select element myself I can't find a way to bind this to the renderer's version of the DOM (or however JSX rendering works) or set the inner HTML.

I've attached my app so you folks can get a better idea of what I'm trying to do. Do npm install then npm start to compile the app and get it running on localhost. The widget has a choices property with the fetched string values and a comboBox property with the constructed HTMLElements. Please let me know what you think, I'm not sure if i'm doing something wrong or if I've hit a bug and I'd like to know if 4.9 has it's own version of a select/combobox element I can use instead.

Thanks!

0 Kudos
1 Solution

Accepted Solutions
ToddAtkins
Occasional Contributor

In your main.tsx, remove the call for zoomToLayer.getChoices(). In your constructor for ZoomToUnique.tsx add this.getChoices() and remove this.renderNow() from the getChoices() method. Also add a renderable() decorator to your choices property and finally add a key tag to your select element. See below for a working ZoomToUnique.tsx:

import { subclass, declared, property } from "esri/core/accessorSupport/decorators";
import { renderable, tsx } from "esri/widgets/support/widget";
import Widget = require("esri/widgets/Widget");
import watchUtils = require("esri/core/watchUtils");
import FeatureLayer = require("esri/layers/FeatureLayer")
import FeatureLayerView = require("esri/views/layers/FeatureLayerView")
import QueryTask = require("esri/tasks/QueryTask")

@subclass("esri.widgets.ZoomToUnique")
export default class ZoomToUnique extends declared(Widget) {
     _currentChoice: string = "";

     @property()
     @renderable()
     choices: string[] = [];

     @property()
     comboBox: HTMLSelectElement = null;

     @property()
     layerView: FeatureLayerView

     @property()
     field: string

     constructor(layerView: FeatureLayerView, field: string, params?: Partial<__esri.WidgetProperties>) {
          super(params)
          this.layerView = layerView;
          this.field = field;
          this.getLayerChoices();
     }

     getLayerChoices() {
          const layer = this.layerView.layer;

          const queryParams = layer.createQuery();
          queryParams.returnDistinctValues = true;
          queryParams.returnGeometry = false;
          queryParams.where = "1=1";
          queryParams.outFields = [this.field];

          layer.queryFeatures(queryParams).then((features) => {
               this.comboBox = document.createElement("select");
               features.features.forEach(g => {
                    const choice = g.attributes[this.field];
                    this.choices.push(choice);
                    const option = document.createElement("option");
                    option.value = choice
                    option.text = choice
                    this.comboBox.add(option);
               });

               if (this._currentChoice === "") this._currentChoice = this.choices[0]
               // this.renderNow();
          });          
     }

     zoomToChoice() {
          if (this.comboBox == null) return;
          console.warn("Not implemented!")
     }

     render() {
          return (
               <div class="esri-widget">
                    <h3>{this.field}</h3>
                    <br />
                    <select>
                         {this.choices.map((c, idx) => <option key={idx} value={c}>{c}</option>)}
                    </select>
               </div>
          )
     }
}

View solution in original post

2 Replies
ToddAtkins
Occasional Contributor

In your main.tsx, remove the call for zoomToLayer.getChoices(). In your constructor for ZoomToUnique.tsx add this.getChoices() and remove this.renderNow() from the getChoices() method. Also add a renderable() decorator to your choices property and finally add a key tag to your select element. See below for a working ZoomToUnique.tsx:

import { subclass, declared, property } from "esri/core/accessorSupport/decorators";
import { renderable, tsx } from "esri/widgets/support/widget";
import Widget = require("esri/widgets/Widget");
import watchUtils = require("esri/core/watchUtils");
import FeatureLayer = require("esri/layers/FeatureLayer")
import FeatureLayerView = require("esri/views/layers/FeatureLayerView")
import QueryTask = require("esri/tasks/QueryTask")

@subclass("esri.widgets.ZoomToUnique")
export default class ZoomToUnique extends declared(Widget) {
     _currentChoice: string = "";

     @property()
     @renderable()
     choices: string[] = [];

     @property()
     comboBox: HTMLSelectElement = null;

     @property()
     layerView: FeatureLayerView

     @property()
     field: string

     constructor(layerView: FeatureLayerView, field: string, params?: Partial<__esri.WidgetProperties>) {
          super(params)
          this.layerView = layerView;
          this.field = field;
          this.getLayerChoices();
     }

     getLayerChoices() {
          const layer = this.layerView.layer;

          const queryParams = layer.createQuery();
          queryParams.returnDistinctValues = true;
          queryParams.returnGeometry = false;
          queryParams.where = "1=1";
          queryParams.outFields = [this.field];

          layer.queryFeatures(queryParams).then((features) => {
               this.comboBox = document.createElement("select");
               features.features.forEach(g => {
                    const choice = g.attributes[this.field];
                    this.choices.push(choice);
                    const option = document.createElement("option");
                    option.value = choice
                    option.text = choice
                    this.comboBox.add(option);
               });

               if (this._currentChoice === "") this._currentChoice = this.choices[0]
               // this.renderNow();
          });          
     }

     zoomToChoice() {
          if (this.comboBox == null) return;
          console.warn("Not implemented!")
     }

     render() {
          return (
               <div class="esri-widget">
                    <h3>{this.field}</h3>
                    <br />
                    <select>
                         {this.choices.map((c, idx) => <option key={idx} value={c}>{c}</option>)}
                    </select>
               </div>
          )
     }
}
DavidSolari
Occasional Contributor III

Thanks Todd, it looks like my lack of a key attribute in the options was bungling it up. As a quick follow-up, do you have any resources on working with widget state in the API? My next step is to change the extents when the select box's onchange event is fired and I'm not sure how to get the element's state (or manage it myself like standard react widgets).

0 Kudos