Popup overflows on first click

398
2
Jump to solution
01-18-2024 02:27 PM
Clinton_Lunn
New Contributor II

Hi all,

I am experiencing an issue where on the first click after a page load, (with default settings) the popup always opens above the click location, causing it to overflow the div and end up out of view. Every click after first seems to reliably work and doesn't collide with the edge of the map. Is this a known issue or am I missing a way to avoid this without docking the popup or something? I am able to reproduce this problem in the sample code snippets. https://developers.arcgis.com/javascript/latest/sample-code/intro-popup/

See below:

first click after page load

Clinton_Lunn_0-1705616499958.png


second click (and beyond) after page load

Clinton_Lunn_2-1705616746655.png

 

 

1 Solution

Accepted Solutions
JoelBennett
MVP Regular Contributor

I've found the default Popup positioning tends to be unreliable, but fortunately you can override the default behavior by assigning a function to the alignment property.  I've modified the aforementioned sample below with what my apps use.  In particular, I added lines 52-80, and also the reference to reactiveUtils on line 35.

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
    <title>Intro to popups | Sample | ArcGIS Maps SDK for JavaScript 4.28</title>

    <style>
      html,
      body,
      #viewDiv {
        padding: 0;
        margin: 0;
        height: 100%;
        width: 100%;
      }

      #instruction {
        z-index: 99;
        position: absolute;
        top: 15px;
        left: 50%;
        padding: 5px;
        margin-left: -175px;
        height: 30px;
        width: 355px;
        background: rgba(25, 25, 25, 0.8);
        color: white;
      }
    </style>

    <link rel="stylesheet" href="https://js.arcgis.com/4.28/esri/themes/light/main.css" />
    <script src="https://js.arcgis.com/4.28/"></script>

    <script>
      require(["esri/rest/locator", "esri/Map", "esri/views/MapView", "esri/core/reactiveUtils"], (locator, Map, MapView, reactiveUtils) => {
        // Set up a locator url using the world geocoding service
        const locatorUrl = "https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer";

        // Create the Map
        const map = new Map({
          basemap: "streets-navigation-vector"
        });

        // Create the MapView
        const view = new MapView({
          container: "viewDiv",
          map: map,
          center: [-71.6899, 43.7598],
          zoom: 12
        });

        reactiveUtils.watch(() => view.popup?.id, function() {
    			view.popup.alignment = function() {
    				var location = this.location;
    				var view = this.view;
    
    				if ((location) && (view)) {
    					var viewPoint = view.toScreen(location);
    					var y2 = view.height / 2;
    					var x2 = view.width / 3;
    					var x3 = x2 * 2;
    
    					if (viewPoint.y >= y2) {
    						if (viewPoint.x < x2)
    							return "top-right";
    						else if (viewPoint.x > x3)
    							return "top-left";
    					} else {
    						if (viewPoint.x < x2)
    							return "bottom-right";
    						else if (viewPoint.x > x3)
    							return "bottom-left";
    						else
    							return "bottom-center";
    					}
    				}
    
    				return "top-center";
    			};
        });

        /*******************************************************************
         * This click event sets generic content on the popup not tied to
         * a layer, graphic, or popupTemplate. The location of the point is
         * used as input to a reverse geocode method and the resulting
         * address is printed to the popup content.
         *******************************************************************/
        view.popupEnabled = false;
        view.on("click", (event) => {
          // Get the coordinates of the click on the view
          const lat = Math.round(event.mapPoint.latitude * 1000) / 1000;
          const lon = Math.round(event.mapPoint.longitude * 1000) / 1000;

          view.openPopup({
            // Set the popup's title to the coordinates of the location
            title: "Reverse geocode: [" + lon + ", " + lat + "]",
            location: event.mapPoint // Set the location of the popup to the clicked location
          });

          const params = {
            location: event.mapPoint
          };

          // Display the popup
          // Execute a reverse geocode using the clicked location
          locator
            .locationToAddress(locatorUrl, params)
            .then((response) => {
              // If an address is successfully found, show it in the popup's content
              view.popup.content = response.address;
            })
            .catch(() => {
              // If the promise fails and no result is found, show a generic message
              view.popup.content = "No address was found for this location";
            });
        });
      });
    </script>
  </head>

  <body>
    <div id="viewDiv"></div>
    <div id="instruction" class="esri-widget">Click any location on the map to see its street address</div>
  </body>
</html>

 

View solution in original post

2 Replies
JoelBennett
MVP Regular Contributor

I've found the default Popup positioning tends to be unreliable, but fortunately you can override the default behavior by assigning a function to the alignment property.  I've modified the aforementioned sample below with what my apps use.  In particular, I added lines 52-80, and also the reference to reactiveUtils on line 35.

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
    <title>Intro to popups | Sample | ArcGIS Maps SDK for JavaScript 4.28</title>

    <style>
      html,
      body,
      #viewDiv {
        padding: 0;
        margin: 0;
        height: 100%;
        width: 100%;
      }

      #instruction {
        z-index: 99;
        position: absolute;
        top: 15px;
        left: 50%;
        padding: 5px;
        margin-left: -175px;
        height: 30px;
        width: 355px;
        background: rgba(25, 25, 25, 0.8);
        color: white;
      }
    </style>

    <link rel="stylesheet" href="https://js.arcgis.com/4.28/esri/themes/light/main.css" />
    <script src="https://js.arcgis.com/4.28/"></script>

    <script>
      require(["esri/rest/locator", "esri/Map", "esri/views/MapView", "esri/core/reactiveUtils"], (locator, Map, MapView, reactiveUtils) => {
        // Set up a locator url using the world geocoding service
        const locatorUrl = "https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer";

        // Create the Map
        const map = new Map({
          basemap: "streets-navigation-vector"
        });

        // Create the MapView
        const view = new MapView({
          container: "viewDiv",
          map: map,
          center: [-71.6899, 43.7598],
          zoom: 12
        });

        reactiveUtils.watch(() => view.popup?.id, function() {
    			view.popup.alignment = function() {
    				var location = this.location;
    				var view = this.view;
    
    				if ((location) && (view)) {
    					var viewPoint = view.toScreen(location);
    					var y2 = view.height / 2;
    					var x2 = view.width / 3;
    					var x3 = x2 * 2;
    
    					if (viewPoint.y >= y2) {
    						if (viewPoint.x < x2)
    							return "top-right";
    						else if (viewPoint.x > x3)
    							return "top-left";
    					} else {
    						if (viewPoint.x < x2)
    							return "bottom-right";
    						else if (viewPoint.x > x3)
    							return "bottom-left";
    						else
    							return "bottom-center";
    					}
    				}
    
    				return "top-center";
    			};
        });

        /*******************************************************************
         * This click event sets generic content on the popup not tied to
         * a layer, graphic, or popupTemplate. The location of the point is
         * used as input to a reverse geocode method and the resulting
         * address is printed to the popup content.
         *******************************************************************/
        view.popupEnabled = false;
        view.on("click", (event) => {
          // Get the coordinates of the click on the view
          const lat = Math.round(event.mapPoint.latitude * 1000) / 1000;
          const lon = Math.round(event.mapPoint.longitude * 1000) / 1000;

          view.openPopup({
            // Set the popup's title to the coordinates of the location
            title: "Reverse geocode: [" + lon + ", " + lat + "]",
            location: event.mapPoint // Set the location of the popup to the clicked location
          });

          const params = {
            location: event.mapPoint
          };

          // Display the popup
          // Execute a reverse geocode using the clicked location
          locator
            .locationToAddress(locatorUrl, params)
            .then((response) => {
              // If an address is successfully found, show it in the popup's content
              view.popup.content = response.address;
            })
            .catch(() => {
              // If the promise fails and no result is found, show a generic message
              view.popup.content = "No address was found for this location";
            });
        });
      });
    </script>
  </head>

  <body>
    <div id="viewDiv"></div>
    <div id="instruction" class="esri-widget">Click any location on the map to see its street address</div>
  </body>
</html>

 

Clinton_Lunn
New Contributor II

Thanks for this! Worked great, I packaged it up into a little function:

// Set the popup alignment based on the location of the popup
export function setPopupAlignment(view: SceneView | MapView) {
    reactiveUtils.watch(() => view.popup?.id, function () {
        view.popup.alignment = function () {
            const { location, view } = this;

            if ((location) && (view)) {
                const viewPoint = view.toScreen(location);
                const y2 = view.height / 2;
                const x2 = view.width / 3;
                const x3 = x2 * 2;

                if (viewPoint.y >= y2) {
                    if (viewPoint.x < x2)
                        return "top-right";
                    else if (viewPoint.x > x3)
                        return "top-left";
                } else {
                    if (viewPoint.x < x2)
                        return "bottom-right";
                    else if (viewPoint.x > x3)
                        return "bottom-left";
                    else
                        return "bottom-center";
                }
            }

            return "top-center";
        };
    });
}