How can I change the order of popups on an ArcGIS Online Map

25962
48
Jump to solution
10-03-2014 01:15 PM
BrianShepard
Occasional Contributor

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?

Tags (2)
48 Replies
GregReinecke
New Contributor III

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. 

GrantWilcox
New Contributor II

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.

TiffanyTuro
New Contributor III
I think at some point it will be figured out, but for now what a time waster! Team frustration for now. Thanks for asking though!
0 Kudos
GrantWilcox
New Contributor II

I sure hope so because it seems so basic a need for most any map with more than one layer.

0 Kudos
CameronKenworthy
New Contributor

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.

var timer = 0
if(timer < 100) {
  timer += 1
}
 
Add this as an expression to the fields list description or whatever you want. Adjust the timer accordingly to get each of your popups to load in the order you want.
JamesMadden1
Occasional Contributor

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;
  });



 

0 Kudos
WWUMap
by
New Contributor III

Just echoing the chorus - not being able to control pop-up order is a drag. And TOC order would be Far better than no control. Processing speed isn't the same in 2023 as it was in 2014 when this is was first posted (!).

JoelBennett
MVP Regular Contributor

Happy 2024...here's some good news to kick off the new year.  ESRI has indicated they're updating the underlying framework (the ArcGIS Maps SDK for JavaScript) to display features in layer order in its next release (targeted for February of this year).  See also this page, although below is the relevant text for future reference:

Feature order in the Popup and Features widget

The Popup and Features widgets now display features from multiple layers in the order they are displayed in the map. This means that the features of the topmost layer in the map will appear first, followed by the features of the next layer, and so on. In previous versions, the features were displayed in the order they were returned from the server.

JCGuarneri
Occasional Contributor II

I know this is a very old post, but here's my workaround for the most recent versions of Enterprise (11.1+) and for AGOL. In Arcade, you can use Now(), DateDiff(), and a while loop to force a given popup to take longer.

This is a really jankety solution, but it works. I recommend using it only if really necessary. It would also work best if there's only one or two popups in particular that you really want to move further down in the pile. Anything more than that would be really hairy to manage well.

Here's the code to do this:

//set target delay in milliseconds
var delay = 1000;

//get start time and initiate the elapsed time variable
var startTime = Now();
var time2 = Now();
var elapsed = DateDiff(time2,startTime);
//while elapsed time is less than target time, check elapsed time until target is met
while (elapsed < delay) {
          elapsed = DateDiff(Now(),startTime);
    
}
//return value for testing. Can use functional code instead
return elapsed;

 

0 Kudos