arcgis-js-api

Popup does not hide the fields correctly when using GraphicsLayer - ArcGIS Javascript API 4.x


Using ArcGIS API Javascript 4.x and a GraphicsLayer, the popup does not hide the fields correctly. It is working fine with FeatureLayer but I need to use GraphicsLayer for this request. Attached below is a complete working example to reproduce the bug.

How to reproduce the issue:

If you click on the left triangle first, it works correctly and shows all the fields. But if you click on the right triangle first and then on the left triangle, all fields are not shown for the left triangle.

Note: Be sure to click on "Run code snippet" before performing the two tests mentioned above in order to reset the page.

Here is the complete code to reproduce the issue:

    <html lang="en">

    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
        <title>
            Custom popup actions per feature attribute | JavaScript 4.27
        </title>

        <style>
            body {
                padding: 0;
                margin: 0;
                height: 100%;
                width: 100%;
                overflow: hidden;
            }

            #viewDiv {
                position: absolute;
                right: 0;
                left: 0;
                top: 0;
                bottom: 0;
            }
        </style>

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

        <script>
            require(["esri/Map", "esri/views/MapView", "esri/layers/GraphicsLayer", "esri/geometry/Polygon","esri/Graphic",
            "esri/PopupTemplate",
            "esri/core/reactiveUtils"], (
                Map,
                MapView,
                GraphicsLayer,
                Polygon,
                Graphic,
                PopupTemplate,
                reactiveUtils
            ) => {
                const map = new Map({
                    basemap: "streets-navigation-vector"
                });

                const view = new MapView({
                    container: "viewDiv",
                    map: map,
                    center: [-112.42914,38.771353],
                    zoom: 5
                });

        /********************
        * Add boundary polygon/symbol
        ********************/
        const boundaryPolygon1 = new Polygon({
            hasZ: false,
            hasM: false,
            rings: [[[-112.42914,38.771353],[-112.324772,38.891169],[-112.571965,38.899720]]]
        });
        
        const boundaryPolygon2 = new Polygon({
            hasZ: false,
            hasM: false,
            rings: [[[-111.52914,38.771353],[-111.424772,38.891169],[-111.671965,38.899720]]]
        });

        const theSymbol = {
            type: "simple-fill",
            style: "solid",
            color: [200, 40, 40, 1],
            outline: {
                color: [255, 0, 0, 255],
                width: 2,
                type: "simple-line",
                style: "solid"
            }
        };

                /********************
                 * Add graphics layer
                 ********************/
                 
let myAtt1 = {
  ObjectID: 1,
  name: "John Ross",
  address: "1234 Main Street"
};

let myAtt2 = {
  ObjectID: 2,
  name: "Bob Cattle",
  address: null
};
                 
                 graphicsLayer = new GraphicsLayer();
                 map.add(graphicsLayer);
 
// Must use shared PopupTemplate since there can be many boundary Graphic objects             
var popTemplate = new PopupTemplate({
            title: "Graphic Test",
                    content: [
                        {
                            type: "fields",
                            fieldInfos: [
                                {
                                    fieldName: "ObjectID"
                                },
                                {
                                    fieldName: "name"
                                },
                                {
                                    fieldName: "address"
                                }
                            ]
                        }
                    ]
        });

const boundaryGraphic1 = new Graphic({
            geometry: boundaryPolygon1,
            symbol: theSymbol,
            attributes: myAtt1,
            popupEnabled: true,
            popupTemplate: popTemplate
        });
        
        const boundaryGraphic2 = new Graphic({
            geometry: boundaryPolygon2,
            symbol: theSymbol,
            attributes: myAtt2,
            popupEnabled: true,
            popupTemplate: popTemplate
        });
        

        view.when(() => {
                graphicsLayer.add(boundaryGraphic1);
                graphicsLayer.add(boundaryGraphic2);
        });

                view.when(() => {
                    // Watch for when features are selected
                    reactiveUtils.watch(() => view.popup.selectedFeature, (graphic) => {
                        if (graphic) {
                            // Get Effective PopupTemplate
                            const graphicTemplate = graphic.getEffectivePopupTemplate();
                           
                            
                            // Hide row if null value
                            for(const fi of graphicTemplate.content[0].fieldInfos) {
                var myfield = graphic.attributes[fi.fieldName];
                                if (!!myfield)
                                {
                                    fi.visible = true;
                                }
                                else    
                               {
                                   fi.visible = false;
                               }
                            }

                        }
                    });

                    
                });
            });
        </script>
    </head>

    <body class="light">
        <div id="viewDiv" class="esri-widget"></div>
    </body>

    </html>


Solution

  • I found the issue! The code to watch the popup changes above in the original question is the correct code for FeatureLayer popup. It watches the view.popup.selectedFeature and references the popup template using getEffectivePopupTemplate:

    view.when(() => {
                        // Watch for when features are selected
                        reactiveUtils.watch(() => view.popup.selectedFeature, (graphic) => {
                            if (graphic) {
                                // Get Effective PopupTemplate
                                const graphicTemplate = graphic.getEffectivePopupTemplate();
                               
                                
                                // Hide row if null value
                                for(const fi of graphicTemplate.content[0].fieldInfos) {
                    var myfield = graphic.attributes[fi.fieldName];
                                    if (!!myfield)
                                    {
                                        fi.visible = true;
                                    }
                                    else    
                                   {
                                       fi.visible = false;
                                   }
                                }
    
                            }
                        });
    
                        
                    });
    

    However the code to watch a popup using a GraphicsLayer is the following. Notice we are now watching view.popup.selectedFeatureWidget and are using the actual template (widget.graphic.popupTemplate instead of calling graphic.getEffectivePopupTemplate()). Also notice the attributes are one level down (widget.graphic.attributes):

    view.when(() => {
                            // Watch for when features are selected
                            reactiveUtils.watch(() => view.popup.selectedFeatureWidget, (widget) => {
                                if (widget && widget.graphic && widget.graphic.popupTemplate) {
                                    // Get the graphic popup template
                            const graphicTemplate = widget.graphic.popupTemplate;
                                   
                                    
                                    // Hide row if null value
                                    for(const fi of graphicTemplate.content[0].fieldInfos) {
                        var myfield = widget.graphic.attributes[fi.fieldName];
                                        if (!!myfield)
                                        {
                                            fi.visible = true;
                                        }
                                        else    
                                       {
                                           fi.visible = false;
                                       }
                                    }
    
                                }
                            });
    
                            
                        });
    

    <html lang="en">
    
        <head>
            <meta charset="utf-8" />
            <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
            <title>
                Custom popup actions per feature attribute | JavaScript 4.27
            </title>
    
            <style>
                body {
                    padding: 0;
                    margin: 0;
                    height: 100%;
                    width: 100%;
                    overflow: hidden;
                }
    
                #viewDiv {
                    position: absolute;
                    right: 0;
                    left: 0;
                    top: 0;
                    bottom: 0;
                }
            </style>
    
            <link rel="stylesheet" href="https://js.arcgis.com/4.27/esri/themes/light/main.css" />
            <script src="https://js.arcgis.com/4.27/"></script>
    
            <script>
                require(["esri/Map", "esri/views/MapView", "esri/layers/GraphicsLayer", "esri/geometry/Polygon","esri/Graphic",
                "esri/PopupTemplate",
                "esri/core/reactiveUtils"], (
                    Map,
                    MapView,
                    GraphicsLayer,
                    Polygon,
                    Graphic,
                    PopupTemplate,
                    reactiveUtils
                ) => {
                    const map = new Map({
                        basemap: "streets-navigation-vector"
                    });
    
                    const view = new MapView({
                        container: "viewDiv",
                        map: map,
                        center: [-112.42914,38.771353],
                        zoom: 5
                    });
    
            /********************
            * Add boundary polygon/symbol
            ********************/
            const boundaryPolygon1 = new Polygon({
                hasZ: false,
                hasM: false,
                rings: [[[-112.42914,38.771353],[-112.324772,38.891169],[-112.571965,38.899720]]]
            });
            
            const boundaryPolygon2 = new Polygon({
                hasZ: false,
                hasM: false,
                rings: [[[-111.52914,38.771353],[-111.424772,38.891169],[-111.671965,38.899720]]]
            });
    
            const theSymbol = {
                type: "simple-fill",
                style: "solid",
                color: [200, 40, 40, 1],
                outline: {
                    color: [255, 0, 0, 255],
                    width: 2,
                    type: "simple-line",
                    style: "solid"
                }
            };
    
                    /********************
                     * Add graphics layer
                     ********************/
                     
    let myAtt1 = {
      ObjectID: 1,
      name: "John Ross",
      address: "1234 Main Street"
    };
    
    let myAtt2 = {
      ObjectID: 2,
      name: "Bob Cattle",
      address: null
    };
                     
                     graphicsLayer = new GraphicsLayer();
                     map.add(graphicsLayer);
     
    // Must use shared PopupTemplate since there can be many boundary Graphic objects             
    var popTemplate = new PopupTemplate({
                title: "Graphic Test",
                        content: [
                            {
                                type: "fields",
                                fieldInfos: [
                                    {
                                        fieldName: "ObjectID"
                                    },
                                    {
                                        fieldName: "name"
                                    },
                                    {
                                        fieldName: "address"
                                    }
                                ]
                            }
                        ]
            });
    
    const boundaryGraphic1 = new Graphic({
                geometry: boundaryPolygon1,
                symbol: theSymbol,
                attributes: myAtt1,
                popupEnabled: true,
                popupTemplate: popTemplate
            });
            
            const boundaryGraphic2 = new Graphic({
                geometry: boundaryPolygon2,
                symbol: theSymbol,
                attributes: myAtt2,
                popupEnabled: true,
                popupTemplate: popTemplate
            });
            
    
            view.when(() => {
                    graphicsLayer.add(boundaryGraphic1);
                    graphicsLayer.add(boundaryGraphic2);
            });
    
                    
                    
                    view.when(() => {
                        // Watch for when features are selected
                        reactiveUtils.watch(() => view.popup.selectedFeatureWidget, (widget) => {
                            if (widget && widget.graphic && widget.graphic.popupTemplate) {
                                // Get the graphic popup template
                        const graphicTemplate = widget.graphic.popupTemplate;
                               
                                
                                // Hide row if null value
                                for(const fi of graphicTemplate.content[0].fieldInfos) {
                    var myfield = widget.graphic.attributes[fi.fieldName];
                                    if (!!myfield)
                                    {
                                        fi.visible = true;
                                    }
                                    else    
                                   {
                                       fi.visible = false;
                                   }
                                }
    
                            }
                        });
    
                        
                    });
                    
                    
                });
            </script>
        </head>
    
        <body class="light">
            <div id="viewDiv" class="esri-widget"></div>
        </body>
    
        </html>