<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>Custom popup content | Sample | ArcGIS Maps SDK for JavaScript</title>
<link rel="stylesheet" href="https://js.arcgis.com/5.0/esri/themes/light/main.css" />
<!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
<script type="module" src="https://js.arcgis.com/5.0/"></script>
] = await $arcgis.import([
"@arcgis/core/views/MapView.js",
"@arcgis/core/layers/FeatureLayer.js",
"@arcgis/core/PopupTemplate.js",
"@arcgis/core/popup/content/CustomContent.js",
"@arcgis/core/widgets/Search.js",
"@arcgis/core/rest/support/Query.js",
"@arcgis/core/rest/query.js",
"@arcgis/core/rest/support/StatisticDefinition.js",
let stats = {}; // Object to hold all school enrollment stats properties
// Underlying states feature layer
const featureLayer = new FeatureLayer({
url: "https://services.arcgis.com/V6ZHFr6zdgNZuVG0/arcgis/rest/services/OverlaySchools/FeatureServer/0",
title: "Private and Public schools in the US",
const view = new MapView({
defaultPopupTemplateEnabled: false,
// Create the Search widget
let searchWidget = new Search({
includeDefaultSources: false,
suggestionsEnabled: true,
searchFields: ["state_name"],
displayField: "state_name",
placeholder: "Search by state",
// This custom content element contains the Search widget
const contentWidget = new CustomContent({
// Query URL for private schools that intersect a state that was clicked
// This is used to display content in the second custom content element
"https://services.arcgis.com/V6ZHFr6zdgNZuVG0/arcgis/rest/services/PrivateSchools/FeatureServer/0";
// This custom content contains the resulting promise from the query
const contentPromise = new CustomContent({
// Generate stats for the count and types of private schools that intersect the state with average enrollment
const levelCount = new StatisticDefinition({
onStatisticField: "LEVEL_",
outStatisticFieldName: "level_count",
const enrollmentAvg = new StatisticDefinition({
onStatisticField: "ENROLLMENT",
outStatisticFieldName: "enroll_avg",
const queryObject = new Query({
geometry: event.graphic.geometry,
groupByFieldsForStatistics: ["LEVEL_"],
spatialRelationship: "intersects",
outStatistics: [levelCount, enrollmentAvg],
// Check if the (school type) LEVEL_ is a specific value, if, return the enrollment average for that level and the count of features for it
return query.executeQueryJSON(queryUrl, queryObject).then((result) => {
// Returns the entire result, need to filter based on LEVEL_
const featureAttributes = result.features.map((item, i) => {
// Loop through all the feature attributes and check for the school level (elementary, secondary, or combined)
featureAttributes.forEach((attribute) => {
switch (attribute.LEVEL_) {
stats.elemCount = attribute.level_count;
stats.elemAvg = attribute.enroll_avg;
// Format the average to remove decimal places
stats.elemAvgFormatted = intl.formatNumber(stats.elemAvg, {
maximumFractionDigits: 0,
stats.secondaryCount = attribute.level_count;
stats.secondaryAvg = attribute.enroll_avg;
stats.secondaryAvgFormatted = intl.formatNumber(stats.secondaryAvg, {
maximumFractionDigits: 0,
case "Combined elementary and secondary":
stats.combinedCount = attribute.level_count;
stats.combinedAvg = attribute.enroll_avg;
stats.combinedAvgFormatted = intl.formatNumber(stats.combinedAvg, {
maximumFractionDigits: 0,
// Format the returned values and display this in the popup content
return `There is a total of <b>${stats.elemCount + stats.secondaryCount + stats.combinedCount}</b> private schools that reside within the state. Out of this total amount of private schools: <ul><li><b>${stats.elemCount}</b> were classified as elementary, with an average enrollment of <b>${stats.elemAvgFormatted}</b> students.</li><li><b>${stats.secondaryCount}</b> were classified as secondary, with an average enrollment of <b>${stats.secondaryAvgFormatted}</b> students.</li><li><b>${stats.combinedCount}</b> were classified as both elementary and secondary, with an average enrollment of <b>${stats.combinedAvgFormatted} </b>students.</li></ul>`;
searchWidget.on("search-complete", (searchResult) => {
// Create the PopupTemplate and reference the two custom content elements
const template = new PopupTemplate({
title: "State: {state_name}",
content: [contentWidget, contentPromise],
featureLayer.popupTemplate = template;