Learn to craft an engaging, interactive experience with Calcite Components and ArcGIS Maps SDK for JavaScript.
Using core web component concepts such as slots, events, and attributes, you will build an interface using Calcite Components and data provided by ArcGIS Maps SDK for JavaScript.
This tutorial will provide an overview of foundational Calcite Component and web component concepts. You will:
-
Scaffold an interface with
calcite-shell
and related components. -
Add a map with a FeatureLayer containing content.
-
Use Calcite Components to display data provided by the
query
method.Top Features() -
Add interactivity to the map and data using the concepts of events and attributes.
-
Customize the look and feel using mode and color tokens.
This tutorial leverages vanilla JavaScript, but these concepts and methods of interaction are applicable across frameworks.
Prerequisites
Steps
Create a new pen
- Go to CodePen to create a new pen for your mapping application.
Add HTML
- In CodePen > HTML, add HTML and CSS to create a page with a
view
element which will display the map. The CSS ensures that the map is the full width and height of the browser window.Div
The <!
tag is not required in CodePen. If you are using a different editor or running the page on a local server, be sure to add this tag to the top of your HTML page.
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<title>Calcite Components: Core concepts</title>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<div id="viewDiv"></div>
</body>
<script>
</script>
</html>
- In the
<head
element, add references to Calcite Components and ArcGIS Maps SDK for JavaScript.>
<script type="module" src="https://js.arcgis.com/calcite-components/2.13.2/calcite.esm.js"></script>
<link rel="stylesheet" type="text/css" href="https://js.arcgis.com/calcite-components/2.13.2/calcite.css" />
<script src="https://js.arcgis.com/4.31/"></script>
<link rel="stylesheet" href="https://js.arcgis.com/4.31/esri/themes/light/main.css" />
Import modules
- In the
<script
element, import the ArcGIS Maps SDK for JavaScript modules that you will use in this application.>
<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/layers/FeatureLayer",
"esri/symbols/WebStyleSymbol",
"esri/Basemap",
"esri/rest/support/TopFeaturesQuery",
"esri/rest/support/TopFilter",
"esri/widgets/Home"
], (Map, MapView, FeatureLayer, WebStyleSymbol, Basemap, TopFeaturesQuery, TopFilter, Home) => (async () => { })());
</script>
</html>
Use an API key
An API key is required to access ArcGIS services if you are using a developer account. You can skip this step if you have an account associated with an ArcGIS Online organization.
- Go to your developer dashboard to get an API key.
- Back in CodePen >
<script
, import the> esri
class.Config - Set the
api
property.Key
"esri/widgets/Home",
"esri/config"
], function (Map, MapView, FeatureLayer, WebStyleSymbol, Basemap, TopFeaturesQuery, TopFilter, Home, esriConfig) => (async () => {
esriConfig.apiKey = "YOUR_API_KEY";
Scaffold the application
Next, you will add Calcite Components to scaffold the application.
- The
calcite-shell
component serves as the application frame. Additionally, notice that thecalcite-shell-panel
, andcalcite-panel
components have been added.
These layout components help organize content and other components in predictable and repeatable ways, and can be configured to accommodate many desired layouts and use cases.
<body>
<calcite-shell>
<calcite-shell-panel slot="panel-start">
<calcite-panel heading="National Park Visitation"> </calcite-panel>
</calcite-shell-panel>
<div id="viewDiv"></div>
</calcite-shell>
</body>
- Pause to understand some core web component concepts in the snippet you added to the application:
- The
panel-start
slot you added to thecalcite-shell-panel
. Defined slots such as this can provide styling or positioning for slotted content or components, making common patterns simple to construct. - The
heading
attribute has been added to thecalcite-panel
component and populated with the name of the application. Using component attributes to add text helps consistently position and style content.
- The
Display a map
- Next, you will add the ArcGIS Maps SDK for JavaScript code, based on the Query top features from a FeatureLayer tutorial.
], (Map, MapView, FeatureLayer, WebStyleSymbol, Basemap, TopFeaturesQuery, TopFilter, Home) =>
(async () => {
const layer = new FeatureLayer({
url: "https://services.arcgis.com/V6ZHFr6zdgNZuVG0/arcgis/rest/services/US_National_Parks_Annual_Visitation/FeatureServer/0",
outFields: ["*"],
renderer: await setRenderer(),
popupTemplate: createPopupTemplate()
});
const map = new Map({
basemap: "streets-navigation-vector",
layers: [layer]
});
const view = new MapView({
container: "viewDiv",
map: map,
center: [-120, 45],
zoom: 3
});
let homeWidget = new Home({
view: view
});
view.ui.add(homeWidget, "top-right");
view.ui.move("zoom", "top-right");
const layerView = await view.whenLayerView(layer);
async function setRenderer() {
const symbol = new WebStyleSymbol({
name: "park",
styleName: "Esri2DPointSymbolsStyle"
});
const cimSymbol = await symbol.fetchCIMSymbol();
cimSymbol.data.symbol.symbolLayers[0].size = 24;
cimSymbol.data.symbol.symbolLayers[1].size = 0;
return {
type: "simple",
symbol: cimSymbol
};
}
function createPopupTemplate() {
return {
title: "{Park}",
content: [
{
type: "fields",
fieldInfos: [
{
fieldName: "F2022",
label: "2022",
format: { digitSeparator: true }
},
{
fieldName: "F2021",
label: "2021",
format: { digitSeparator: true }
},
{
fieldName: "F2020",
label: "2020",
format: { digitSeparator: true }
},
{
fieldName: "F2019",
label: "2019",
format: { digitSeparator: true }
}
At this point the map is visible behind the calcite-shell-panel
you added. Click around the map points and see the data displayed in popups. This configuration is not specific to Calcite Components - you can view the original tutorial for more context.
- Add a function to filter the data on the map. The
filter
function contains aItems() query
method, which returns a set of results based on a set of filter options. Notice that we have assigned some default values to variables that will later be made configurable in the application.Top Features()
format: { digitSeparator: true }
}
]
}
]
};
}
const countDefault = 1;
const orderByDefault = "DESC";
const yearDefault = "F2022";
let count = countDefault;
let orderBy = orderByDefault;
let year = yearDefault;
async function filterItems() {
const query = new TopFeaturesQuery({
topFilter: new TopFilter({
topCount: count,
groupByFields: ["State"],
orderByFields: `${year} ${orderBy}`
}),
orderByFields: `${year} ${orderBy}`,
outFields: ["State, F2022, F2021, F2020, F2019, Park"],
returnGeometry: true,
cacheHint: false
});
query.orderByFields = [""];
Add the results list
Next, you will create a list using the results returned from ArcGIS Maps SDK for JavaScript. Clicking on the result items will show an associated popup on the map.
- Add a
calcite-block
with anid
attribute. Inside, place acalcite-list
, again with anid
attribute, that will be used for appending content. Note thecollapsible
andheading
attributes used to customize the component.
<calcite-shell-panel slot="panel-start">
<calcite-panel heading="National Park Visitation">
<calcite-block collapsible heading="Results" id="result-block">
<calcite-list id="result-list"></calcite-list>
</calcite-block>
</calcite-panel>
</calcite-shell-panel>
- Next, add logic to create a
calcite-list-item
for each displayed item in the FeatureLayer.
topCount: count,
groupByFields: ["State"],
orderByFields: `${year} ${orderBy}`
}),
orderByFields: `${year} ${orderBy}`,
outFields: ["State, F2022, F2021, F2020, F2019, Park"],
returnGeometry: true,
cacheHint: false
});
document.getElementById("result-list").innerHTML = "";
document.getElementById("result-block").open = true;
const results = await layer.queryTopFeatures(query);
graphics = results.features;
graphics.forEach((result, index) => {
const attributes = result.attributes;
const item = document.createElement("calcite-list-item");
const chip = document.createElement("calcite-chip");
chip.value = attributes.State;
chip.slot = "content-end";
chip.scale = "s";
chip.innerText = attributes.State;
item.label = attributes.Park;
item.value = index;
item.description = `${attributes[year].toLocaleString()} visitors`;
For each graphic, you are programmatically creating a calcite-list-item
, assigning property values to customize the display, and appending to the result container reference you added earlier. You are also creating a calcite-chip
with the state name of each result, and placing it in a slot.
- An event listener was added to each
calcite-list-item
created in the previous step. Create theresult
function, which will display a ArcGIS Maps SDK for JavaScript popup.Click Handler()
item.addEventListener("click", () => resultClickHandler(result, index));
item.appendChild(chip);
document.getElementById("result-list").appendChild(item);
});
query.orderByFields = [""];
const objectIds = await layer.queryTopObjectIds(query);
layerView.filter = { objectIds };
}
function resultClickHandler(result, index) {
const popup = graphics && graphics[parseInt(index, 10)];
if (popup) {
view.popup.open({
Build the filters
- Add another
calcite-block
component. Inside, place a set of controls that will adjust the parameters of thequery
query. Each control is wrapped in aTop Features() calcite-label
.
<calcite-shell-panel slot="panel-start">
<calcite-panel heading="National Park Visitation">
<calcite-block heading="Filters" open>
<calcite-label>
Data type, per state
<calcite-segmented-control id="control-visited-type-el" width="full">
<calcite-segmented-control-item value="DESC" checked>Most visited</calcite-segmented-control-item>
<calcite-segmented-control-item value="ASC">Least visited</calcite-segmented-control-item>
</calcite-segmented-control>
</calcite-label>
<calcite-label>
Year data to display
<calcite-select id="control-year-el">
<calcite-option label="2022" value="F2022"></calcite-option>
<calcite-option label="2021" value="F2021"></calcite-option>
<calcite-option label="2020" value="F2020"></calcite-option>
<calcite-option label="2019" value="F2019"></calcite-option>
</calcite-select>
</calcite-label>
<calcite-label>
Max parks per state
<calcite-slider id="control-count-per-state-el" label-ticks ticks="1" min="1" max="5"></calcite-slider>
</calcite-label>
</calcite-block>
- Note that you have assigned
id
attributes to thecalcite-segmented-control
,calcite-select
, andcalcite-slider
. You will use these references to set up event listeners to determine when to update the results.
{
fieldName: "F2019",
label: "2019",
format: { digitSeparator: true }
}
]
}
]
};
}
const controlVisitedTypeEl = document.getElementById("control-visited-type-el");
const controlYearEl = document.getElementById("control-year-el");
const controlCountPerStateEl = document.getElementById("control-count-per-state-el");
Note that each event listener references a custom Calcite Components event: calcite
, calcite
, and calcite
.
When that event is fired, you will set the related local variable, and run the filter
function. In a production environment, you might make use of more granular filtering functions to improve performance.
Create a reset action
Because you added events that allowed a user to change the view from the default filter parameters, it will be useful to add a way to reset the options.
- Add a
calcite-action
andcalcite-tooltip
using slots.
<calcite-block heading="Filters" open>
<div slot="control">
<calcite-action disabled icon="reset" id="control-reset-el"></calcite-action>
<calcite-tooltip reference-element="control-reset-el" placement="bottom">
Reset to defaults
</calcite-tooltip>
</div>
<calcite-label>
You may notice some concepts used in previous tutorial steps. Take note of the icon
attribute on the calcite-action
. Calcite Components often provide helpers for loading Calcite UI Icons - the full set can be explored using the icon search.
The disabled
attribute is also set - you will enable it when the set of parameters a user has created differs from the defaults.
- Add a reference and an event listener to the reset action.
}
]
}
]
};
}
const controlVisitedTypeEl = document.getElementById("control-visited-type-el");
const controlYearEl = document.getElementById("control-year-el");
- Add the functions
determine
, andReset Action State() reset
, where you will determine the state of the reset action, and reset parameters when requested by a user. TheFilters() reset
function will reset the parameters of the query, and reset the controls to their initial display. The action will beFilters() disabled
when the set of filter options matches the initial values, or a user interacts with thecalcite-action
.
determineResetActionState();
}
function resultClickHandler(result, index) {
const popup = graphics && graphics[parseInt(index, 10)];
if (popup) {
view.popup.open({
features: [popup],
location: result.geometry
});
view.goTo({ center: [result.geometry.longitude, result.geometry.latitude], zoom: 4 }, { duration: 400 });
}
}
function determineResetActionState() {
if (count !== countDefault || orderBy !== orderByDefault || year !== yearDefault) {
controlResetEl.removeAttribute("disabled");
controlResetEl.indicator = true;
} else {
controlResetEl.disabled = true;
controlResetEl.removeAttribute("indicator");
}
}
function resetFilters() {
count = countDefault;
- Call the
determine
function in theReset Action State() filter
function.Items()
chip.scale = "s";
chip.innerText = attributes.State;
item.label = attributes.Park;
Theme the application
By combining the functionality of the ArcGIS Maps SDK for JavaScript with Calcite Components, you created a rich interactive experience for your users. To go one step further, you will add styles to create a custom look and feel.
- Add a CSS class to the
calcite-shell
:
<calcite-shell class="calcite-tutorial">
- Target individual components or sections to add custom styles:
- Add custom brand colors to create a unique experience using color and theming tokens.
- Use the custom colors you added to make the slotted
calcite-chip
components stand out. - Add spacing to the slotted
calcite-chip
components.
.calcite-tutorial {
--calcite-color-brand: #039851;
--calcite-color-brand-hover: #008d52;
}
.calcite-tutorial calcite-chip {
--calcite-color-foreground-2: var(--calcite-color-brand);
--calcite-color-text-1: white;
margin-inline-end: 0.75rem;
}
Run the app
In CodePen, run your code to display the map.