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.
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!
Solved! Go to Solution.
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>
)
}
}
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>
)
}
}
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).