ListView Widget

384
6
09-19-2019 06:44 AM
Highlighted
New Contributor

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');
}
});
});@
Reply
0 Kudos
6 Replies
Highlighted
MVP Esteemed Contributor

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];
Reply
0 Kudos
Highlighted
by
New Contributor II

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!

Reply
0 Kudos
Highlighted
New Contributor

 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.

Reply
0 Kudos
Highlighted
MVP Esteemed Contributor

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;
}
Reply
0 Kudos
Highlighted
New Contributor

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
});
}
});
});
Reply
0 Kudos
Highlighted
MVP Esteemed Contributor

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.

Reply
0 Kudos