| name | arcgis-interaction |
| description | Handle user interaction with map features including popups, editing, sketching, and event handling. Use for creating interactive map applications with feature selection, editing workflows, and custom interactions. |
ArcGIS Interaction
Use this skill when implementing user interactions like popups, editing, sketching, hit testing, and event handling.
Popups
Basic PopupTemplate
const layer = new FeatureLayer({
url: "...",
popupTemplate: {
title: "{name}",
content: "Population: {population}"
}
});
PopupTemplate with Field Formatting
const popupTemplate = {
title: "Feature: {name}",
content: [{
type: "fields",
fieldInfos: [
{
fieldName: "population",
label: "Population",
format: {
digitSeparator: true,
places: 0
}
},
{
fieldName: "date_created",
label: "Created",
format: {
dateFormat: "short-date"
}
},
{
fieldName: "area_sqkm",
label: "Area (km²)",
format: {
places: 2
}
}
]
}]
};
Multiple Content Elements
const popupTemplate = {
title: "{name}",
content: [
{
type: "text",
text: "<b>Description:</b> {description}"
},
{
type: "fields",
fieldInfos: [...]
},
{
type: "media",
mediaInfos: [{
type: "image",
value: {
sourceURL: "{image_url}"
}
}]
},
{
type: "attachments"
}
]
};
Custom Content Function
const popupTemplate = {
title: "{name}",
content: (feature) => {
const div = document.createElement("div");
div.innerHTML = `
<p>Custom content for ${feature.graphic.attributes.name}</p>
<button id="customBtn">Click me</button>
`;
return div;
}
};
Arcade Expressions in Popups
const popupTemplate = {
title: "{name}",
expressionInfos: [{
name: "density",
title: "Population Density",
expression: "Round($feature.population / $feature.area_sqkm, 2)"
}],
content: "Population Density: {expression/density} people/km²"
};
Popup Actions
const measureAction = {
title: "Measure Length",
id: "measure-this",
icon: "measure"
};
const popupTemplate = {
title: "{name}",
content: "{description}",
actions: [measureAction]
};
// Listen for action clicks
import reactiveUtils from "@arcgis/core/core/reactiveUtils.js";
reactiveUtils.on(
() => view.popup,
"trigger-action",
(event) => {
if (event.action.id === "measure-this") {
const geometry = view.popup.selectedFeature.geometry;
// Do something with the geometry
}
}
);
Programmatic Popup Control
// Open popup at location
view.openPopup({
title: "Custom Popup",
content: "Hello World",
location: view.center
});
// Open popup with features
view.openPopup({
features: [graphic1, graphic2],
location: mapPoint
});
// Close popup
view.closePopup();
// Access popup properties
const selectedFeature = view.popup.selectedFeature;
const isVisible = view.popup.visible;
Popup Component
<arcgis-map basemap="streets-vector">
<arcgis-popup slot="popup"></arcgis-popup>
</arcgis-map>
Hit Testing
Basic Hit Test
view.on("click", async (event) => {
const response = await view.hitTest(event);
if (response.results.length > 0) {
const graphic = response.results[0].graphic;
console.log("Clicked feature:", graphic.attributes);
}
});
Hit Test with Layer Filter
view.on("click", async (event) => {
const response = await view.hitTest(event, {
include: [featureLayer] // Only test this layer
});
// Or exclude layers
const response2 = await view.hitTest(event, {
exclude: [graphicsLayer]
});
});
Pointer Move Hit Test
view.on("pointer-move", async (event) => {
const response = await view.hitTest(event, {
include: featureLayer
});
if (response.results.length > 0) {
document.body.style.cursor = "pointer";
} else {
document.body.style.cursor = "default";
}
});
Highlighting
Highlight Features
const layerView = await view.whenLayerView(featureLayer);
// Highlight a single feature
const highlight = layerView.highlight(graphic);
// Highlight multiple features
const highlight = layerView.highlight([graphic1, graphic2]);
// Highlight by object IDs
const highlight = layerView.highlight([1, 2, 3]);
// Remove highlight
highlight.remove();
Highlight on Click
let highlightHandle;
view.on("click", async (event) => {
// Remove previous highlight
if (highlightHandle) {
highlightHandle.remove();
}
const response = await view.hitTest(event, { include: featureLayer });
if (response.results.length > 0) {
const graphic = response.results[0].graphic;
const layerView = await view.whenLayerView(featureLayer);
highlightHandle = layerView.highlight(graphic);
}
});
Highlight Options
// Set highlight options on the layer view
layerView.highlightOptions = {
color: [255, 255, 0, 1],
haloOpacity: 0.9,
fillOpacity: 0.2
};
Editing
Editor Component (Simplest)
<arcgis-map item-id="YOUR_WEBMAP_ID">
<arcgis-editor slot="top-right"></arcgis-editor>
</arcgis-map>
Editor Widget
import Editor from "@arcgis/core/widgets/Editor.js";
const editor = new Editor({
view: view,
layerInfos: [{
layer: featureLayer,
formTemplate: {
elements: [
{ type: "field", fieldName: "name" },
{ type: "field", fieldName: "description" }
]
}
}]
});
view.ui.add(editor, "top-right");
FeatureForm
import FeatureForm from "@arcgis/core/widgets/FeatureForm.js";
const featureForm = new FeatureForm({
container: "formDiv",
layer: featureLayer,
formTemplate: {
title: "Edit Feature",
elements: [
{
type: "field",
fieldName: "name",
label: "Name"
},
{
type: "field",
fieldName: "type",
label: "Type"
}
]
}
});
// Set feature to edit
featureForm.feature = graphic;
// Listen for submit
featureForm.on("submit", () => {
const values = featureForm.getValues();
// Update feature attributes
Object.keys(values).forEach(key => {
graphic.attributes[key] = values[key];
});
});
// Submit programmatically
featureForm.submit();
applyEdits API
// Add features
const edits = {
addFeatures: [newGraphic]
};
const result = await featureLayer.applyEdits(edits);
console.log("Added:", result.addFeatureResults);
// Update features
const edits = {
updateFeatures: [updatedGraphic]
};
const result = await featureLayer.applyEdits(edits);
// Delete features
const edits = {
deleteFeatures: [graphicToDelete]
};
const result = await featureLayer.applyEdits(edits);
// Combined edits
const edits = {
addFeatures: [newGraphic1, newGraphic2],
updateFeatures: [updatedGraphic],
deleteFeatures: [deleteGraphic]
};
const result = await featureLayer.applyEdits(edits);
Sketching
Sketch Component (Simplest)
<arcgis-map basemap="topo-vector">
<arcgis-sketch slot="top-right" creation-mode="update"></arcgis-sketch>
</arcgis-map>
Sketch Widget
import Sketch from "@arcgis/core/widgets/Sketch.js";
import GraphicsLayer from "@arcgis/core/layers/GraphicsLayer.js";
const graphicsLayer = new GraphicsLayer();
map.add(graphicsLayer);
const sketch = new Sketch({
view: view,
layer: graphicsLayer,
creationMode: "update" // or "single", "continuous"
});
view.ui.add(sketch, "top-right");
// Listen for events
sketch.on("create", (event) => {
if (event.state === "complete") {
console.log("Created:", event.graphic);
}
});
sketch.on("update", (event) => {
if (event.state === "complete") {
console.log("Updated:", event.graphics);
}
});
sketch.on("delete", (event) => {
console.log("Deleted:", event.graphics);
});
Draw Tool (Low-level)
import Draw from "@arcgis/core/views/draw/Draw.js";
const draw = new Draw({ view: view });
// Create a polygon
const action = draw.create("polygon");
action.on("vertex-add", (event) => {
console.log("Vertex added:", event.vertices);
});
action.on("draw-complete", (event) => {
const polygon = {
type: "polygon",
rings: event.vertices,
spatialReference: view.spatialReference
};
// Create graphic with polygon
});
Event Handling
View Events
// Click
view.on("click", (event) => {
console.log("Map point:", event.mapPoint);
console.log("Screen point:", event.x, event.y);
});
// Double-click
view.on("double-click", (event) => {
event.stopPropagation(); // Prevent default zoom
});
// Pointer move
view.on("pointer-move", (event) => {
const point = view.toMap(event);
console.log("Coordinates:", point.longitude, point.latitude);
});
// Drag
view.on("drag", (event) => {
if (event.action === "start") { }
if (event.action === "update") { }
if (event.action === "end") { }
});
// Key events
view.on("key-down", (event) => {
if (event.key === "Escape") {
// Cancel operation
}
});
Property Watching
// Watch single property
view.watch("zoom", (newZoom) => {
console.log("Zoom changed to:", newZoom);
});
// Watch multiple properties
view.watch(["center", "zoom"], ([center, zoom]) => {
console.log("View changed:", center, zoom);
});
// Watch stationary (after navigation completes)
view.watch("stationary", (isStationary) => {
if (isStationary) {
console.log("Navigation complete");
}
});
// One-time watch
import { when } from "@arcgis/core/core/reactiveUtils.js";
await when(() => view.stationary === true);
console.log("View is now stationary");
Layer Events
// Layer view updating
const layerView = await view.whenLayerView(featureLayer);
layerView.watch("updating", (updating) => {
if (updating) {
console.log("Layer is updating...");
} else {
console.log("Layer update complete");
}
});
Widget Events
// Search widget
searchWidget.on("select-result", (event) => {
console.log("Selected:", event.result);
});
// Sketch widget
sketchWidget.on("create", (event) => {
if (event.state === "complete") {
console.log("Sketch complete");
}
});
Coordinate Conversion
// Screen to map coordinates
const mapPoint = view.toMap({ x: screenX, y: screenY });
// Map to screen coordinates
const screenPoint = view.toScreen(mapPoint);
TypeScript Usage
Popup and symbol configurations use autocasting with type properties. For TypeScript safety, use as const:
// Use 'as const' for popup content types
layer.popupTemplate = {
title: "{name}",
content: [{
type: "fields",
fieldInfos: [
{ fieldName: "name", label: "Name" }
]
}]
} as const;
// Use 'as const' for symbol configurations
const graphic = new Graphic({
geometry: point,
symbol: {
type: "simple-marker",
color: "red",
size: 12
} as const
});
Tip: See arcgis-core-maps skill for detailed guidance on autocasting vs explicit classes.
Common Pitfalls
Popup not showing: Ensure layer has
popupEnabled: true(default)Hit test returns nothing: Check if layers are included/excluded correctly
Highlight not visible: Make sure to store the highlight handle and call
remove()before creating new highlightsapplyEdits fails: Ensure layer is editable and user has edit permissions
Events fire multiple times: Remove event handlers when no longer needed