Skip to content

Learn how to perform a text-based search to find places within a bounding box.

Click on any place graphic to see its details in a popup.

The ArcGIS Places service allows you to search for places within a geographic bounding box, typically the visible area of a map. After performing a search, you can request additional details for each place, such as street address and telephone number.

In this tutorial, you’ll use the ArcGIS Maps SDK for JavaScript to:

  • Perform a bounding box search for places based on the current map extent
  • Display results as interactive graphics on the map
  • Use the Calcite Design System to build a category-based search interface
  • Show detailed place information in a popup when a result is clicked

By the end, you’ll have a functional app that lets users explore places within the map view and see details for each location.

Prerequisites

Steps

Create a new pen

  1. To get started, either complete the Display a map tutorial or .

Get an access token

You need an access token with the correct privileges to access the location services used in this tutorial.

  1. Go to the Create an API key tutorial and create an API key with the following privilege(s) :
    • Privileges
      • Location services > Basemaps
      • Location services > Places
  2. In CodePen, set the apiKey property on the global esriConfig variable to your access token.
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };

To learn about other ways to get an access token, go to Types of authentication.

Update the HTML

  1. Set up the main layout for your app:

    • Use the Calcite Shell component to define the application container and enable dark mode.
    • Add the Map component to display the interactive map.
      • Set the initial center and zoom.
      • Disable the default popup, since you will display custom place details in your own popup component.
    • Insert the Zoom component in the top-left slot for map navigation.
    • Add the Calcite Chip Group component in the top-right slot to let users select a place category (e.g., Restaurants, Hotels, Museums, ATMs, Breweries).
    • Place the Popup component in the popup slot to show details when a place is clicked.

    Ensure each UI element is placed in the correct slot to provide a clear and intuitive user experience.

    <body>
    <calcite-shell class="calcite-mode-auto">
    <arcgis-map center="-122.33, 47.61" popup-disabled zoom="14">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <calcite-chip-group id="search-chip-group" label="Category" selection-mode="single-persist" slot="top-right">
    <calcite-chip appearance="outline-fill" label="Restaurants" selected value="Restaurant">
    Restaurants
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Hotels" value="Hotel">
    Hotels
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Museums" value="Museum">
    Museums
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="ATMs" value="ATM"> ATMs </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Breweries" value="Brewery">
    Breweries
    </calcite-chip>
    </calcite-chip-group>
    <arcgis-popup slot="popup"></arcgis-popup>
    </arcgis-map>
    </calcite-shell>
    </body>

Update the CSS

  1. The Calcite Shell component automatically fills the available space, so you do not need extra CSS to size the map container. Remove any previous CSS that set the height or margin for the html or body.

    <style>
    html,
    body {
    height: 100%;
    margin: 0;
    }
    </style>
  2. Add custom CSS to style the Calcite Chip components so they stand out clearly against the basemap. Use the --calcite-color-foreground-1 CSS variable for the background color to match the Zoom component and ensure visual consistency.

    16 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Display a map</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.0/"></script>
    <style>
    calcite-chip {
    --calcite-chip-background-color: var(--calcite-color-foreground-1);
    }
    33 collapsed lines
    </style>
    </head>
    <body>
    <calcite-shell class="calcite-mode-auto">
    <arcgis-map center="-122.33, 47.61" popup-disabled zoom="14">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <calcite-chip-group id="search-chip-group" label="Category" selection-mode="single-persist" slot="top-right">
    <calcite-chip appearance="outline-fill" label="Restaurants" selected value="Restaurant">
    Restaurants
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Hotels" value="Hotel">
    Hotels
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Museums" value="Museum">
    Museums
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="ATMs" value="ATM"> ATMs </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Breweries" value="Brewery">
    Breweries
    </calcite-chip>
    </calcite-chip-group>
    <arcgis-popup slot="popup"></arcgis-popup>
    </arcgis-map>
    </calcite-shell>
    </body>
    </html>

Add JavaScript

  1. In a new <script> at the bottom of the <body>, import the following modules from the ArcGIS Maps SDK for JavaScript:

    These modules are required to create the map, display place results, and interact with the places service.

    24 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Display a map</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.0/"></script>
    <style>
    calcite-chip {
    --calcite-chip-background-color: var(--calcite-color-foreground-1);
    }
    </style>
    </head>
    <body>
    <calcite-shell class="calcite-mode-auto">
    <arcgis-map center="-122.33, 47.61" popup-disabled zoom="14">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <calcite-chip-group id="search-chip-group" label="Category" selection-mode="single-persist" slot="top-right">
    <calcite-chip appearance="outline-fill" label="Restaurants" selected value="Restaurant">
    Restaurants
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Hotels" value="Hotel">
    Hotels
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Museums" value="Museum">
    Museums
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="ATMs" value="ATM"> ATMs </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Breweries" value="Brewery">
    Breweries
    </calcite-chip>
    </calcite-chip-group>
    <arcgis-popup slot="popup"></arcgis-popup>
    </arcgis-map>
    </calcite-shell>
    <script type="module">
    const [
    Graphic,
    GraphicsLayer,
    Map,
    places,
    FetchPlaceParameters,
    PlacesQueryParameters,
    PictureMarkerSymbol,
    ] = await $arcgis.import([
    "@arcgis/core/Graphic.js",
    "@arcgis/core/layers/GraphicsLayer.js",
    "@arcgis/core/Map.js",
    "@arcgis/core/rest/places.js",
    "@arcgis/core/rest/support/FetchPlaceParameters.js",
    "@arcgis/core/rest/support/PlacesQueryParameters.js",
    "@arcgis/core/symbols/PictureMarkerSymbol.js",
    ]);
    </script>
    </body>
    3 collapsed lines
    </html>
  2. Create a state object to store the current map extent and the selected search category. This object will be updated whenever the map view changes or a new category is selected, and will be used for querying places within the bounding box.

    68 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Display a map</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.0/"></script>
    <style>
    calcite-chip {
    --calcite-chip-background-color: var(--calcite-color-foreground-1);
    }
    </style>
    </head>
    <body>
    <calcite-shell class="calcite-mode-auto">
    <arcgis-map center="-122.33, 47.61" popup-disabled zoom="14">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <calcite-chip-group id="search-chip-group" label="Category" selection-mode="single-persist" slot="top-right">
    <calcite-chip appearance="outline-fill" label="Restaurants" selected value="Restaurant">
    Restaurants
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Hotels" value="Hotel">
    Hotels
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Museums" value="Museum">
    Museums
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="ATMs" value="ATM"> ATMs </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Breweries" value="Brewery">
    Breweries
    </calcite-chip>
    </calcite-chip-group>
    <arcgis-popup slot="popup"></arcgis-popup>
    </arcgis-map>
    </calcite-shell>
    <script type="module">
    const [
    Graphic,
    GraphicsLayer,
    Map,
    places,
    FetchPlaceParameters,
    PlacesQueryParameters,
    PictureMarkerSymbol,
    ] = await $arcgis.import([
    "@arcgis/core/Graphic.js",
    "@arcgis/core/layers/GraphicsLayer.js",
    "@arcgis/core/Map.js",
    "@arcgis/core/rest/places.js",
    "@arcgis/core/rest/support/FetchPlaceParameters.js",
    "@arcgis/core/rest/support/PlacesQueryParameters.js",
    "@arcgis/core/symbols/PictureMarkerSymbol.js",
    ]);
    // Application state
    const state = {
    extent: null,
    searchText: "Restaurant",
    };
    7 collapsed lines
    </script>
    </body>
    </html>
  3. Query the DOM for the key UI components you created in the HTML step, such as the Calcite Chip Group, Popup, and Map, so you can interact with them in your JavaScript code (e.g., to listen for events or update their properties).

    74 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Display a map</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.0/"></script>
    <style>
    calcite-chip {
    --calcite-chip-background-color: var(--calcite-color-foreground-1);
    }
    </style>
    </head>
    <body>
    <calcite-shell class="calcite-mode-auto">
    <arcgis-map center="-122.33, 47.61" popup-disabled zoom="14">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <calcite-chip-group id="search-chip-group" label="Category" selection-mode="single-persist" slot="top-right">
    <calcite-chip appearance="outline-fill" label="Restaurants" selected value="Restaurant">
    Restaurants
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Hotels" value="Hotel">
    Hotels
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Museums" value="Museum">
    Museums
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="ATMs" value="ATM"> ATMs </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Breweries" value="Brewery">
    Breweries
    </calcite-chip>
    </calcite-chip-group>
    <arcgis-popup slot="popup"></arcgis-popup>
    </arcgis-map>
    </calcite-shell>
    <script type="module">
    const [
    Graphic,
    GraphicsLayer,
    Map,
    places,
    FetchPlaceParameters,
    PlacesQueryParameters,
    PictureMarkerSymbol,
    ] = await $arcgis.import([
    "@arcgis/core/Graphic.js",
    "@arcgis/core/layers/GraphicsLayer.js",
    "@arcgis/core/Map.js",
    "@arcgis/core/rest/places.js",
    "@arcgis/core/rest/support/FetchPlaceParameters.js",
    "@arcgis/core/rest/support/PlacesQueryParameters.js",
    "@arcgis/core/symbols/PictureMarkerSymbol.js",
    ]);
    // Application state
    const state = {
    extent: null,
    searchText: "Restaurant",
    };
    // Query the Calcite Chip Group component
    const chipGroup = document.querySelector("#search-chip-group");
    // Query the Popup component
    const popupElement = document.querySelector("arcgis-popup");
    // Query the Map component
    const viewElement = document.querySelector("arcgis-map");
    7 collapsed lines
    </script>
    </body>
    </html>
  4. Create a new GraphicsLayer to store the graphics representing place results on the map. This layer will be added to the map and updated whenever a new places search is performed.

    83 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Display a map</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.0/"></script>
    <style>
    calcite-chip {
    --calcite-chip-background-color: var(--calcite-color-foreground-1);
    }
    </style>
    </head>
    <body>
    <calcite-shell class="calcite-mode-auto">
    <arcgis-map center="-122.33, 47.61" popup-disabled zoom="14">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <calcite-chip-group id="search-chip-group" label="Category" selection-mode="single-persist" slot="top-right">
    <calcite-chip appearance="outline-fill" label="Restaurants" selected value="Restaurant">
    Restaurants
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Hotels" value="Hotel">
    Hotels
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Museums" value="Museum">
    Museums
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="ATMs" value="ATM"> ATMs </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Breweries" value="Brewery">
    Breweries
    </calcite-chip>
    </calcite-chip-group>
    <arcgis-popup slot="popup"></arcgis-popup>
    </arcgis-map>
    </calcite-shell>
    <script type="module">
    const [
    Graphic,
    GraphicsLayer,
    Map,
    places,
    FetchPlaceParameters,
    PlacesQueryParameters,
    PictureMarkerSymbol,
    ] = await $arcgis.import([
    "@arcgis/core/Graphic.js",
    "@arcgis/core/layers/GraphicsLayer.js",
    "@arcgis/core/Map.js",
    "@arcgis/core/rest/places.js",
    "@arcgis/core/rest/support/FetchPlaceParameters.js",
    "@arcgis/core/rest/support/PlacesQueryParameters.js",
    "@arcgis/core/symbols/PictureMarkerSymbol.js",
    ]);
    // Application state
    const state = {
    extent: null,
    searchText: "Restaurant",
    };
    // Query the Calcite Chip Group component
    const chipGroup = document.querySelector("#search-chip-group");
    // Query the Popup component
    const popupElement = document.querySelector("arcgis-popup");
    // Query the Map component
    const viewElement = document.querySelector("arcgis-map");
    // Create a graphics layer to display places
    const placesLayer = new GraphicsLayer({
    id: "places-layer",
    title: "Places Layer",
    });
    7 collapsed lines
    </script>
    </body>
    </html>
  5. Initialize a new Map with your chosen basemap (e.g., “arcgis/navigation”) and add the GraphicsLayer you created.

    89 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Display a map</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.0/"></script>
    <style>
    calcite-chip {
    --calcite-chip-background-color: var(--calcite-color-foreground-1);
    }
    </style>
    </head>
    <body>
    <calcite-shell class="calcite-mode-auto">
    <arcgis-map center="-122.33, 47.61" popup-disabled zoom="14">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <calcite-chip-group id="search-chip-group" label="Category" selection-mode="single-persist" slot="top-right">
    <calcite-chip appearance="outline-fill" label="Restaurants" selected value="Restaurant">
    Restaurants
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Hotels" value="Hotel">
    Hotels
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Museums" value="Museum">
    Museums
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="ATMs" value="ATM"> ATMs </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Breweries" value="Brewery">
    Breweries
    </calcite-chip>
    </calcite-chip-group>
    <arcgis-popup slot="popup"></arcgis-popup>
    </arcgis-map>
    </calcite-shell>
    <script type="module">
    const [
    Graphic,
    GraphicsLayer,
    Map,
    places,
    FetchPlaceParameters,
    PlacesQueryParameters,
    PictureMarkerSymbol,
    ] = await $arcgis.import([
    "@arcgis/core/Graphic.js",
    "@arcgis/core/layers/GraphicsLayer.js",
    "@arcgis/core/Map.js",
    "@arcgis/core/rest/places.js",
    "@arcgis/core/rest/support/FetchPlaceParameters.js",
    "@arcgis/core/rest/support/PlacesQueryParameters.js",
    "@arcgis/core/symbols/PictureMarkerSymbol.js",
    ]);
    // Application state
    const state = {
    extent: null,
    searchText: "Restaurant",
    };
    // Query the Calcite Chip Group component
    const chipGroup = document.querySelector("#search-chip-group");
    // Query the Popup component
    const popupElement = document.querySelector("arcgis-popup");
    // Query the Map component
    const viewElement = document.querySelector("arcgis-map");
    // Create a graphics layer to display places
    const placesLayer = new GraphicsLayer({
    id: "places-layer",
    title: "Places Layer",
    });
    // Initialize the map and add the places layer
    viewElement.map = new Map({
    basemap: "arcgis/navigation",
    layers: [placesLayer],
    });
    7 collapsed lines
    </script>
    </body>
    </html>
  6. To ensure the bounding box stays within service limits, configure Map’s zoom constraints to prevent zooming out too far.

    95 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Display a map</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.0/"></script>
    <style>
    calcite-chip {
    --calcite-chip-background-color: var(--calcite-color-foreground-1);
    }
    </style>
    </head>
    <body>
    <calcite-shell class="calcite-mode-auto">
    <arcgis-map center="-122.33, 47.61" popup-disabled zoom="14">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <calcite-chip-group id="search-chip-group" label="Category" selection-mode="single-persist" slot="top-right">
    <calcite-chip appearance="outline-fill" label="Restaurants" selected value="Restaurant">
    Restaurants
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Hotels" value="Hotel">
    Hotels
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Museums" value="Museum">
    Museums
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="ATMs" value="ATM"> ATMs </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Breweries" value="Brewery">
    Breweries
    </calcite-chip>
    </calcite-chip-group>
    <arcgis-popup slot="popup"></arcgis-popup>
    </arcgis-map>
    </calcite-shell>
    <script type="module">
    const [
    Graphic,
    GraphicsLayer,
    Map,
    places,
    FetchPlaceParameters,
    PlacesQueryParameters,
    PictureMarkerSymbol,
    ] = await $arcgis.import([
    "@arcgis/core/Graphic.js",
    "@arcgis/core/layers/GraphicsLayer.js",
    "@arcgis/core/Map.js",
    "@arcgis/core/rest/places.js",
    "@arcgis/core/rest/support/FetchPlaceParameters.js",
    "@arcgis/core/rest/support/PlacesQueryParameters.js",
    "@arcgis/core/symbols/PictureMarkerSymbol.js",
    ]);
    // Application state
    const state = {
    extent: null,
    searchText: "Restaurant",
    };
    // Query the Calcite Chip Group component
    const chipGroup = document.querySelector("#search-chip-group");
    // Query the Popup component
    const popupElement = document.querySelector("arcgis-popup");
    // Query the Map component
    const viewElement = document.querySelector("arcgis-map");
    // Create a graphics layer to display places
    const placesLayer = new GraphicsLayer({
    id: "places-layer",
    title: "Places Layer",
    });
    // Initialize the map and add the places layer
    viewElement.map = new Map({
    basemap: "arcgis/navigation",
    layers: [placesLayer],
    });
    // Configure the maps's zoom constraints
    await viewElement.viewOnReady();
    viewElement.constraints.maxZoom = 20;
    viewElement.constraints.minZoom = 12;
    7 collapsed lines
    </script>
    </body>
    </html>

Find places in the map extent

  1. Create an asynchronous function named queryPlaces to search for places within the current map extent:

    • Check that both the map extent and search text are defined in your state object. If either is missing, exit the function early.
      • The searchText parameter allows you to search across multiple attributes of a place, including its name and category.
    • If both are present, call the queryPlacesWithinExtent method, passing the current extent and search text, and request icons for each place.
    • Pass the response to the addGraphics function to display the results on the map.
    • Include error handling to log any issues that occur during the query.
    100 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Display a map</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.0/"></script>
    <style>
    calcite-chip {
    --calcite-chip-background-color: var(--calcite-color-foreground-1);
    }
    </style>
    </head>
    <body>
    <calcite-shell class="calcite-mode-auto">
    <arcgis-map center="-122.33, 47.61" popup-disabled zoom="14">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <calcite-chip-group id="search-chip-group" label="Category" selection-mode="single-persist" slot="top-right">
    <calcite-chip appearance="outline-fill" label="Restaurants" selected value="Restaurant">
    Restaurants
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Hotels" value="Hotel">
    Hotels
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Museums" value="Museum">
    Museums
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="ATMs" value="ATM"> ATMs </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Breweries" value="Brewery">
    Breweries
    </calcite-chip>
    </calcite-chip-group>
    <arcgis-popup slot="popup"></arcgis-popup>
    </arcgis-map>
    </calcite-shell>
    <script type="module">
    const [
    Graphic,
    GraphicsLayer,
    Map,
    places,
    FetchPlaceParameters,
    PlacesQueryParameters,
    PictureMarkerSymbol,
    ] = await $arcgis.import([
    "@arcgis/core/Graphic.js",
    "@arcgis/core/layers/GraphicsLayer.js",
    "@arcgis/core/Map.js",
    "@arcgis/core/rest/places.js",
    "@arcgis/core/rest/support/FetchPlaceParameters.js",
    "@arcgis/core/rest/support/PlacesQueryParameters.js",
    "@arcgis/core/symbols/PictureMarkerSymbol.js",
    ]);
    // Application state
    const state = {
    extent: null,
    searchText: "Restaurant",
    };
    // Query the Calcite Chip Group component
    const chipGroup = document.querySelector("#search-chip-group");
    // Query the Popup component
    const popupElement = document.querySelector("arcgis-popup");
    // Query the Map component
    const viewElement = document.querySelector("arcgis-map");
    // Create a graphics layer to display places
    const placesLayer = new GraphicsLayer({
    id: "places-layer",
    title: "Places Layer",
    });
    // Initialize the map and add the places layer
    viewElement.map = new Map({
    basemap: "arcgis/navigation",
    layers: [placesLayer],
    });
    // Configure the maps's zoom constraints
    await viewElement.viewOnReady();
    viewElement.constraints.maxZoom = 20;
    viewElement.constraints.minZoom = 12;
    // Function to query places within the current extent
    async function queryPlaces() {
    // Ensure extent and search text are defined
    if (!state.extent || !state.searchText) {
    return;
    }
    // Perform the places queryPlacesWithinExtent request
    try {
    const response = await places.queryPlacesWithinExtent(
    new PlacesQueryParameters({
    extent: state.extent,
    icon: "png",
    searchText: state.searchText,
    }),
    );
    addGraphics(response);
    } catch (error) {
    console.error("Error querying places within extent:", error);
    }
    }
    7 collapsed lines
    </script>
    </body>
    </html>
  2. Create a function named addGraphics to display place results on the map:

    • Take the places search response as an argument.
    • Use the JavaScript array map method to iterate through the results and create a new Graphic for each place.
    • For each place:
      • Build a categories string from the array of categories in the result.
      • Add other relevant attributes (e.g., name, distance, placeId).
      • Set the geometry to the place’s location.
      • Create a PictureMarkerSymbol using the icon URL and set its size.
    • Clear any existing graphics from the GraphicsLayer, then add the new graphics to the layer.
    100 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Display a map</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.0/"></script>
    <style>
    calcite-chip {
    --calcite-chip-background-color: var(--calcite-color-foreground-1);
    }
    </style>
    </head>
    <body>
    <calcite-shell class="calcite-mode-auto">
    <arcgis-map center="-122.33, 47.61" popup-disabled zoom="14">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <calcite-chip-group id="search-chip-group" label="Category" selection-mode="single-persist" slot="top-right">
    <calcite-chip appearance="outline-fill" label="Restaurants" selected value="Restaurant">
    Restaurants
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Hotels" value="Hotel">
    Hotels
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Museums" value="Museum">
    Museums
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="ATMs" value="ATM"> ATMs </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Breweries" value="Brewery">
    Breweries
    </calcite-chip>
    </calcite-chip-group>
    <arcgis-popup slot="popup"></arcgis-popup>
    </arcgis-map>
    </calcite-shell>
    <script type="module">
    const [
    Graphic,
    GraphicsLayer,
    Map,
    places,
    FetchPlaceParameters,
    PlacesQueryParameters,
    PictureMarkerSymbol,
    ] = await $arcgis.import([
    "@arcgis/core/Graphic.js",
    "@arcgis/core/layers/GraphicsLayer.js",
    "@arcgis/core/Map.js",
    "@arcgis/core/rest/places.js",
    "@arcgis/core/rest/support/FetchPlaceParameters.js",
    "@arcgis/core/rest/support/PlacesQueryParameters.js",
    "@arcgis/core/symbols/PictureMarkerSymbol.js",
    ]);
    // Application state
    const state = {
    extent: null,
    searchText: "Restaurant",
    };
    // Query the Calcite Chip Group component
    const chipGroup = document.querySelector("#search-chip-group");
    // Query the Popup component
    const popupElement = document.querySelector("arcgis-popup");
    // Query the Map component
    const viewElement = document.querySelector("arcgis-map");
    // Create a graphics layer to display places
    const placesLayer = new GraphicsLayer({
    id: "places-layer",
    title: "Places Layer",
    });
    // Initialize the map and add the places layer
    viewElement.map = new Map({
    basemap: "arcgis/navigation",
    layers: [placesLayer],
    });
    // Configure the maps's zoom constraints
    await viewElement.viewOnReady();
    viewElement.constraints.maxZoom = 20;
    viewElement.constraints.minZoom = 12;
    // Function to add graphics to the places layer based on query results
    function addGraphics(response) {
    // Using the JavaScript map method, convert each place result to a graphic
    const graphics = response.results.map((result) => {
    const categories = result.categories.map((category) => `${category.label}`).join(", ");
    const graphic = new Graphic({
    attributes: {
    categories: categories,
    distance: result.distance,
    name: result.name,
    placeId: result.placeId,
    },
    geometry: result.location,
    symbol: new PictureMarkerSymbol({
    url: result.icon.url,
    width: 24,
    height: 24,
    }),
    });
    return graphic;
    });
    // Remove existing graphics and add the new ones to the places layer
    placesLayer.removeAll();
    placesLayer.addMany(graphics);
    }
    29 collapsed lines
    // Function to query places within the current extent
    async function queryPlaces() {
    // Ensure extent and search text are defined
    if (!state.extent || !state.searchText) {
    return;
    }
    // Perform the places queryPlacesWithinExtent request
    try {
    const response = await places.queryPlacesWithinExtent(
    new PlacesQueryParameters({
    extent: state.extent,
    icon: "png",
    searchText: state.searchText,
    }),
    );
    addGraphics(response);
    } catch (error) {
    console.error("Error querying places within extent:", error);
    }
    }
    </script>
    </body>
    </html>

Add event listeners

  1. Add an event listener to the Calcite Chip Group’s calciteChipGroupSelect event:

    • When a user selects a new category chip, update the search category in your state object with the selected value.
    • If the popup is currently visible, hide it to avoid showing outdated information.
    • Call the queryPlaces function to perform a new search and update the map with results for the selected category.
    100 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Display a map</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.0/"></script>
    <style>
    calcite-chip {
    --calcite-chip-background-color: var(--calcite-color-foreground-1);
    }
    </style>
    </head>
    <body>
    <calcite-shell class="calcite-mode-auto">
    <arcgis-map center="-122.33, 47.61" popup-disabled zoom="14">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <calcite-chip-group id="search-chip-group" label="Category" selection-mode="single-persist" slot="top-right">
    <calcite-chip appearance="outline-fill" label="Restaurants" selected value="Restaurant">
    Restaurants
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Hotels" value="Hotel">
    Hotels
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Museums" value="Museum">
    Museums
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="ATMs" value="ATM"> ATMs </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Breweries" value="Brewery">
    Breweries
    </calcite-chip>
    </calcite-chip-group>
    <arcgis-popup slot="popup"></arcgis-popup>
    </arcgis-map>
    </calcite-shell>
    <script type="module">
    const [
    Graphic,
    GraphicsLayer,
    Map,
    places,
    FetchPlaceParameters,
    PlacesQueryParameters,
    PictureMarkerSymbol,
    ] = await $arcgis.import([
    "@arcgis/core/Graphic.js",
    "@arcgis/core/layers/GraphicsLayer.js",
    "@arcgis/core/Map.js",
    "@arcgis/core/rest/places.js",
    "@arcgis/core/rest/support/FetchPlaceParameters.js",
    "@arcgis/core/rest/support/PlacesQueryParameters.js",
    "@arcgis/core/symbols/PictureMarkerSymbol.js",
    ]);
    // Application state
    const state = {
    extent: null,
    searchText: "Restaurant",
    };
    // Query the Calcite Chip Group component
    const chipGroup = document.querySelector("#search-chip-group");
    // Query the Popup component
    const popupElement = document.querySelector("arcgis-popup");
    // Query the Map component
    const viewElement = document.querySelector("arcgis-map");
    // Create a graphics layer to display places
    const placesLayer = new GraphicsLayer({
    id: "places-layer",
    title: "Places Layer",
    });
    // Initialize the map and add the places layer
    viewElement.map = new Map({
    basemap: "arcgis/navigation",
    layers: [placesLayer],
    });
    // Configure the maps's zoom constraints
    await viewElement.viewOnReady();
    viewElement.constraints.maxZoom = 20;
    viewElement.constraints.minZoom = 12;
    // Event listener for chip group selection changes to update search text, close any visible popup and query places
    chipGroup.addEventListener("calciteChipGroupSelect", () => {
    if (chipGroup.selectedItems.length > 0) {
    state.searchText = chipGroup.selectedItems[0].value;
    if (popupElement.visible) {
    popupElement.visible = false;
    }
    queryPlaces();
    }
    });
    57 collapsed lines
    // Function to add graphics to the places layer based on query results
    function addGraphics(response) {
    // Using the JavaScript map method, convert each place result to a graphic
    const graphics = response.results.map((result) => {
    const categories = result.categories.map((category) => `${category.label}`).join(", ");
    const graphic = new Graphic({
    attributes: {
    categories: categories,
    distance: result.distance,
    name: result.name,
    placeId: result.placeId,
    },
    geometry: result.location,
    symbol: new PictureMarkerSymbol({
    url: result.icon.url,
    width: 24,
    height: 24,
    }),
    });
    return graphic;
    });
    // Remove existing graphics and add the new ones to the places layer
    placesLayer.removeAll();
    placesLayer.addMany(graphics);
    }
    // Function to query places within the current extent
    async function queryPlaces() {
    // Ensure extent and search text are defined
    if (!state.extent || !state.searchText) {
    return;
    }
    // Perform the places queryPlacesWithinExtent request
    try {
    const response = await places.queryPlacesWithinExtent(
    new PlacesQueryParameters({
    extent: state.extent,
    icon: "png",
    searchText: state.searchText,
    }),
    );
    addGraphics(response);
    } catch (error) {
    console.error("Error querying places within extent:", error);
    }
    }
    </script>
    </body>
    </html>
  2. Add an event listener to the Map’s arcgisViewChange event:

    • When the map view changes (e.g., pan or zoom), update the extent property in your state object with the new map extent.
    • Call the queryPlaces function to perform a new search and update the map with results for the new visible extent.
    113 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Display a map</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.0/"></script>
    <style>
    calcite-chip {
    --calcite-chip-background-color: var(--calcite-color-foreground-1);
    }
    </style>
    </head>
    <body>
    <calcite-shell class="calcite-mode-auto">
    <arcgis-map center="-122.33, 47.61" popup-disabled zoom="14">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <calcite-chip-group id="search-chip-group" label="Category" selection-mode="single-persist" slot="top-right">
    <calcite-chip appearance="outline-fill" label="Restaurants" selected value="Restaurant">
    Restaurants
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Hotels" value="Hotel">
    Hotels
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Museums" value="Museum">
    Museums
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="ATMs" value="ATM"> ATMs </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Breweries" value="Brewery">
    Breweries
    </calcite-chip>
    </calcite-chip-group>
    <arcgis-popup slot="popup"></arcgis-popup>
    </arcgis-map>
    </calcite-shell>
    <script type="module">
    const [
    Graphic,
    GraphicsLayer,
    Map,
    places,
    FetchPlaceParameters,
    PlacesQueryParameters,
    PictureMarkerSymbol,
    ] = await $arcgis.import([
    "@arcgis/core/Graphic.js",
    "@arcgis/core/layers/GraphicsLayer.js",
    "@arcgis/core/Map.js",
    "@arcgis/core/rest/places.js",
    "@arcgis/core/rest/support/FetchPlaceParameters.js",
    "@arcgis/core/rest/support/PlacesQueryParameters.js",
    "@arcgis/core/symbols/PictureMarkerSymbol.js",
    ]);
    // Application state
    const state = {
    extent: null,
    searchText: "Restaurant",
    };
    // Query the Calcite Chip Group component
    const chipGroup = document.querySelector("#search-chip-group");
    // Query the Popup component
    const popupElement = document.querySelector("arcgis-popup");
    // Query the Map component
    const viewElement = document.querySelector("arcgis-map");
    // Create a graphics layer to display places
    const placesLayer = new GraphicsLayer({
    id: "places-layer",
    title: "Places Layer",
    });
    // Initialize the map and add the places layer
    viewElement.map = new Map({
    basemap: "arcgis/navigation",
    layers: [placesLayer],
    });
    // Configure the maps's zoom constraints
    await viewElement.viewOnReady();
    viewElement.constraints.maxZoom = 20;
    viewElement.constraints.minZoom = 12;
    // Event listener for chip group selection changes to update search text, close any visible popup and query places
    chipGroup.addEventListener("calciteChipGroupSelect", () => {
    if (chipGroup.selectedItems.length > 0) {
    state.searchText = chipGroup.selectedItems[0].value;
    if (popupElement.visible) {
    popupElement.visible = false;
    }
    queryPlaces();
    }
    });
    // Event listener for map view changes to update extent and query places
    viewElement.addEventListener("arcgisViewChange", () => {
    state.extent = viewElement.extent;
    queryPlaces();
    });
    57 collapsed lines
    // Function to add graphics to the places layer based on query results
    function addGraphics(response) {
    // Using the JavaScript map method, convert each place result to a graphic
    const graphics = response.results.map((result) => {
    const categories = result.categories.map((category) => `${category.label}`).join(", ");
    const graphic = new Graphic({
    attributes: {
    categories: categories,
    distance: result.distance,
    name: result.name,
    placeId: result.placeId,
    },
    geometry: result.location,
    symbol: new PictureMarkerSymbol({
    url: result.icon.url,
    width: 24,
    height: 24,
    }),
    });
    return graphic;
    });
    // Remove existing graphics and add the new ones to the places layer
    placesLayer.removeAll();
    placesLayer.addMany(graphics);
    }
    // Function to query places within the current extent
    async function queryPlaces() {
    // Ensure extent and search text are defined
    if (!state.extent || !state.searchText) {
    return;
    }
    // Perform the places queryPlacesWithinExtent request
    try {
    const response = await places.queryPlacesWithinExtent(
    new PlacesQueryParameters({
    extent: state.extent,
    icon: "png",
    searchText: state.searchText,
    }),
    );
    addGraphics(response);
    } catch (error) {
    console.error("Error querying places within extent:", error);
    }
    }
    </script>
    </body>
    </html>
  3. Test your app: Select a category chip and verify that place results appear on the map within the current extent. Pan or zoom the map to confirm that results update automatically for the new visible area.

Display a popup

  1. Create a new asynchronous function named fetchPlaceDetails to retrieve detailed information for a selected place:

    • Accept a place ID as an argument.
    • Use the places service’s fetchPlace method to request additional details, such as address and phone number, for the specified place.
    • Return the response so it can be used to enrich the graphic’s attributes and display in the popup.
    147 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Display a map</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.0/"></script>
    <style>
    calcite-chip {
    --calcite-chip-background-color: var(--calcite-color-foreground-1);
    }
    </style>
    </head>
    <body>
    <calcite-shell class="calcite-mode-auto">
    <arcgis-map center="-122.33, 47.61" popup-disabled zoom="14">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <calcite-chip-group id="search-chip-group" label="Category" selection-mode="single-persist" slot="top-right">
    <calcite-chip appearance="outline-fill" label="Restaurants" selected value="Restaurant">
    Restaurants
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Hotels" value="Hotel">
    Hotels
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Museums" value="Museum">
    Museums
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="ATMs" value="ATM"> ATMs </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Breweries" value="Brewery">
    Breweries
    </calcite-chip>
    </calcite-chip-group>
    <arcgis-popup slot="popup"></arcgis-popup>
    </arcgis-map>
    </calcite-shell>
    <script type="module">
    const [
    Graphic,
    GraphicsLayer,
    Map,
    places,
    FetchPlaceParameters,
    PlacesQueryParameters,
    PictureMarkerSymbol,
    ] = await $arcgis.import([
    "@arcgis/core/Graphic.js",
    "@arcgis/core/layers/GraphicsLayer.js",
    "@arcgis/core/Map.js",
    "@arcgis/core/rest/places.js",
    "@arcgis/core/rest/support/FetchPlaceParameters.js",
    "@arcgis/core/rest/support/PlacesQueryParameters.js",
    "@arcgis/core/symbols/PictureMarkerSymbol.js",
    ]);
    // Application state
    const state = {
    extent: null,
    searchText: "Restaurant",
    };
    // Query the Calcite Chip Group component
    const chipGroup = document.querySelector("#search-chip-group");
    // Query the Popup component
    const popupElement = document.querySelector("arcgis-popup");
    // Query the Map component
    const viewElement = document.querySelector("arcgis-map");
    // Create a graphics layer to display places
    const placesLayer = new GraphicsLayer({
    id: "places-layer",
    title: "Places Layer",
    });
    // Initialize the map and add the places layer
    viewElement.map = new Map({
    basemap: "arcgis/navigation",
    layers: [placesLayer],
    });
    // Configure the maps's zoom constraints
    await viewElement.viewOnReady();
    viewElement.constraints.maxZoom = 20;
    viewElement.constraints.minZoom = 12;
    // Event listener for chip group selection changes to update search text, close any visible popup and query places
    chipGroup.addEventListener("calciteChipGroupSelect", () => {
    if (chipGroup.selectedItems.length > 0) {
    state.searchText = chipGroup.selectedItems[0].value;
    if (popupElement.visible) {
    popupElement.visible = false;
    }
    queryPlaces();
    }
    });
    // Event listener for map view changes to update extent and query places
    viewElement.addEventListener("arcgisViewChange", () => {
    state.extent = viewElement.extent;
    queryPlaces();
    });
    // Function to add graphics to the places layer based on query results
    function addGraphics(response) {
    // Using the JavaScript map method, convert each place result to a graphic
    const graphics = response.results.map((result) => {
    const categories = result.categories.map((category) => `${category.label}`).join(", ");
    const graphic = new Graphic({
    attributes: {
    categories: categories,
    distance: result.distance,
    name: result.name,
    placeId: result.placeId,
    },
    geometry: result.location,
    symbol: new PictureMarkerSymbol({
    url: result.icon.url,
    width: 24,
    height: 24,
    }),
    });
    return graphic;
    });
    // Remove existing graphics and add the new ones to the places layer
    placesLayer.removeAll();
    placesLayer.addMany(graphics);
    }
    // Function to fetch detailed information about a place
    async function fetchPlaceDetails(placeId) {
    const response = await places.fetchPlace(
    new FetchPlaceParameters({
    placeId: placeId,
    requestedFields: ["address", "contactInfo:telephone"],
    }),
    );
    return response;
    }
    29 collapsed lines
    // Function to query places within the current extent
    async function queryPlaces() {
    // Ensure extent and search text are defined
    if (!state.extent || !state.searchText) {
    return;
    }
    // Perform the places queryPlacesWithinExtent request
    try {
    const response = await places.queryPlacesWithinExtent(
    new PlacesQueryParameters({
    extent: state.extent,
    icon: "png",
    searchText: state.searchText,
    }),
    );
    addGraphics(response);
    } catch (error) {
    console.error("Error querying places within extent:", error);
    }
    }
    </script>
    </body>
    </html>
  2. Add an enrichGraphicAttributes function to update the graphic with detailed place information:

    • Accept a Graphic and the details returned from the fetchPlaceDetails function as arguments.
    • Update the graphic’s attributes with the address and phone number fields from the details response.
    • This ensures the popup displays complete information when a place is selected.
    147 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Display a map</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.0/"></script>
    <style>
    calcite-chip {
    --calcite-chip-background-color: var(--calcite-color-foreground-1);
    }
    </style>
    </head>
    <body>
    <calcite-shell class="calcite-mode-auto">
    <arcgis-map center="-122.33, 47.61" popup-disabled zoom="14">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <calcite-chip-group id="search-chip-group" label="Category" selection-mode="single-persist" slot="top-right">
    <calcite-chip appearance="outline-fill" label="Restaurants" selected value="Restaurant">
    Restaurants
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Hotels" value="Hotel">
    Hotels
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Museums" value="Museum">
    Museums
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="ATMs" value="ATM"> ATMs </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Breweries" value="Brewery">
    Breweries
    </calcite-chip>
    </calcite-chip-group>
    <arcgis-popup slot="popup"></arcgis-popup>
    </arcgis-map>
    </calcite-shell>
    <script type="module">
    const [
    Graphic,
    GraphicsLayer,
    Map,
    places,
    FetchPlaceParameters,
    PlacesQueryParameters,
    PictureMarkerSymbol,
    ] = await $arcgis.import([
    "@arcgis/core/Graphic.js",
    "@arcgis/core/layers/GraphicsLayer.js",
    "@arcgis/core/Map.js",
    "@arcgis/core/rest/places.js",
    "@arcgis/core/rest/support/FetchPlaceParameters.js",
    "@arcgis/core/rest/support/PlacesQueryParameters.js",
    "@arcgis/core/symbols/PictureMarkerSymbol.js",
    ]);
    // Application state
    const state = {
    extent: null,
    searchText: "Restaurant",
    };
    // Query the Calcite Chip Group component
    const chipGroup = document.querySelector("#search-chip-group");
    // Query the Popup component
    const popupElement = document.querySelector("arcgis-popup");
    // Query the Map component
    const viewElement = document.querySelector("arcgis-map");
    // Create a graphics layer to display places
    const placesLayer = new GraphicsLayer({
    id: "places-layer",
    title: "Places Layer",
    });
    // Initialize the map and add the places layer
    viewElement.map = new Map({
    basemap: "arcgis/navigation",
    layers: [placesLayer],
    });
    // Configure the maps's zoom constraints
    await viewElement.viewOnReady();
    viewElement.constraints.maxZoom = 20;
    viewElement.constraints.minZoom = 12;
    // Event listener for chip group selection changes to update search text, close any visible popup and query places
    chipGroup.addEventListener("calciteChipGroupSelect", () => {
    if (chipGroup.selectedItems.length > 0) {
    state.searchText = chipGroup.selectedItems[0].value;
    if (popupElement.visible) {
    popupElement.visible = false;
    }
    queryPlaces();
    }
    });
    // Event listener for map view changes to update extent and query places
    viewElement.addEventListener("arcgisViewChange", () => {
    state.extent = viewElement.extent;
    queryPlaces();
    });
    // Function to add graphics to the places layer based on query results
    function addGraphics(response) {
    // Using the JavaScript map method, convert each place result to a graphic
    const graphics = response.results.map((result) => {
    const categories = result.categories.map((category) => `${category.label}`).join(", ");
    const graphic = new Graphic({
    attributes: {
    categories: categories,
    distance: result.distance,
    name: result.name,
    placeId: result.placeId,
    },
    geometry: result.location,
    symbol: new PictureMarkerSymbol({
    url: result.icon.url,
    width: 24,
    height: 24,
    }),
    });
    return graphic;
    });
    // Remove existing graphics and add the new ones to the places layer
    placesLayer.removeAll();
    placesLayer.addMany(graphics);
    }
    // Function to enrich graphic attributes with detailed place information
    function enrichGraphicAttributes(graphic, details) {
    graphic.attributes.locality = details?.address?.locality;
    graphic.attributes.postcode = details?.address?.postcode;
    graphic.attributes.region = details?.address?.region;
    graphic.attributes.streetAddress = details?.address?.streetAddress;
    graphic.attributes.telephone = details?.contactInfo?.telephone;
    }
    40 collapsed lines
    // Function to fetch detailed information about a place
    async function fetchPlaceDetails(placeId) {
    const response = await places.fetchPlace(
    new FetchPlaceParameters({
    placeId: placeId,
    requestedFields: ["address", "contactInfo:telephone"],
    }),
    );
    return response;
    }
    // Function to query places within the current extent
    async function queryPlaces() {
    // Ensure extent and search text are defined
    if (!state.extent || !state.searchText) {
    return;
    }
    // Perform the places queryPlacesWithinExtent request
    try {
    const response = await places.queryPlacesWithinExtent(
    new PlacesQueryParameters({
    extent: state.extent,
    icon: "png",
    searchText: state.searchText,
    }),
    );
    addGraphics(response);
    } catch (error) {
    console.error("Error querying places within extent:", error);
    }
    }
    </script>
    </body>
    </html>
  3. Add a function named createPopupTemplate to format the popup content:

    • Set the popup’s title to the place name.
    • Build an content string using the graphic’s attributes (such as name, address, phone number, and categories).
    • Return the popup template object to be used when displaying place details.
    156 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Display a map</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.0/"></script>
    <style>
    calcite-chip {
    --calcite-chip-background-color: var(--calcite-color-foreground-1);
    }
    </style>
    </head>
    <body>
    <calcite-shell class="calcite-mode-auto">
    <arcgis-map center="-122.33, 47.61" popup-disabled zoom="14">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <calcite-chip-group id="search-chip-group" label="Category" selection-mode="single-persist" slot="top-right">
    <calcite-chip appearance="outline-fill" label="Restaurants" selected value="Restaurant">
    Restaurants
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Hotels" value="Hotel">
    Hotels
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Museums" value="Museum">
    Museums
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="ATMs" value="ATM"> ATMs </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Breweries" value="Brewery">
    Breweries
    </calcite-chip>
    </calcite-chip-group>
    <arcgis-popup slot="popup"></arcgis-popup>
    </arcgis-map>
    </calcite-shell>
    <script type="module">
    const [
    Graphic,
    GraphicsLayer,
    Map,
    places,
    FetchPlaceParameters,
    PlacesQueryParameters,
    PictureMarkerSymbol,
    ] = await $arcgis.import([
    "@arcgis/core/Graphic.js",
    "@arcgis/core/layers/GraphicsLayer.js",
    "@arcgis/core/Map.js",
    "@arcgis/core/rest/places.js",
    "@arcgis/core/rest/support/FetchPlaceParameters.js",
    "@arcgis/core/rest/support/PlacesQueryParameters.js",
    "@arcgis/core/symbols/PictureMarkerSymbol.js",
    ]);
    // Application state
    const state = {
    extent: null,
    searchText: "Restaurant",
    };
    // Query the Calcite Chip Group component
    const chipGroup = document.querySelector("#search-chip-group");
    // Query the Popup component
    const popupElement = document.querySelector("arcgis-popup");
    // Query the Map component
    const viewElement = document.querySelector("arcgis-map");
    // Create a graphics layer to display places
    const placesLayer = new GraphicsLayer({
    id: "places-layer",
    title: "Places Layer",
    });
    // Initialize the map and add the places layer
    viewElement.map = new Map({
    basemap: "arcgis/navigation",
    layers: [placesLayer],
    });
    // Configure the maps's zoom constraints
    await viewElement.viewOnReady();
    viewElement.constraints.maxZoom = 20;
    viewElement.constraints.minZoom = 12;
    // Event listener for chip group selection changes to update search text, close any visible popup and query places
    chipGroup.addEventListener("calciteChipGroupSelect", () => {
    if (chipGroup.selectedItems.length > 0) {
    state.searchText = chipGroup.selectedItems[0].value;
    if (popupElement.visible) {
    popupElement.visible = false;
    }
    queryPlaces();
    }
    });
    // Event listener for map view changes to update extent and query places
    viewElement.addEventListener("arcgisViewChange", () => {
    state.extent = viewElement.extent;
    queryPlaces();
    });
    // Function to add graphics to the places layer based on query results
    function addGraphics(response) {
    // Using the JavaScript map method, convert each place result to a graphic
    const graphics = response.results.map((result) => {
    const categories = result.categories.map((category) => `${category.label}`).join(", ");
    const graphic = new Graphic({
    attributes: {
    categories: categories,
    distance: result.distance,
    name: result.name,
    placeId: result.placeId,
    },
    geometry: result.location,
    symbol: new PictureMarkerSymbol({
    url: result.icon.url,
    width: 24,
    height: 24,
    }),
    });
    return graphic;
    });
    // Remove existing graphics and add the new ones to the places layer
    placesLayer.removeAll();
    placesLayer.addMany(graphics);
    }
    // Function to enrich graphic attributes with detailed place information
    function enrichGraphicAttributes(graphic, details) {
    graphic.attributes.locality = details?.address?.locality;
    graphic.attributes.postcode = details?.address?.postcode;
    graphic.attributes.region = details?.address?.region;
    graphic.attributes.streetAddress = details?.address?.streetAddress;
    graphic.attributes.telephone = details?.contactInfo?.telephone;
    }
    // Function to create a popup template for a place
    function createPopupTemplate() {
    return {
    title: "{name}",
    content: `
    <strong>Address:</strong> {streetAddress}, {locality}, {region} {postcode}<br/>
    <strong>Phone:</strong> {telephone}<br/>
    <strong>Categories:</strong> {categories}
    `,
    };
    }
    40 collapsed lines
    // Function to fetch detailed information about a place
    async function fetchPlaceDetails(placeId) {
    const response = await places.fetchPlace(
    new FetchPlaceParameters({
    placeId: placeId,
    requestedFields: ["address", "contactInfo:telephone"],
    }),
    );
    return response;
    }
    // Function to query places within the current extent
    async function queryPlaces() {
    // Ensure extent and search text are defined
    if (!state.extent || !state.searchText) {
    return;
    }
    // Perform the places queryPlacesWithinExtent request
    try {
    const response = await places.queryPlacesWithinExtent(
    new PlacesQueryParameters({
    extent: state.extent,
    icon: "png",
    searchText: state.searchText,
    }),
    );
    addGraphics(response);
    } catch (error) {
    console.error("Error querying places within extent:", error);
    }
    }
    </script>
    </body>
    </html>
  4. Add an event listener to the Map’s arcgisViewClick event to display place details in a popup:

    • In the event handler, use a hitTest to check if a places graphic was clicked.
    • For each graphic returned by the hit test:
      • Use fetchPlaceDetails to request additional details for the place.
      • Use enrichGraphicAttributes to update the graphic with the new details.
      • Use createPopupTemplate to format the popup content for the graphic.
    • Use Promise.all and map to process all graphics asynchronously.
    • If any errors occur, log them to the console and set the graphic to null.
    • Use filter to remove any null graphics from the array.
    • If there are valid graphics, set the popup’s location to the clicked map point and display the enriched graphic’s attributes in the popup.
    119 collapsed lines
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <title>ArcGIS Maps SDK for JavaScript Tutorials: Display a map</title>
    <script>
    var esriConfig = {
    apiKey: "YOUR_ACCESS_TOKEN",
    };
    </script>
    <!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
    <script type="module" src="https://js.arcgis.com/5.0/"></script>
    <style>
    calcite-chip {
    --calcite-chip-background-color: var(--calcite-color-foreground-1);
    }
    </style>
    </head>
    <body>
    <calcite-shell class="calcite-mode-auto">
    <arcgis-map center="-122.33, 47.61" popup-disabled zoom="14">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
    <calcite-chip-group id="search-chip-group" label="Category" selection-mode="single-persist" slot="top-right">
    <calcite-chip appearance="outline-fill" label="Restaurants" selected value="Restaurant">
    Restaurants
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Hotels" value="Hotel">
    Hotels
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Museums" value="Museum">
    Museums
    </calcite-chip>
    <calcite-chip appearance="outline-fill" label="ATMs" value="ATM"> ATMs </calcite-chip>
    <calcite-chip appearance="outline-fill" label="Breweries" value="Brewery">
    Breweries
    </calcite-chip>
    </calcite-chip-group>
    <arcgis-popup slot="popup"></arcgis-popup>
    </arcgis-map>
    </calcite-shell>
    <script type="module">
    const [
    Graphic,
    GraphicsLayer,
    Map,
    places,
    FetchPlaceParameters,
    PlacesQueryParameters,
    PictureMarkerSymbol,
    ] = await $arcgis.import([
    "@arcgis/core/Graphic.js",
    "@arcgis/core/layers/GraphicsLayer.js",
    "@arcgis/core/Map.js",
    "@arcgis/core/rest/places.js",
    "@arcgis/core/rest/support/FetchPlaceParameters.js",
    "@arcgis/core/rest/support/PlacesQueryParameters.js",
    "@arcgis/core/symbols/PictureMarkerSymbol.js",
    ]);
    // Application state
    const state = {
    extent: null,
    searchText: "Restaurant",
    };
    // Query the Calcite Chip Group component
    const chipGroup = document.querySelector("#search-chip-group");
    // Query the Popup component
    const popupElement = document.querySelector("arcgis-popup");
    // Query the Map component
    const viewElement = document.querySelector("arcgis-map");
    // Create a graphics layer to display places
    const placesLayer = new GraphicsLayer({
    id: "places-layer",
    title: "Places Layer",
    });
    // Initialize the map and add the places layer
    viewElement.map = new Map({
    basemap: "arcgis/navigation",
    layers: [placesLayer],
    });
    // Configure the maps's zoom constraints
    await viewElement.viewOnReady();
    viewElement.constraints.maxZoom = 20;
    viewElement.constraints.minZoom = 12;
    // Event listener for chip group selection changes to update search text, close any visible popup and query places
    chipGroup.addEventListener("calciteChipGroupSelect", () => {
    if (chipGroup.selectedItems.length > 0) {
    state.searchText = chipGroup.selectedItems[0].value;
    if (popupElement.visible) {
    popupElement.visible = false;
    }
    queryPlaces();
    }
    });
    // Event listener for map view changes to update extent and query places
    viewElement.addEventListener("arcgisViewChange", () => {
    state.extent = viewElement.extent;
    queryPlaces();
    });
    // Event listener for map clicks to show place details in a popup
    viewElement.addEventListener("arcgisViewClick", async (event) => {
    // Perform a hit test to identify clicked place graphics
    const hitTestResult = await viewElement.hitTest(event.detail, {
    include: [placesLayer],
    });
    // Process each hit test result to fetch and enrich place details
    const graphics = (
    await Promise.all(
    hitTestResult.results.map(async (result) => {
    if ("graphic" in result && result.graphic) {
    try {
    const details = await fetchPlaceDetails(result.graphic.attributes.placeId);
    enrichGraphicAttributes(result.graphic, details.placeDetails);
    result.graphic.popupTemplate = createPopupTemplate();
    return result.graphic;
    } catch (error) {
    console.error("Error fetching place details:", error);
    return null;
    }
    }
    return null;
    }),
    )
    ).filter((graphic) => graphic !== null);
    // Open the popup if there are any graphics with details
    if (graphics.length > 0) {
    popupElement.open({
    location: event.detail.mapPoint,
    features: graphics,
    });
    }
    });
    89 collapsed lines
    // Function to add graphics to the places layer based on query results
    function addGraphics(response) {
    // Using the JavaScript map method, convert each place result to a graphic
    const graphics = response.results.map((result) => {
    const categories = result.categories.map((category) => `${category.label}`).join(", ");
    const graphic = new Graphic({
    attributes: {
    categories: categories,
    distance: result.distance,
    name: result.name,
    placeId: result.placeId,
    },
    geometry: result.location,
    symbol: new PictureMarkerSymbol({
    url: result.icon.url,
    width: 24,
    height: 24,
    }),
    });
    return graphic;
    });
    // Remove existing graphics and add the new ones to the places layer
    placesLayer.removeAll();
    placesLayer.addMany(graphics);
    }
    // Function to enrich graphic attributes with detailed place information
    function enrichGraphicAttributes(graphic, details) {
    graphic.attributes.locality = details?.address?.locality;
    graphic.attributes.postcode = details?.address?.postcode;
    graphic.attributes.region = details?.address?.region;
    graphic.attributes.streetAddress = details?.address?.streetAddress;
    graphic.attributes.telephone = details?.contactInfo?.telephone;
    }
    // Function to create a popup template for a place
    function createPopupTemplate() {
    return {
    title: "{name}",
    content: `
    <strong>Address:</strong> {streetAddress}, {locality}, {region} {postcode}<br/>
    <strong>Phone:</strong> {telephone}<br/>
    <strong>Categories:</strong> {categories}
    `,
    };
    }
    // Function to fetch detailed information about a place
    async function fetchPlaceDetails(placeId) {
    const response = await places.fetchPlace(
    new FetchPlaceParameters({
    placeId: placeId,
    requestedFields: ["address", "contactInfo:telephone"],
    }),
    );
    return response;
    }
    // Function to query places within the current extent
    async function queryPlaces() {
    // Ensure extent and search text are defined
    if (!state.extent || !state.searchText) {
    return;
    }
    // Perform the places queryPlacesWithinExtent request
    try {
    const response = await places.queryPlacesWithinExtent(
    new PlacesQueryParameters({
    extent: state.extent,
    icon: "png",
    searchText: state.searchText,
    }),
    );
    addGraphics(response);
    } catch (error) {
    console.error("Error querying places within extent:", error);
    }
    }
    </script>
    </body>
    </html>

Run the app

In CodePen, run your code to display the map.

Your app should now be fully functional:

  • Select a category chip to search for places in that category within the current map extent.
  • Pan or zoom the map to update the search results automatically for the new visible area.
  • Click a place graphic to display a popup with detailed information, which is fetched only for the selected place to minimize requests.

What’s next?

Learn how to use additional SDK features and ArcGIS services in these tutorials: