I have 3 layers (2 point and 1 line) that coincide, let's call them A, B, and C. I have tried several things to get the popup order to be A (1 of 3), B,(2 of 3), C (3 of 3): ordering the layers in Content with A on top, then B, then C; renaming them so that they are in alphabetical order; creating copies of the layers in a specific order. No matter what I try, the popups appear in the order B, A, C. They don't seem to order in the order they appear in Content or the Legend, alphabetically, or in order of creation/modification. Is there any way to force one layer's popup to display 1st, or specify the order in which the popups display?
Solved! Go to Solution.
I just stumbled across this post, has this been added as an ArcGIS Idea yet? Someone should add it there, perhaps that would get more traction on a solution. If it has been posted, please share the link so we can upvote if, if not, any volunteers 😉 ?
Yeah it was added as an ArcGIS Idea... back in 2012!
I have created a Dashboard for our Public Safety Teams for Emergency Operations, such as the current Winter Storm Severe Weather Event we are experiencing. Our Field Ops crews perform various tasks at specific locations, such as Plow Snow, Sand Road, Close and Reopen Road. It would be a very helpful for those teams to review the activities performed based on the entry date in chronological order. With a more severe event, there can be 20-30 activities at the same point. Without the functionality to order the pop-ups, it is very difficult to follow the order of work performed at each location and kinda makes the whole thing much less useful.
ESRI, please consider adding this functionality. There is clearly a big need across multiple different types of organizations for many important functions!
Super Thanks.
No, I have not. And, this has been an issue since the flexviewer days.
It appears as if this one will never get addressed, so the solution is to just accept it.
R_
Well it has been a while. I’ll pile on to the list. I get dinged literally every demo I do as to the seemingly random (I understand it’s not) selection order of features. I’ve been in the biz 30 years and still can’t get used to customers eyes glazing over when I discuss that “performance” drives the constraint. And then of course this from customers, “but aren’t computers super fast these days? A lot faster than 10 years ago?” From a practical sense perhaps feature type could determine order of selection. A user being able to select points, then lines and then areas (or other combo) would be a reasonable compromise. I’ll add a pretty please to all this. Thank you though for an amazing product regardless.
I asked about this yesterday during a Map Viewer discussion at the ESRI IUC. There is apparently no intention to fix this despite the numerous users clamoring for it. They said it was too technologically challenging and one layer might bog down the map. I did not have a chance to respond to that unfortunately but with all the posts here, they already know we want it and don't care.
Edit: I relistened to what they said and, to be fair, they didn't say no intention but my question was will it be soon and the response was basically "not close." So they probably do care but are finding it too hard right now which doesn't make me much happier.
I sure hope so because it seems so basic a need for most any map with more than one layer.
Hello this question says solved but obviously it is not. In the year of our lord 2023 I have come up with a stop-gap solution.
Hi Cameron - I don't think ESRI has any plans to add this as an enhancement. WAB/3.X will be retired in the next year. You can add the "reorderPopupFeatures" and "sortFeatures" functions shown below to the PopupManager.js file that is bundled with the web appbuilder source code. Make sure to add "this.reorderPopupFeatures();" at the end of the init function. The example below (also attached) is the entire PopupManager.js file that gets bundled with WAB. It's from version 2.19.
FYI - I believe ESRI runs an identify operation by service to get the popup features when the user clicks on the map. All of those identify requests are done asynchronously and there is no guarantee what order they come back in. ESRI could run all of that synchronously but that could create lag/bottlenecks. The example presented in this response simply sorts the features array by layer order.
///////////////////////////////////////////////////////////////////////////
// Copyright © 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',
'dojo/_base/lang',
'dojo/_base/html',
'dojo/topic',
'dojo/on',
'dojo/query',
'./FeatureActionManager',
'./utils',
'./dijit/FeatureActionPopupMenu',
'./RelatedRecordsPopupProjector',
'dojo/_base/array',
'dojo/promise/all',
'dojo/Deferred',
'dojo/aspect',
'./LayerStructure'], function(declare, lang, html, topic, on, query, FeatureActionManager,
jimuUtils, PopupMenu, RelatedRecordsPopupProjector,array, all, Deferred, aspect, LayerStructure) {
var instance = null;
var clazz = declare(null, {
mapManager: null,
// popupUnion = {
// mobile: is mobile popup of map,
// bigScreen: is popup of map
// };
popupUnion: null,
_relatedRecordsPopupProjector: null,
constructor: function(options) {
lang.mixin(this, options);
this.popupMenu = PopupMenu.getInstance();
this.isInited = false;
this.featureActionManager = FeatureActionManager.getInstance();
topic.subscribe("mapLoaded", lang.hitch(this, this.onMapLoadedOrChanged));
topic.subscribe("mapChanged", lang.hitch(this, this.onMapLoadedOrChanged));
topic.subscribe("appConfigChanged", lang.hitch(this, this._onAppConfigChanged));
topic.subscribe("widgetsActionsRegistered", lang.hitch(this, this._onWidgetsActionsRegistered));
},
init: function() {
this.popupUnion = this.mapManager.getMapInfoWindow();
if(!this.popupUnion.bigScreen || !this.popupUnion.mobile ||
!this.popupUnion.bigScreen.domNode || !this.popupUnion.mobile.domNode){
return;
}
if(!this.isInited){
this._createPopupMenuButton();
this._bindSelectionChangeEvent();
this.isInited = true;
}
this.reorderPopupFeatures();
},
reorderPopupFeatures: function() {
var that = this;
this.layerStructrue = LayerStructure.getInstance();
// intercept the popup.setFeatures method, reorder feautres and recall original popup.setFeatures method.
aspect.around(this.popupUnion.bigScreen, "setFeatures", function(originalSetFeatures) {
return function(featuresArg, options) {
var convertedFeatureDefs = [];
// having to consider that there are two categories of features parameter can be received.
// 1, feature array
// 2, deferred array
array.forEach(featuresArg, function(featureOrDef) {
if(featureOrDef.declaredClass === "esri.Graphic") {
// it is a feature
var def = new Deferred();
def.resolve([featureOrDef]);
convertedFeatureDefs.push(def);
} else {
// it is a deferred
convertedFeatureDefs.push(featureOrDef);
}
});
this.clearFeatures();
all(convertedFeatureDefs).then(lang.hitch(this, function(results) {
var features = [];
array.forEach(results, function(result) {
array.forEach(result, function(feature) {
if(feature) {
// remove duplicated features
var featureAlreadyExist = array.some(features, function(f) {
if(feature === f) {
return true;
} else {
return false;
}
});
if(!featureAlreadyExist) {
features.push(feature);
}
}
});
}, this);
// sort features by layers order.
var orderedFeatures = that.sortFeatures(features);
// recall origin setFeatures.
originalSetFeatures.apply(this, [orderedFeatures, options]);
}));
};
});
// js-api will using options.closetFirst paramether to show popup by default when clicking the map.
// this paramether will impact features order, so having to deny it.
// that means the closeFirst parameter will never tack effect for show popup in the WAB environment.
aspect.around(this.popupUnion.bigScreen, 'show', function(originalShow) {
return function(location/*, options*/) {
originalShow.apply(this, [location, false]);
};
});
aspect.around(this.popupUnion.mobile, 'show', function(originalShow) {
return function(location/*, options*/) {
originalShow.apply(this, [location, false]);
};
});
},
// accordiing to layers order to sort features
sortFeatures: function(features) {
if(!this.layerOrderPriority) {
// according to layers order to define a priority object, using to sort features.
this.layerOrderPriority = {};
var priority = 1;
this.layerStructrue.traversal(lang.hitch(this, function(layerNode) {
this.layerOrderPriority[layerNode.id] = priority++;
}));
}
// update this.layerOrderPriority if the layer structure has been changed.
if(!this.layerStructureChangeHandler) {
this.layerStructureChangeHandler = this.layerStructrue.on('structure-change', lang.hitch(this, function() {
this.layerOrderPriority = null;
}));
}
array.forEach(features, function(feature) {
if(feature && feature.getLayer) {
feature._priority = this.layerOrderPriority[feature.getLayer().id];
} else {
feature._priority = 100000;
}
}, this);
// features.sort(function(featureA, featureB) {
// return featureA._priority > featureB._priority;
// });
features.sort(function(featureA, featureB) {
if(featureA._priority > featureB._priority)
{ return 1;}
else
{ return -1;}
return 0;
});
return features;
},
_createPopupMenuButton: function(){
if(this.popupMenuButtonDesktop) {
html.destroy(this.popupMenuButtonDesktop);
}
if(this.popupMenuButtonMobile) {
html.destroy(this.popupMenuButtonMobile);
}
this.popupMenuButtonDesktop = html.create('span', {
'class': 'popup-menu-button'
}, query(".actionList", this.popupUnion.bigScreen.domNode)[0]);
var mobileActionListNode =
query("div.esriMobileInfoView.esriMobilePopupInfoView .esriMobileInfoViewItem").parent()[0];
var mobileViewItem = html.create('div', {
'class': 'esriMobileInfoViewItem'
}, mobileActionListNode);
this.popupMenuButtonMobile = html.create('span', {
'class': 'popup-menu-button'
}, mobileViewItem);
on(this.popupMenuButtonMobile, 'click', lang.hitch(this, this._onPopupMenuButtonClick));
on(this.popupMenuButtonDesktop, 'click', lang.hitch(this, this._onPopupMenuButtonClick));
},
_onPopupMenuButtonClick: function(evt){
var position = html.position(evt.target);
if(this.menuActionsOfSelectedFeature) {
this.popupMenu.setActions(this.menuActionsOfSelectedFeature);
}
this.popupMenu.show(position);
},
_bindSelectionChangeEvent: function(){
on(this.popupUnion.bigScreen, "show,selection-change", lang.hitch(this, this._onSelectionChange));
on(this.popupUnion.mobile, "show,selection-change", lang.hitch(this, this._onSelectionChange));
},
_onSelectionChange: function(evt){
this.selectedFeature = evt.target.getSelectedFeature();
//this.selectedFeature = this.mapManager.map.infoWindow.getSelectedFeature();
if(!this.selectedFeature){
this._disablePopupMenu();
return;
}
node = dojo.query("[dojoattachpoint=\"_title\"]", this.popupUnion.bigScreen.domNode)[0];
node2 = dojo.query("[dojoattachpoint=\"_description\"]", this.popupUnion.bigScreen.domNode)[0];
this.initPopupMenu([this.selectedFeature]);
var selectedFeatureLayer = this.selectedFeature.getLayer();
var hasInfoTemplate = this.selectedFeature.infoTemplate ||
(selectedFeatureLayer && selectedFeatureLayer.infoTemplate);
if(hasInfoTemplate) {
//this._createRelatedRecordsPopupProjector(this.selectedFeature);
}
},
_disablePopupMenu: function() {
html.addClass(this.popupMenuButtonDesktop, 'disabled');
html.addClass(this.popupMenuButtonMobile, 'disabled');
},
_enablePopupMenu: function() {
html.removeClass(this.popupMenuButtonDesktop, 'disabled');
html.removeClass(this.popupMenuButtonMobile, 'disabled');
},
// public method, can be called from outside.
initPopupMenu: function(features){
if(!features) {
this._disablePopupMenu();
this.popupMenu.setActions([]);
return;
}
var featureSet = jimuUtils.toFeatureSet(features);
this.featureActionManager.getSupportedActions(featureSet).then(lang.hitch(this, function(actions){
var excludeActions = ['ZoomTo', 'ShowPopup', 'Flash', 'ExportToCSV',
'ExportToFeatureCollection', 'ExportToGeoJSON', 'ShowRelatedRecords',
'SaveToMyContent', 'CreateLayer'];
var popupActions = actions.filter(lang.hitch(this, function(action){
return excludeActions.indexOf(action.name) < 0 ;
}));
if(popupActions.length === 0){
this._disablePopupMenu();
}else{
this._enablePopupMenu();
}
var menuActions = popupActions.map(lang.hitch(this, function(action){
//action.data = jimuUtils.toFeatureSet(feature);
action.data = featureSet;
return action;
}));
this.menuActionsOfSelectedFeature = menuActions;
this.popupMenu.setActions(menuActions);
}));
},
/******************************
* Events
******************************/
onMapLoadedOrChanged: function() {
this.isInited = false;
this.init();
},
_onAppConfigChanged: function() {
if(this.popupUnion) {
if(this.popupUnion.bigScreen && this.popupUnion.bigScreen.hide) {
this.popupUnion.bigScreen.hide();
this.popupMenu.hide();
}
if(this.popupUnion.mobile && this.popupUnion.mobile.hide) {
this.popupUnion.mobile.hide();
this.popupMenu.hide();
}
}
},
_onWidgetsActionsRegistered: function(){
//to init actions
this.init();
},
/**********************************
* Methods for show related records
**********************************/
_createRelatedRecordsPopupProjector: function(selectedFeature) {
try {
if(this._relatedRecordsPopupProjector &&
this._relatedRecordsPopupProjector.domNode) {
this._relatedRecordsPopupProjector.destroy();
this._relatedRecordsPopupProjector = null;
}
//var refDomNode = query(".esriViewPopup", this.popupUnion.bigScreen.domNode)[0];
this._relatedRecordsPopupProjector = new RelatedRecordsPopupProjector({
originalFeature: selectedFeature,
//refDomNode: refDomNode,
popup: this.mapManager.map.infoWindow,
popupManager: this
});
} catch(err) {
console.warn(err.message);
}
}
});
clazz.getInstance = function(mapManager) {
if (instance === null) {
instance = new clazz({
mapManager: mapManager
});
}
return instance;
};
return clazz;
});