Hi All or Robert Scheitlin, GISP ,
I am needing help with the ListView widget. We are needing it to display only unique values from a feature layer field as well as filter and zoom to from the selected list item. here is my widget.js code. I changed the outfield to the config.titleField and was able to pass only unique values to the list however this changed the click function. See widget.js below I also attached the full zip.
define(['dojo/_base/declare',
'jimu/BaseWidget',
'dojo/_base/lang',
'dojo/Deferred',
'dgrid/OnDemandList',
'dgrid/Selection',
"dojo/store/Memory",
"esri/tasks/query",
"esri/symbols/SimpleMarkerSymbol",
"esri/symbols/SimpleLineSymbol",
"esri/symbols/SimpleFillSymbol",
"dojo/on"
],
function(declare, BaseWidget,
lang, Deferred,
OnDemandList, Selection, Memory,
Query, SimpleMarkerSymbol, SimpleLineSymbol, SimpleFillSymbol, on) {
//To create a widget, you need to derive from BaseWidget.
return declare([BaseWidget], {
// DemoWidget code goes here
//please note that this property is be set by the framework when widget is loaded.
//templateString: template,
baseClass: 'jimu-widget-listview',
featureLayer: null,
list: null,
postCreate: function() {
this.inherited(arguments);
console.log('postCreate');
this.headerNode.innerHTML = this.config.widgetHeaderText;
this.featureLayer = this.map.getLayer(this.config.layerId);
var highlightSymbol;
switch(this.featureLayer.geometryType) {
case 'esriGeometryPoint':
highlightSymbol = new SimpleMarkerSymbol(SimpleMarkerSymbol.STYLE_CIRCLE, 20, null, '#e74c3c');
break;
case 'esriGeometryPolyline':
highlightSymbol = new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID, '#e74c3c', 3);
break;
case 'esriGeometryPolygon':
highlightSymbol = new SimpleFillSymbol(SimpleFillSymbol.STYLE_SOLID,
new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID, '#fff', 2),
'#e74c3c');
break;
}
this.featureLayer.setSelectionSymbol(highlightSymbol);
this.createList();
},
startup: function() {
this.inherited(arguments);
console.log('startup');
},
_createListItem: function(featureObj) {
var listItemRoot = document.createElement('DIV');
listItemRoot.className = 'list-item';
if(featureObj) {
var thumbnailImgWrapper, thumbnailImg, listItemTitle;
// Create thumbnail
if(featureObj.thumbnailImg) {
thumbnailImgWrapper = document.createElement('div');
thumbnailImgWrapper.className = 'thumbnail-wrapper';
thumbnailImg = document.createElement('img');
thumbnailImg.src = featureObj.thumbnailImg;
thumbnailImgWrapper.appendChild(thumbnailImg);
listItemRoot.appendChild(thumbnailImgWrapper);
}
// Create title
if(featureObj.title && typeof featureObj.title === 'string') {
listItemTitle = document.createElement('H4');
listItemTitle.innerHTML = featureObj.title;
listItemRoot.appendChild(listItemTitle);
if(thumbnailImg)
thumbnailImg.alt = featureObj.title;
}
} else {
listItemRoot.innerHTML = 'NO DATA AVAILABLE';
}
return listItemRoot;
},
createList: function() {
this.getDataStore().then(lang.hitch(this, function(datastore) {
var list = new (declare([OnDemandList, Selection]))({
'store': datastore,
'selectionMode': 'single',
'renderRow': lang.hitch(this, function (object, options) {
return this._createListItem(object);
})
}, this.ListNode);
list.startup();
list.on('.dgrid-row:click', lang.hitch(this, function(evt) {
var row = list.row(evt);
var query = new Query();
query.objectIds = [row.data.id];
this.featureLayer.selectFeatures(query, esri.layers.FeatureLayer.SELECTION_NEW, lang.hitch(this, function(result) {
if (result.length) {
var feature = result[0],
newMapCenter,
geometry = feature.geometry,
extent = geometry.getExtent(),
shape = feature.getShape();
if(extent && extent.getCenter) {
newMapCenter = extent.getCenter(); // polygon & polyline
} else {
newMapCenter = geometry; // point
}
this.map.centerAt(newMapCenter); // move to the feature
if(shape) shape.moveToFront(); // move the feature to front
}
}));
}));
}));
},
getDataStore: function() {
var def = new Deferred();
// Query features
var query = new Query();
query.returnGeometry = false;
query.outFields = [this.config.titleField];
query.returnDistinctValues = true;
query.where = '1=1';
this.featureLayer.queryFeatures(query, lang.hitch(this, function(featureSet) {
// console.log(featureSet);
var featureSetRemapped = [];
for(var index in featureSet.features) {
var feature = featureSet.features[index];
featureSetRemapped.push({
'id': feature.attributes[this.featureLayer.objectIdField],
'title': feature.attributes[this.config.titleField],
'thumbnailImg': feature.attributes[this.config.thumbnailField]
});
}
def.resolve(new Memory({
data: featureSetRemapped
}));
}));
return def;
},
onOpen: function(){
console.log('onOpen');
},
onClose: function(){
console.log('onClose');
},
onMinimize: function(){
console.log('onMinimize');
},
onMaximize: function(){
console.log('onMaximize');
},
onSignIn: function(credential){
/* jshint unused:false*/
console.log('onSignIn');
},
onSignOut: function(){
console.log('onSignOut');
}
});
});@
Richard,
You were not zooming because you were not including the ObjectId field in the getDataStore.
getDataStore: function () {
var def = new Deferred();
// Query features
var query = new Query();
query.returnGeometry = false;
query.outFields = [this.config.titleField, this.featureLayer.objectIdField];
Hi Robert,
When using the ListView widget, how can we get the map to zoom directly to the selected feature (similar to how it's done in the search widget). I tried adding the modification you suggested above, but the map still does not zoom to the feature. Thanks!
Eric Barr John Adams That did not work, we are needing unique values only and filter and zoom on those unique values. Example feature has 25 features 10 are in City1, 5 are in City2, and 10 are in City3. From a Field called City, We would like the List view to show only 1 of each city and when onclick filter and zoom to only that city that was selected. This functionality is currently in Operations Dashboard, and Experience Builder. However we need this functionality in Web App Builder and the ListView is the closest widget I could find. Thanks for your help.
OK. This is what you are needing then:
getDataStore: function () {
var def = new Deferred();
// Query features
var query = new Query();
query.returnGeometry = false;
query.outFields = ['*'];
query.where = '1=1';
var uniqueVals = [];
this.featureLayer.queryFeatures(query, lang.hitch(this, function (featureSet) {
// console.log(featureSet);
var featureSetRemapped = [];
for (var index in featureSet.features) {
var feature = featureSet.features[index];
// do not add the result if that title already exists.
if(uniqueVals.indexOf(feature.attributes[this.config.titleField])<0){
featureSetRemapped.push({
'id': feature.attributes[this.featureLayer.objectIdField],
'title': feature.attributes[this.config.titleField],
'thumbnailImg': feature.attributes[this.config.thumbnailField]
});
uniqueVals.push(feature.attributes[this.config.titleField]);
}
}
def.resolve(new Memory({
data: featureSetRemapped
}));
}));
return def;
}
That got us part of the way thank you. After some testing we found out that we needed to pass the classname and tagname in seperate var's. When we are finished we will be Sharing the widget for the community to use. We do need some more help though. Do you know how to use the datasource provider widget. We are wanting to add our widget to the extra datasource tab and cannot find any good documentation on it. https://developers.arcgis.com/web-appbuilder/guide/provide-and-consume-data-source.htm here is what we have in the settings.js
/////////////////////////////////////////////////////////////////////////// // Copyright © 2014 Esri. All Rights Reserved. // // Licensed under the Apache License Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /////////////////////////////////////////////////////////////////////////// define([ 'dojo/_base/declare', 'jimu/BaseWidgetSetting', 'dojo/_base/lang', 'dojo/_base/array', 'dijit/_WidgetsInTemplateMixin', 'dijit/form/Select', 'jimu/utils', 'jimu/LayerInfos/LayerInfos' ], function(declare, BaseWidgetSetting, lang, array, _WidgetsInTemplateMixin, Select, jimUtils, LayerInfos) { return declare([BaseWidgetSetting, _WidgetsInTemplateMixin], { baseClass: 'jimu-widget-listview-setting', postCreate: function(){ //the config object is passed in this.setConfig(this.config); }, setConfig: function(config){ // Update header text this.headerTextNode.value = config.widgetHeaderText; // Get all feature layers from the map LayerInfos.getInstance(this.map, this.map.itemInfo) .then(lang.hitch(this, function(layerInfosObj) { var infos = layerInfosObj.getLayerInfoArray(); var options = []; array.forEach(infos, function(info) { if(info.originOperLayer.layerType === 'ArcGISFeatureLayer') { options.push({ label: info.title, value: info.id }); } }); // Populate select options this.layerSelect.set('options', options); this.layerSelect.on('change', lang.hitch(this, function(value) { var selectedLayer = layerInfosObj.getLayerInfoById(value); if(selectedLayer) { var fieldOptions = array.map(selectedLayer.layerObject.fields, function(field) { return { label: field.alias || field.name, value: field.name } }); this.titleSelect.set('options', fieldOptions); } })); })); }, getConfig: function(){ //WAB will get config object through this method return { widgetHeaderText: this.headerTextNode.value, layerId: this.layerSelect.get('value'), titleField: this.titleSelect.get('value') }; }, getDataSources: function(){ var filters = this.getConfig(); var layerDefinition = this.layerSelect.toJson().layerDefinition; return array.map(filters, function(){ return { id: this.layerSelect.get('value'), type: 'Features', label: this.titleSelect.get('value'), dataSchema: jimuUtils.getDataSchemaFromLayerDefinition(layerDefinition) }; }, this); } }); });
and in the Widget.js
define(['dojo/_base/declare', 'dojo/_base/html', 'dojo/_base/array', 'dojo/on', 'jimu/BaseWidget', 'dojo/_base/lang', 'dojo/Deferred', 'dgrid/OnDemandList', 'dgrid/Selection', "dojo/store/Memory", "esri/tasks/query", "esri/symbols/SimpleMarkerSymbol", "esri/symbols/SimpleLineSymbol", "esri/symbols/SimpleFillSymbol", "jimu/dijit/Filter", 'esri/tasks/QueryTask' ], function(declare, html, array, on, BaseWidget, lang, Deferred, OnDemandList, Selection, Memory, Query, SimpleMarkerSymbol, SimpleLineSymbol, SimpleFillSymbol, Filter, QueryTask) { //To create a widget, you need to derive from BaseWidget. return declare([BaseWidget], { baseClass: 'jimu-widget-listview', postCreate: function() { this.inherited(arguments); this.headerNode.innerHTML = this.config.widgetHeaderText; this.featureLayer = this.map.getLayer(this.config.layerId); var highlightSymbol; switch(this.featureLayer.geometryType) { case 'esriGeometryPoint': highlightSymbol = new SimpleMarkerSymbol(SimpleMarkerSymbol.STYLE_CIRCLE, 20, new SimpleLineSymbol("solid", 'rgba(255,255,255,1)', 2), 'rgba(255,255,255,0)'); break; case 'esriGeometryPolyline': highlightSymbol = new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID, '#e74c3c', 3); break; case 'esriGeometryPolygon': highlightSymbol = new SimpleFillSymbol(SimpleFillSymbol.STYLE_SOLID, new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID, '#fff', 2), '#e74c3c'); break; } this.featureLayer.setSelectionSymbol(highlightSymbol); this.createList(); }, getDataStore: function () { var def = new Deferred(); // Query features var query = new Query(); query.returnGeometry = false; query.outFields = ['*']; query.where = '1=1'; var uniqueVals = []; this.featureLayer.queryFeatures(query, lang.hitch(this, function (featureSet) { // console.log(featureSet); var featureSetRemapped = []; for (var index in featureSet.features) { var feature = featureSet.features[index]; // do not add the result if that title already exists. if(uniqueVals.indexOf(feature.attributes[this.config.titleField])<0){ featureSetRemapped.push({ 'id': feature.attributes[this.featureLayer.objectIdField], 'title': feature.attributes[this.config.titleField], 'thumbnailImg': feature.attributes[this.config.thumbnailField] }); uniqueVals.push(feature.attributes[this.config.titleField]); } } def.resolve(new Memory({ data: featureSetRemapped })); })); return def; }, createList: function() { this.getDataStore().then(lang.hitch(this, function(datastore) { var list = new(declare([OnDemandList, Selection]))({ 'store': datastore, 'selectionMode': 'single', 'renderRow': lang.hitch(this, function(object, options) { return this._createListItem(object); }) }, this.ListNode); list.startup(); list.on('.dgrid-row:click', lang.hitch(this, function(evt) { var row = list.row(evt); var query = new Query(); //get element that is selected from the class of dgrid-selected first selection var selectedlist = document.getElementsByClassName("dgrid-selected")[0]; //pass the selected list values to the tag name var selectedvalues = selectedlist.getElementsByTagName("h4")[0].innerHTML; query.where = this.config.titleField + "='" + selectedvalues + "'"; this.featureLayer.setDefinitionExpression(query.where); this.featureLayer.selectFeatures(query, esri.layers.FeatureLayer.SELECTION_NEW, lang.hitch(this, function(result) { if(result.length) { var feature = result[0], newMapCenter, geometry = feature.geometry, extent = geometry.getExtent(), shape = feature.getShape(); if(extent && extent.getCenter) { newMapCenter = extent.getCenter(); // polygon & polyline } else { newMapCenter = geometry; // point } this.map.centerAt(newMapCenter); // move to the feature if(shape) shape.moveToFront(); // move the feature to front } })); })); })); }, _createListItem: function(featureObj) { var listItemRoot = document.createElement('DIV'); listItemRoot.className = 'list-item'; if(featureObj) { var listItemTitle; // Create title if(featureObj.title && typeof featureObj.title === 'string') { listItemTitle = document.createElement('H4'); listItemTitle.innerHTML = featureObj.title; listItemRoot.appendChild(listItemTitle); } } else { listItemRoot.innerHTML = 'NO DATA AVAILABLE'; } return listItemRoot; }, _dataprovider: function(args){ var taskIndex = args.taskIndex; var features = args.features; this.updateDataSourceData(taskIndex, { features: features }); } }); });
Richard,
1. What is your goal here?
There is only one widget (infographics) currently that can consume extra data sources.
2. So are you developing another widget that will consume this extra data from your widget?
From what I see in your widget and settings code you are properly providing your data source data.