Select to view content in your preferred language

Related table query results into popup

1037
8
04-06-2023 01:39 PM
AshleyPeters
Occasional Contributor III

I am working with a non-hosted feature layer with a related table. The service is sitting on our REST endpoint, not on ArcGIS Online, because it contains data we consider sensitive. Because it is not hosted on AGOL, I am unable to use relationshipContent. Also, while I've been using the JavaScript API off and on for a while, I am still very much a beginner.

I ran across this sample earlier - PopupTemplate with promise | Sample Code | ArcGIS Maps SDK for JavaScript 4.26 | ArcGIS Developers. It meets a lot of my needs I believe, but I am unsure how to rework the code to pull from a related table. I believe I need to change the queryURL, but that may not even be doable.

Any guidance on reworking this code or suggestions on what I should consider instead?

0 Kudos
8 Replies
KenBuja
MVP Esteemed Contributor

Have you looked at the "Browse related records in a popup" sample?

0 Kudos
AshleyPeters
Occasional Contributor III

I have, yes. We can't use that sample as a guide as the layer sits on our ArcGIS Server and not on ArcGIS Online.

I believe the closest I can get is actually using Query Related Records (Feature Service)—ArcGIS REST APIs | ArcGIS Developers. I have no experience with writing queries, so that's acting as another wall at the moment.

0 Kudos
KenBuja
MVP Esteemed Contributor

Do you have relationships set up in your service the way they are in the service used in that sample? If so, why wouldn't you be able to use your own services in a similar way?

service.png

0 Kudos
AshleyPeters
Occasional Contributor III

I do.

AshleyPeters_0-1680890862291.png

But, per Esri, there is a limitation in ArcGIS Enterprise that doesn't allow it to work. It only works on hosted feature layers on AGOL. See the note here: RelationshipContent | API Reference | ArcGIS Maps SDK for JavaScript 4.26 | ArcGIS Developers

0 Kudos
KenBuja
MVP Esteemed Contributor

Ah, thanks for that clarification.

A while back, I wrote a script to return the related records for a feature using an older methodology. Maybe this will help

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport"
        content="initial-scale=1, maximum-scale=1,user-scalable=no" />
  <title>
    Query Related Features | Sample | ArcGIS API for JavaScript 4.16
  </title>

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

    #gridDiv {
      padding: 10px;
      max-width: 500px;
    }

    #viewDiv {
      height: 100%;
      width: 100%;
    }

    #clearButton {
      margin-top: 5px;
      display: none;
    }

    .dgrid {
      height: auto !important;
    }

      .dgrid .dgrid-scroller {
        position: relative;
        max-height: 200px;
        overflow: auto;
      }

    .action {
      color: blue;
      cursor: pointer;
      text-decoration: underline;
    }
  </style>

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

  <script>
    require([
      "esri/Map",
      "esri/Basemap",
      "esri/views/MapView",
      "esri/layers/FeatureLayer",
      "esri/widgets/Legend",
      "esri/widgets/Expand",
      "dgrid/Grid",
      "dojo/dom-style",
      "dojo/dom-attr",
      "dojo/on",
      "dojo/dom"
    ], function (Map, Basemap, MapView, FeatureLayer, Legend, Expand, Grid, Portal, OAuthInfo, identityManager, domStyle, domAttr, on, dom) {
      // Create the layer
      let map, view;
      let highlight, grid, clearbutton;
      let layer = new FeatureLayer({
          portalItem: {
            id: "8be2866c3b1441b6beb5d79650198b05"
          },
          outFields: ["*"]
        });

        map = new Map({
          basemap: 'oceans',
          layers: [layer]
        });

        view = new MapView({
          container: "viewDiv",
          map: map,
          center: [-153, 63],
          zoom: 4,
          popup: {
            autoOpenEnabled: false
          }
        });

        const legend = new Legend({ view: view });
        // Expand widget to expand and contract the legend widget
        const legendExpand = new Expand({
          expandTooltip: "Show Legend",
          expanded: false,
          view: view,
          content: legend
        });

        // Add widgets to the view
        view.ui.add(document.getElementById("gridDiv"), "bottom-left");
        view.ui.add(legendExpand, "top-right");

        // Initialize variables

        // call clearMap method when clear is clicked
        clearbutton = document.getElementById("clearButton");
        clearbutton.addEventListener("click", clearMap);

        layer.load().then(function () {
          return (g = new Grid());
        });

        view.on("click", function (event) {
          clearMap();
          queryFeatures(event);
        });

      function queryFeatures(screenPoint) {
        const point = view.toMap(screenPoint);

        // Query the for the feature ids where the user clicked
        layer
          .queryObjectIds({
            geometry: point,
            spatialRelationship: "intersects",
            returnGeometry: false,
            outFields: ["*"]
          })

          .then(function (objectIds) {
            if (!objectIds.length) {
              return;
            }

            // Highlight the area returned from the first query
            view.whenLayerView(layer).then(function (layerView) {
              if (highlight) {
                highlight.remove();
              }
              highlight = layerView.highlight(objectIds);
            });

            // Query the for the related features for the features ids found
            return layer.queryRelatedFeatures({
              outFields: ["Source"],
              relationshipId: layer.relationships[0].id,
              objectIds: objectIds
            });
          })

          .then(function (relatedFeatureSetByObjectId) {
            if (!relatedFeatureSetByObjectId) {
              return;
            }
            // Create a grid with the data
            Object.keys(relatedFeatureSetByObjectId).forEach(function (
              objectId
            ) {
              // get the attributes of the FeatureSet
              const relatedFeatureSet = relatedFeatureSetByObjectId[objectId];
              const rows = relatedFeatureSet.features.map(function (feature) {
                return feature.attributes;
              });

              if (!rows.length) {
                return;
              }

              // create a new div for the grid of related features
              // append to queryResults div inside of the gridDiv
              const gridDiv = document.createElement("div");
              const results = document.getElementById("queryResults");
              results.appendChild(gridDiv);

              // destroy current grid if exists
              if (grid) {
                grid.destroy();
              }
              // create new grid to hold the results of the query
              grid = new Grid(
                {
                  columns: Object.keys(rows[0]).map(function (fieldName) {
                    return {
                      label: fieldName,
                      field: fieldName,
                      sortable: true
                    };
                  })
                },
                gridDiv
              );

              // add the data to the grid
              grid.renderArray(rows);
            });
            clearbutton.style.display = "inline";
          })
          .catch(function (error) {
            console.error(error);
          });
      }

      function clearMap() {
        if (highlight) {
          highlight.remove();
        }
        if (grid) {
          grid.destroy();
        }
        clearbutton.style.display = "none";
      }
    });</script>
</head>

<body>
  <div id="gridDiv" class="esri-widget">
    <h2>Alaska Spatial Bibliography</h2>
    <p>
      Click on a region in the map to view the documents found in that region.
    </p>
    <div id="queryResults"></div>
    <button id="clearButton" class="esri-widget">Clear Query</button>
  </div>
  <div id="viewDiv"></div>

</body>
</html>

 

0 Kudos
AshleyPeters
Occasional Contributor III

Actually, we did run across that script and have been playing around with it. It works for us, but the map has three layers we need to toggle between, and we haven't been able to get it to work/focus on just the layer that is turned on. We also tried to rework it so it would open in a popup, but have not had success with that either.

Both of those issues are likely due to lack of experience with queries on our end.

0 Kudos
ViktorSafar
Occasional Contributor II

If you gave us some code, it would be easier to help. Here is my take on your problem 

const layer = map.getLayer("some ID") as FeatureLayer;
layer.queryRelatedFeatures(query).then(function (result) {
  
  if (layer.id === "layer1")
	// do something with layer1
  
  else if (layer.id === "layer2")
	// do something with layer2
	
  else if (layer.id === "layer3")
	// do something with layer3

}).catch(function (error) {
  console.log("error from queryRelatedFeatures", error);
});

 

Adding a popup should be fairly easy https://developers.arcgis.com/javascript/latest/api-reference/esri-widgets-Popup.html#open

Build the content from the result of the relatedQuery, send it to the view's popup as content, and open the popup.

 

0 Kudos
AshleyPeters
Occasional Contributor III

Viktor,

We've taken the code that Ken provided and have reworked it so it works with our data. Here it is:

// Initialize variables
		
		// This is the construct for telling the query which layer to pull from
		
		const visibleLayer = counties;
        
		let highlight, grid;
		

        // call clearMap method when clear is clicked
        const clearbutton = document.getElementById("clearButton");
        clearbutton.addEventListener("click", clearMap);
		

		view.on("click", function (event) {
          clearMap();
          queryFeatures(event);
		  });

        function queryFeatures(screenPoint) {
          const point = view.toMap(screenPoint);
		  
		  

          // Query the for the feature ids where the user clicked
          visibleLayer                      
            .queryObjectIds({
              geometry: point,
              spatialRelationship: "intersects",
              returnGeometry: false,
              outFields: ["*"]
			  
            })

            .then(function (objectIds) {
              if (!objectIds.length) {
                return;
              }

              // Highlight the area returned from the first query
              view.whenLayerView(visibleLayer).then(function (layerView) {
                if (highlight) {
                  highlight.remove();
                }
                highlight = layerView.highlight(objectIds);
              });
			  

              // Query the for the related features for the features ids found
              return visibleLayer.queryRelatedFeatures({
                outFields: ["SpeciesGroup", "CommonName", "ProtectionLevels"],
                relationshipId: visibleLayer.relationships[2].id,
                objectIds: objectIds
			  });
            })

            .then(function (relatedFeatureSetByObjectId) {
              if (!relatedFeatureSetByObjectId) {
                return;
              }
              // Create a grid with the data
              Object.keys(relatedFeatureSetByObjectId).forEach(function (
                objectId
              ) {
                // get the attributes of the FeatureSet
                const relatedFeatureSet = relatedFeatureSetByObjectId[objectId];
                const rows = relatedFeatureSet.features.map(function (feature) {
                  return feature.attributes;
                });

                if (!rows.length) {
                  return;
                }

                // create a new div for the grid of related features
                // append to queryResults div inside of the gridDiv
                const gridDiv = document.createElement("div");
                const results = document.getElementById("queryResults");
                results.appendChild(gridDiv);

                // destroy current grid if exists
                if (grid) {
                  grid.destroy();
                }
                // create new grid to hold the results of the query
                 grid = new Grid(
                  {
                    // create field aliases for query window
					columns: Object.keys(rows[0]).map(function (fieldName) {
                      var fieldAliases = {
                        SpeciesGroup: "Species Group",
                        CommonName: "Common Name",
						ProtectionLevels: "Protection Level"
                      };

                      return {
                        label: fieldAliases[fieldName] || fieldName,
                        field: fieldName,
                        sortable: true
                      };
                    })
                  },
                  gridDiv
                );

                // add the data to the grid
                grid.renderArray(rows);
              });
              clearbutton.style.display = "inline";
            })
            .catch(function (error) {
              console.error(error);
            });
        }

        function clearMap() {
          if (highlight) {
            highlight.remove();
          }
          if (grid) {
            grid.destroy();
          }
          clearbutton.style.display = "none";
        } 

 

I've never tried to write queries before within the API, so I have had a few meetings with our IT group to get their assistance with this issue. We gave them the same code block above, with the idea of working from it to move towards a popup. So far, this is the closest we've come up with, and it isn't behaving properly.

const visibleLayer3 = hex;

            let highlight3, grid3;


            // call clearMap method when clear is clicked
            const clearbutton3 = document.getElementById("clearButton");
            clearbutton3.addEventListener("click", clearMap);


            view.on("click", function (event) {
                clearMap();
                queryFeatures(event);
            });

            function queryFeatures(screenPoint) {
                var strResult = '';
                //const strResult = document.getElementById("prpResults");
                const point = view.toMap(screenPoint);



                // Query the for the feature ids where the user clicked
                visibleLayer3
                    .queryObjectIds({
                        geometry: point,
                        spatialRelationship: "intersects",
                        returnGeometry: false,
                        outFields: ["*"]

                    })

                    .then(function (objectIds) {
                        if (!objectIds.length) {
                            return;
                        }

                        // Highlight the area returned from the first query
                        view.whenLayerView(visibleLayer3).then(function (layerView) {
                            if (highlight3) {
                                highlight3.remove();
                            }
                            highlight3 = layerView.highlight3(objectIds);
                        });


                        // Query the for the related features for the features ids found
                        return visibleLayer3.queryRelatedFeatures({
                            outFields: ["SpeciesGroup", "CommonName", "ProtectionLevels"],
                            relationshipId: visibleLayer3.relationships[0].id,
                            objectIds: objectIds
                        });
                    })

                    .then(function (relatedFeatureSetByObjectId) {
                        if (!relatedFeatureSetByObjectId) {
                            return;
                        }
                        // Create a grid with the data
                        Object.keys(relatedFeatureSetByObjectId).forEach(function (
                            objectId
                        ) {
                            // get the attributes of the FeatureSet
                            const relatedFeatureSet = relatedFeatureSetByObjectId[objectId];
                            const rows = relatedFeatureSet.features.map(function (feature) {
                                return feature.attributes;
                            });

                            if (!rows.length) {
                                return;
                            }
                            //**** Shane Code Beg ****
                            strResult = '<ul>';
                            //document.getElementById("prpResults").innerHTML = '<ul>';
                            //strResult.value = '<ul>';
                            var aobjRows = rows;
                            for (let z = 0; z < aobjRows.length; z++) {
                                let strCommonName = aobjRows[z].CommonName;
                                let strSpeciesGroup = aobjRows[z].SpeciesGroup;
                                let strProtectionLevel = aobjRows[z].ProtectionLevels;
                                //alert(aobjRows[z].CommonName);
                                strResult += '<li>' + strCommonName + ',' + strSpeciesGroup + ',' + strProtectionLevel + '<li>';
                                //strResult.value += '<li>' + strCommonName + ',' + strSpeciesGroup + ',' + strProtectionLevel + '<li>';
                                //document.getElementById("prpResults").innerHTML += '<li>' + strCommonName + ',' + strSpeciesGroup + ',' + strProtectionLevel + '<li>';
                            }
                            strResult += '</ul>';
                            //strResult.value += '</ul>';
                            //document.getElementById("prpResults").innerHTML += '</ul>';
                            //**** Shane Code End ****

                            //*** Begin Comment ***
                            // create a new div for the grid of related features
                            // append to queryResults div inside of the gridDiv
                            const gridDiv = document.createElement("div");
                            const results = document.getElementById("queryResults");
                            results.appendChild(gridDiv);

                            // destroy current grid if exists
                            if (grid3) {
                                grid3.destroy();
                            }
                            // create new grid to hold the results of the query
                            grid3 = new Grid(
                                {
                                    // create field aliases for query window
                                    columns: Object.keys(rows[0]).map(function (fieldName) {
                                        var fieldAliases = {
                                            SpeciesGroup: "Species Group",
                                            CommonName: "Common Name",
                                            ProtectionLevels: "Protection Level"
                                        };

                                        return {
                                            label: fieldAliases[fieldName] || fieldName,
                                            field: fieldName,
                                            sortable: true
                                        };
                                    })
                                },
                                gridDiv
                            );

                            // add the data to the grid
                            grid3.renderArray(rows);
                            //**** End comment ****
                        });
                        alert(strResult);
                        //alert(document.getElementById("prpResults").value);
                        clearbutton3.style.display = "inline";
                    })
                    .catch(function (error) {
                        console.error(error);
                    });
                alert(strResult);
                //alert(document.getElementById("prpResults").innerHTML);
                //alert(document.getElementById("prpResults"));
            }

            function clearMap() {
                if (highlight3) {
                    highlight3.remove();
                }
                if (grid3) {
                    grid3.destroy();
                }
                clearbutton.style.display = "none";
            }

This code isn't clean, but will give an idea the direction we are heading.

 

0 Kudos