<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>MapNotesLayer | Sample | ArcGIS Maps SDK for JavaScript</title>
<!-- Load the ArcGIS Maps SDK for JavaScript from CDN -->
<script type="module" src="https://js.arcgis.com/5.0/"></script>
padding: 0 1rem 1rem 1rem;
<calcite-shell class="calcite-mode-auto">
<arcgis-map center="-111.79, 41.28" zoom="12">
<arcgis-zoom slot="top-left"></arcgis-zoom>
<calcite-tooltip placement="bottom-start" reference-element="draw-point-action">
<calcite-action icon="pin" id="draw-point-action" text="Draw point"></calcite-action>
<calcite-tooltip placement="bottom-start" reference-element="draw-polyline-action">
<span>Draw polyline</span>
id="draw-polyline-action"
text="Draw polyline"></calcite-action>
<calcite-tooltip placement="bottom-start" reference-element="draw-polygon-action">
<span>Draw polygon</span>
text="Draw polygon"></calcite-action>
<calcite-tooltip placement="bottom-start" reference-element="draw-text-action">
<calcite-action icon="text" id="draw-text-action" text="Draw text"></calcite-action>
<calcite-tooltip placement="bottom-start" reference-element="select-action">
<span>Select graphic and edit attributes</span>
text="Select graphic and edit attributes"></calcite-action>
<calcite-tooltip placement="bottom-start" reference-element="delete-action">
<span>Delete all graphics</span>
text="Delete all graphics"></calcite-action>
<calcite-panel hidden id="attribute-panel" slot="top-right">
<calcite-label class="label-padding"
>Edit the title attribute:
<calcite-input-text id="attribute-title-input-text"></calcite-input-text>
<calcite-panel heading="Save WebMap" slot="bottom-right">
<calcite-label class="label-padding"
id="webmap-title-input-text"
value="Map Notes Webmap"></calcite-input-text>
<calcite-button appearance="solid" class="button-padding" id="save-webmap"
<calcite-dialog heading="Delete All Graphics" id="delete-dialog" modal width="s">
<calcite-notice icon id="delete-notice" kind="danger" open>
<div slot="title">All graphics will be deleted</div>
>Are you sure you want to delete all graphics?<br />To delete a single graphic, select
it and press the delete key.</span
<calcite-button appearance="outline" id="delete-cancel-button" slot="footer-end"
id="delete-confirm-button"
<calcite-dialog heading="Saved Webmap Information" id="save-results-dialog" modal width="s">
text="Saving Webmap"></calcite-loader>
<calcite-notice icon id="save-success-notice" kind="success">
<div slot="title">The Webmap has been saved successfully</div>
<calcite-link id="save-success-link" slot="message" target="_blank"></calcite-link>
<calcite-notice icon id="save-error-notice" kind="danger">
<div slot="title">The Webmap could not be saved</div>
<div id="save-error-message" slot="message"></div>
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
] = await $arcgis.import([
"@arcgis/core/layers/MapNotesLayer.js",
"@arcgis/core/portal/PortalItem.js",
"@arcgis/core/symbols/SimpleFillSymbol.js",
"@arcgis/core/symbols/SimpleLineSymbol.js",
"@arcgis/core/symbols/SimpleMarkerSymbol.js",
"@arcgis/core/symbols/TextSymbol.js",
"@arcgis/core/WebMap.js",
"@arcgis/core/widgets/Sketch/SketchViewModel.js",
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
// Tracks the sequential number used to generate unique default titles for new map notes.
// Holds a reference to the SketchViewModel currently being edited,
// used for attribute synchronization and UI state management.
activeSketchViewModel: null,
// -----------------------------------------------------------------------
// HTML Element References
// -----------------------------------------------------------------------
// Panel UI element that allows users to view and edit the title
// of the currently selected map note graphic.
const attributePanel = document.querySelector("#attribute-panel");
// Text input field where users can enter or edit the title of the selected map note graphic.
const attributeTitleInput = document.querySelector("#attribute-title-input-text");
// Action button that initiates the process to delete all map note graphics from the map,
// prompting user confirmation.
const deleteAction = document.querySelector("#delete-action");
// Button that, when clicked, confirms the user's intent to delete all map note graphics
// and executes the deletion action.
const deleteConfirmButton = document.querySelector("#delete-confirm-button");
// Button that allows the user to cancel the deletion process and keep all existing map note graphics.
const deleteCancelButton = document.querySelector("#delete-cancel-button");
// Dialog window that prompts the user to confirm or cancel the deletion of all map note graphics,
// ensuring intentional action.
const deleteDialog = document.querySelector("#delete-dialog");
// Toolbar containing drawing actions for creating new map note graphics
// of various types (point, line, polygon, text).
const drawActionBar = document.querySelector("#draw-action-bar");
// UI control (action button) that enables users to start drawing a new point map note graphic on the map.
const drawPointAction = document.querySelector("#draw-point-action");
// UI control (action button) that enables users to start drawing a new polygon map note graphic on the map.
const drawPolygonAction = document.querySelector("#draw-polygon-action");
// UI control (action button) that enables users to start drawing a new polyline map note graphic on the map.
const drawPolylineAction = document.querySelector("#draw-polyline-action");
// UI control (action button) that enables users to start drawing a new text map note graphic on the map.
const drawTextAction = document.querySelector("#draw-text-action");
// Container (div) for displaying detailed error messages to the user when saving the web map fails.
const saveErrorMessage = document.querySelector("#save-error-message");
// Visual alert (notice) that informs the user when an error occurs during the web map save process.
const saveErrorNotice = document.querySelector("#save-error-notice");
// Visual loading indicator that shows the user when the web map is being saved or processed.
const saveLoader = document.querySelector("#save-loader");
// Dialog window that communicates the outcome of the web map save operation,
// showing success or error details to the user.
const saveResultsDialog = document.querySelector("#save-results-dialog");
// Direct link that allows the user to access and view the newly saved web map
// after a successful save operation.
const saveSuccessLink = document.querySelector("#save-success-link");
// Visual alert (notice) that informs the user when the web map has been saved successfully.
const saveSuccessNotice = document.querySelector("#save-success-notice");
// Button that initiates the process of saving the current web map,
// including user-drawn map notes, to the portal.
const saveWebMapButton = document.querySelector("#save-webmap");
// Action button that enables users to select existing map note graphics on the map
// for editing or updating their attributes.
const selectAction = document.querySelector("#select-action");
// Main interactive map container that displays the web map and all user-created map note graphics.
const viewElement = document.querySelector("arcgis-map");
// Text input field where users specify or edit the title for the web map before saving it to the portal.
const webMapTitleInput = document.querySelector("#webmap-title-input-text");
// -----------------------------------------------------------------------
// MapNotesLayer and Map Setup
// -----------------------------------------------------------------------
// Dedicated layer for storing and managing all user-created map note graphics—including
// points, lines, polygons, and text—on the map.
const mapNotesLayer = new MapNotesLayer();
// Extract individual sublayers for points, polylines, polygons, and text from the MapNotesLayer
// for direct access and management.
const { pointLayer, polylineLayer, polygonLayer, textLayer } = mapNotesLayer;
// Instantiate the main WebMap, configured with a gray vector basemap and the MapNotesLayer
// as its operational layer for displaying user notes.
const webMap = new WebMap({
// Assign the configured WebMap to the map view element,
// making it visible and interactive in the application UI.
viewElement.map = webMap;
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
// Instantiate a dedicated SketchViewModel for each geometry type (point, polyline, polygon, text),
// enabling independent drawing and editing workflows tailored to each map note type.
// The createSketchViewModel factory function encapsulates event handling and UI integration
// for modularity and maintainability.
const pointSketchViewModel = createSketchViewModel({
const polygonSketchViewModel = createSketchViewModel({
action: drawPolygonAction,
const polylineSketchViewModel = createSketchViewModel({
action: drawPolylineAction,
const textSketchViewModel = createSketchViewModel({
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
// Event listener that synchronizes the title input field with the selected map note
// graphic's title attribute whenever the user modifies the input value.
attributeTitleInput.addEventListener("calciteInputTextChange", () => {
if (state.activeSketchViewModel) {
state.activeSketchViewModel.complete();
// Event listener that initiates the deletion workflow, prompting the user to confirm before
// removing all map note graphics from the map.
deleteAction.addEventListener("click", () => {
deleteAction.indicator = true;
deleteDialog.open = true;
// Event listener that executes the deletion of all map note graphics after user confirmation,
// then resets the UI state.
deleteConfirmButton.addEventListener("click", () => {
polylineLayer?.removeAll();
polygonLayer?.removeAll();
deleteDialog.open = false;
// Event listener that aborts the deletion workflow, closes the confirmation dialog,
// and restores the UI to its previous state.
deleteCancelButton.addEventListener("click", () => {
deleteDialog.open = false;
// Event listener that initializes the workflow for creating a new point map note graphic,
// including UI updates and drawing activation.
drawPointAction.addEventListener("click", () => {
// Prepare the application for creating a new map note by clearing active tools,
// disabling editing of existing graphics, and incrementing the note counter
// to assign a unique default title.
// Visually activate the draw point action to indicate to the user that the
// point drawing tool is currently selected.
drawPointAction.indicator = true;
// Begin the drawing interaction by creating a new point graphic on the map,
// using a visually distinct circular simple marker symbol.
pointSketchViewModel.create("point", {
graphicSymbol: new SimpleMarkerSymbol({
outline: new SimpleLineSymbol({
// Event listener that initiates the workflow for creating a new polygon map note graphic,
// including UI updates and drawing tool activation.
drawPolygonAction.addEventListener("click", () => {
// Prepare the application for creating a new polygon map note by clearing active tools,
// disabling editing of existing graphics, and incrementing the note counter
// to assign a unique default title.
// Visually activate the draw polygon action to indicate to the user that the
// polygon drawing tool is currently selected.
drawPolygonAction.indicator = true;
// Begin the drawing interaction by creating a new polygon graphic on the map,
// using a visually distinct simple fill symbol and outline.
polygonSketchViewModel.create("polygon", {
graphicSymbol: new SimpleFillSymbol({
outline: new SimpleLineSymbol({
// Event listener that initiates the workflow for creating a new polyline map note graphic,
// including UI updates and drawing tool activation.
drawPolylineAction.addEventListener("click", () => {
// Prepare the application for creating a new polyline map note by clearing active tools,
// disabling editing of existing graphics, and incrementing the note counter
// to assign a unique default title.
// Visually activate the draw polyline action to indicate to the user that the
// polyline drawing tool is currently selected.
drawPolylineAction.indicator = true;
// Begin the drawing interaction by creating a new polyline graphic on the map,
// using a visually distinct simple line symbol.
polylineSketchViewModel.create("polyline", {
graphicSymbol: new SimpleLineSymbol({
// Event listener that initiates the workflow for creating a new text map note graphic,
// including UI updates and drawing tool activation.
drawTextAction.addEventListener("click", () => {
// Prepare the application for creating a new text map note by clearing active tools,
// disabling editing of existing graphics, and incrementing the note counter
// to assign a unique default title.
// Visually activate the draw text action to indicate to the user that the
// text drawing tool is currently selected.
drawTextAction.indicator = true;
// Begin the drawing interaction by creating a new text graphic on the map,
// using a visually distinct text symbol.
textSketchViewModel.create("point", {
graphicSymbol: new TextSymbol({
family: "Arial Unicode MS",
// Event listener that resets the UI state when the save results dialog is closed,
// preparing the application for further user actions.
saveResultsDialog.addEventListener("calciteDialogClose", () => {
saveLoader.hidden = false;
saveSuccessNotice.open = false;
saveErrorNotice.open = false;
// Event listener that handles the workflow for saving the current WebMap,
// including UI feedback for success or error states.
saveWebMapButton.addEventListener("click", async () => {
// Open the dialog that displays the outcome of the web map save operation,
// providing feedback on success or failure.
saveResultsDialog.open = true;
// Retrieve the user-provided title for the WebMap from the input field,
// or use a default title if none is specified.
const title = webMapTitleInput.value || "Map Notes WebMap";
// Synchronize the WebMap's state with the current map view, ensuring all user changes and
// map notes are included before saving.
await webMap.updateFrom(viewElement.view);
// Create a new PortalItem instance representing the WebMap, including its title and description,
// to prepare it for saving to the portal.
const portalItem = new PortalItem({
"WebMap created with the ArcGIS Maps SDK for JavaScript MapNotesLayer sample.",
// Save the current WebMap as a new PortalItem in the user's portal,
// making it accessible and shareable online.
const saveAsResult = await webMap.saveAs(portalItem);
// Update the success link to provide direct access to the newly saved WebMap,
// setting its URL and display title from the new PortalItem.
saveSuccessLink.href = `${saveAsResult.portal.url}/home/item.html?id=${saveAsResult.id}`;
saveSuccessLink.textContent = saveAsResult.title ?? "";
// Hide the loading indicator and display a success notice to inform the user
// that the WebMap was saved successfully.
saveLoader.hidden = true;
saveSuccessNotice.open = true;
// Display a detailed error message and visual feedback if the WebMap save operation fails,
// helping the user understand what went wrong.
saveErrorMessage.textContent = error.message;
saveLoader.hidden = true;
saveErrorNotice.open = true;
// Event listener that manages the workflow for selecting existing map note graphics and
// enabling their attribute updates, including toggling between selection and editing modes.
selectAction.addEventListener("click", () => {
// If the select action is already active, reset all UI actions and
// disable editing mode to exit selection.
if (selectAction.indicator) {
// Otherwise, reset all UI actions and enable editing mode, allowing the user to select and
// update map note graphics.
selectAction.indicator = true;
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
// Enables update mode on all SketchViewModels, allowing users to select and edit
// any map note graphic by clicking on it.
function allowUpdates() {
pointSketchViewModel.updateOnGraphicClick = true;
polylineSketchViewModel.updateOnGraphicClick = true;
polygonSketchViewModel.updateOnGraphicClick = true;
textSketchViewModel.updateOnGraphicClick = true;
// Sets initial attributes, popup template, and symbol properties
// for a newly created map note graphic based on user input.
function createAttributes(action, event) {
// Only proceed if the graphic creation event has finished and a new graphic exists.
if (event.state === "complete" && event.graphic) {
// Clone the current symbol and update the text if it's a text symbol.
const symbol = event.graphic.symbol?.clone();
if (symbol && symbol.type === "text" && event.graphic) {
symbol.text = attributeTitleInput.value || "";
event.graphic.symbol = symbol;
// Assign the user-provided title as an attribute on the new graphic.
event.graphic.attributes = {
title: attributeTitleInput.value || "",
// Configure the popup to display the graphic's title when clicked.
event.graphic.popupTemplate = {
// Reset the action's visual indicator and close the attribute editing panel.
action.indicator = false;
attributePanel.hidden = true;
// Enable all drawing actions so the user can create or edit additional map notes.
drawActionBar.querySelectorAll("calcite-action").forEach((action) => {
// Factory function to initialize a SketchViewModel for a specific geometry type,
// wiring up creation and update event handlers for map note graphics.
function createSketchViewModel(options) {
// Instantiate a SketchViewModel for the specified layer and view,
// disabling update-on-click by default.
const sketchViewModel = new SketchViewModel({
updateOnGraphicClick: false,
// Attach handlers for create and update events to manage drawing and editing workflows.
sketchViewModel.on("create", (event) => {
// Temporarily disable all drawing actions to prevent user interaction during graphic creation.
drawActionBar.querySelectorAll("calcite-action").forEach((action) => {
createAttributes(options.action, event);
sketchViewModel.on("update", (event) => {
// Temporarily disable all drawing actions to prevent user interaction during graphic updates.
drawActionBar.querySelectorAll("calcite-action").forEach((action) => {
attributePanel.hidden = false;
updateAttributes(sketchViewModel, event);
// Disables update-on-click for all SketchViewModels, preventing users from
// selecting and editing existing graphics.
function disableUpdates() {
pointSketchViewModel.updateOnGraphicClick = false;
polylineSketchViewModel.updateOnGraphicClick = false;
polygonSketchViewModel.updateOnGraphicClick = false;
textSketchViewModel.updateOnGraphicClick = false;
// Prepares the UI and state for drawing a new map note, resetting actions,
// disabling updates, incrementing the note number, and setting a default title.
function initializeNewMapNote() {
// Clear all active drawing states and indicators to prepare for a new map note.
// Prevent editing of existing graphics while starting a new drawing.
// Prevent deletion while drawing a new map note.
deleteAction.disabled = true;
// Advance the map note counter to ensure each new note has a unique default title.
state.currentMapNoteNumber++;
// Assign a unique, descriptive default title to the new map note graphic
// using the updated note number.
attributeTitleInput.value = `Map Note ${state.currentMapNoteNumber}`;
// Display the attribute panel to prompt the user to enter a title and
// details for the new map note graphic.
attributePanel.hidden = false;
// Resets all drawing action indicators and cancels any active drawing
// or editing sessions for map notes.
function resetActions() {
// Visually reset all drawing tool indicators in the UI so none appear active.
drawActionBar.querySelectorAll("calcite-action").forEach((action) => {
action.indicator = false;
// Abort any ongoing drawing or editing operations for all map note types to ensure a clean state.
pointSketchViewModel.cancel();
polylineSketchViewModel.cancel();
polygonSketchViewModel.cancel();
textSketchViewModel.cancel();
// Synchronizes the UI and attributes when editing an existing map note graphic,
// ensuring updates are reflected in both.
function updateAttributes(sketchViewModel, event) {
// When the user initiates editing, populate the UI with the
// current attributes of the selected graphic.
if (event.state === "start") {
attributeTitleInput.value = event.graphics[0].attributes?.title || "";
// Track which SketchViewModel is currently being edited to enable correct UI and
// state management during the update process.
state.activeSketchViewModel = sketchViewModel;
// When editing is finished, save changes to the graphic and reset the UI to its default state.
} else if (event.state === "complete") {
// Save the updated title from the input field back to the graphic's attributes,
// ensuring the user's changes are persisted.
event.graphics[0].attributes.title = attributeTitleInput.value || "";
// If the graphic uses a text symbol, clone it and update its text property to match the new title.
const symbol = event.graphics[0].symbol?.clone();
if (symbol && symbol.type === "text") {
symbol.text = attributeTitleInput.value || "";
event.graphics[0].symbol = symbol;
// Remove focus from the input field after editing to signal completion and improve user experience.
attributeTitleInput.blur();
// Reset the reference to the active SketchViewModel, marking the end of the current editing session.
state.activeSketchViewModel = null;
// Hide the attribute editing panel to return the UI to its default state after a graphic update.
attributePanel.hidden = true;
// Re-enable all drawing and editing actions so the user can continue
// interacting with map notes after an update.
drawActionBar.querySelectorAll("calcite-action").forEach((action) => {