@saschabrunnerch/arcgis-maps-sdk-js-ai-context 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +201 -0
- package/bin/cli.js +173 -0
- package/contexts/4.34/claude/arcgis-3d-advanced/SKILL.md +586 -0
- package/contexts/4.34/claude/arcgis-advanced-layers/SKILL.md +431 -0
- package/contexts/4.34/claude/arcgis-analysis-services/SKILL.md +607 -0
- package/contexts/4.34/claude/arcgis-arcade/SKILL.md +366 -0
- package/contexts/4.34/claude/arcgis-authentication/SKILL.md +301 -0
- package/contexts/4.34/claude/arcgis-cim-symbols/SKILL.md +486 -0
- package/contexts/4.34/claude/arcgis-coordinates-projection/SKILL.md +406 -0
- package/contexts/4.34/claude/arcgis-core-maps/SKILL.md +739 -0
- package/contexts/4.34/claude/arcgis-core-utilities/SKILL.md +732 -0
- package/contexts/4.34/claude/arcgis-custom-rendering/SKILL.md +445 -0
- package/contexts/4.34/claude/arcgis-editing-advanced/SKILL.md +702 -0
- package/contexts/4.34/claude/arcgis-feature-effects/SKILL.md +393 -0
- package/contexts/4.34/claude/arcgis-geometry-operations/SKILL.md +489 -0
- package/contexts/4.34/claude/arcgis-imagery/SKILL.md +307 -0
- package/contexts/4.34/claude/arcgis-interaction/SKILL.md +572 -0
- package/contexts/4.34/claude/arcgis-knowledge-graphs/SKILL.md +582 -0
- package/contexts/4.34/claude/arcgis-layers/SKILL.md +601 -0
- package/contexts/4.34/claude/arcgis-map-tools/SKILL.md +668 -0
- package/contexts/4.34/claude/arcgis-media-layers/SKILL.md +290 -0
- package/contexts/4.34/claude/arcgis-popup-templates/SKILL.md +891 -0
- package/contexts/4.34/claude/arcgis-portal-content/SKILL.md +679 -0
- package/contexts/4.34/claude/arcgis-scene-effects/SKILL.md +512 -0
- package/contexts/4.34/claude/arcgis-smart-mapping/SKILL.md +686 -0
- package/contexts/4.34/claude/arcgis-tables-forms/SKILL.md +877 -0
- package/contexts/4.34/claude/arcgis-time-animation/SKILL.md +722 -0
- package/contexts/4.34/claude/arcgis-utility-networks/SKILL.md +301 -0
- package/contexts/4.34/claude/arcgis-visualization/SKILL.md +580 -0
- package/contexts/4.34/claude/arcgis-widgets-ui/SKILL.md +574 -0
- package/contexts/4.34/copilot/arcgis-3d.instructions.md +267 -0
- package/contexts/4.34/copilot/arcgis-analysis.instructions.md +294 -0
- package/contexts/4.34/copilot/arcgis-arcade.instructions.md +234 -0
- package/contexts/4.34/copilot/arcgis-authentication.instructions.md +187 -0
- package/contexts/4.34/copilot/arcgis-cim-symbols.instructions.md +177 -0
- package/contexts/4.34/copilot/arcgis-core-maps.instructions.md +246 -0
- package/contexts/4.34/copilot/arcgis-core-utilities.instructions.md +247 -0
- package/contexts/4.34/copilot/arcgis-editing.instructions.md +262 -0
- package/contexts/4.34/copilot/arcgis-geometry.instructions.md +225 -0
- package/contexts/4.34/copilot/arcgis-layers.instructions.md +278 -0
- package/contexts/4.34/copilot/arcgis-popup-templates.instructions.md +266 -0
- package/contexts/4.34/copilot/arcgis-portal-advanced.instructions.md +275 -0
- package/contexts/4.34/copilot/arcgis-smart-mapping.instructions.md +184 -0
- package/contexts/4.34/copilot/arcgis-time-animation.instructions.md +112 -0
- package/contexts/4.34/copilot/arcgis-visualization.instructions.md +321 -0
- package/contexts/4.34/copilot/arcgis-widgets-ui.instructions.md +277 -0
- package/lib/installer.js +379 -0
- package/package.json +45 -0
|
@@ -0,0 +1,891 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: arcgis-popup-templates
|
|
3
|
+
description: Configure rich popup content with text, fields, media, charts, attachments, and related records. Use for customizing feature popups with dynamic content, expressions, and interactive elements.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ArcGIS Popup Templates
|
|
7
|
+
|
|
8
|
+
Use this skill for creating and customizing popup templates with various content types.
|
|
9
|
+
|
|
10
|
+
## PopupTemplate Overview
|
|
11
|
+
|
|
12
|
+
| Content Type | Purpose |
|
|
13
|
+
|--------------|---------|
|
|
14
|
+
| TextContent | HTML or plain text |
|
|
15
|
+
| FieldsContent | Attribute table |
|
|
16
|
+
| MediaContent | Charts and images |
|
|
17
|
+
| AttachmentsContent | File attachments |
|
|
18
|
+
| ExpressionContent | Arcade expression results |
|
|
19
|
+
| CustomContent | Custom HTML/JavaScript |
|
|
20
|
+
| RelationshipContent | Related records |
|
|
21
|
+
|
|
22
|
+
## Basic PopupTemplate
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
layer.popupTemplate = {
|
|
26
|
+
title: "{name}",
|
|
27
|
+
content: "Population: {population}<br>Area: {area} sq mi"
|
|
28
|
+
};
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### With Field Substitution
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
layer.popupTemplate = {
|
|
35
|
+
title: "{city_name}, {state}",
|
|
36
|
+
content: `
|
|
37
|
+
<h3>Demographics</h3>
|
|
38
|
+
<p>Population: {population:NumberFormat(places: 0)}</p>
|
|
39
|
+
<p>Median Income: {median_income:NumberFormat(digitSeparator: true, places: 0)}</p>
|
|
40
|
+
<p>Founded: {founded_date:DateFormat(selector: 'date', datePattern: 'MMMM d, yyyy')}</p>
|
|
41
|
+
`
|
|
42
|
+
};
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Content Array (Multiple Content Types)
|
|
46
|
+
|
|
47
|
+
```javascript
|
|
48
|
+
layer.popupTemplate = {
|
|
49
|
+
title: "{name}",
|
|
50
|
+
content: [
|
|
51
|
+
{
|
|
52
|
+
type: "text",
|
|
53
|
+
text: "<b>Overview</b><br>{description}"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
type: "fields",
|
|
57
|
+
fieldInfos: [
|
|
58
|
+
{ fieldName: "population", label: "Population" },
|
|
59
|
+
{ fieldName: "area", label: "Area (sq mi)" }
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
type: "media",
|
|
64
|
+
mediaInfos: [{
|
|
65
|
+
type: "pie-chart",
|
|
66
|
+
title: "Demographics",
|
|
67
|
+
value: {
|
|
68
|
+
fields: ["white", "black", "asian", "other"]
|
|
69
|
+
}
|
|
70
|
+
}]
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
};
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Content Types
|
|
77
|
+
|
|
78
|
+
### TextContent
|
|
79
|
+
|
|
80
|
+
Display HTML or text.
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
{
|
|
84
|
+
type: "text",
|
|
85
|
+
text: `
|
|
86
|
+
<div style="padding: 10px;">
|
|
87
|
+
<h2>{name}</h2>
|
|
88
|
+
<p>{description}</p>
|
|
89
|
+
<a href="{website}" target="_blank">Visit Website</a>
|
|
90
|
+
</div>
|
|
91
|
+
`
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### FieldsContent
|
|
96
|
+
|
|
97
|
+
Display attributes as a table.
|
|
98
|
+
|
|
99
|
+
```javascript
|
|
100
|
+
{
|
|
101
|
+
type: "fields",
|
|
102
|
+
fieldInfos: [
|
|
103
|
+
{
|
|
104
|
+
fieldName: "name",
|
|
105
|
+
label: "Name"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
fieldName: "population",
|
|
109
|
+
label: "Population",
|
|
110
|
+
format: {
|
|
111
|
+
digitSeparator: true,
|
|
112
|
+
places: 0
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
fieldName: "date_created",
|
|
117
|
+
label: "Created",
|
|
118
|
+
format: {
|
|
119
|
+
dateFormat: "short-date"
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
fieldName: "percentage",
|
|
124
|
+
label: "Percentage",
|
|
125
|
+
format: {
|
|
126
|
+
places: 2,
|
|
127
|
+
digitSeparator: true
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
#### Date Formats
|
|
135
|
+
|
|
136
|
+
- `short-date` - 12/30/2024
|
|
137
|
+
- `short-date-short-time` - 12/30/2024, 3:30 PM
|
|
138
|
+
- `short-date-short-time-24` - 12/30/2024, 15:30
|
|
139
|
+
- `short-date-long-time` - 12/30/2024, 3:30:45 PM
|
|
140
|
+
- `short-date-long-time-24` - 12/30/2024, 15:30:45
|
|
141
|
+
- `long-month-day-year` - December 30, 2024
|
|
142
|
+
- `long-month-day-year-short-time` - December 30, 2024, 3:30 PM
|
|
143
|
+
- `long-month-day-year-long-time` - December 30, 2024, 3:30:45 PM
|
|
144
|
+
- `day-short-month-year` - 30 Dec 2024
|
|
145
|
+
- `year` - 2024
|
|
146
|
+
|
|
147
|
+
### MediaContent
|
|
148
|
+
|
|
149
|
+
Display charts or images.
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
{
|
|
153
|
+
type: "media",
|
|
154
|
+
mediaInfos: [
|
|
155
|
+
{
|
|
156
|
+
title: "Sales by Quarter",
|
|
157
|
+
type: "column-chart", // bar-chart, pie-chart, line-chart, column-chart, image
|
|
158
|
+
value: {
|
|
159
|
+
fields: ["q1_sales", "q2_sales", "q3_sales", "q4_sales"],
|
|
160
|
+
normalizeField: "total_sales" // Optional
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
]
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
#### Chart Types
|
|
168
|
+
|
|
169
|
+
**Bar Chart**
|
|
170
|
+
```javascript
|
|
171
|
+
{
|
|
172
|
+
type: "bar-chart",
|
|
173
|
+
title: "Population by Age",
|
|
174
|
+
value: {
|
|
175
|
+
fields: ["age_0_17", "age_18_34", "age_35_54", "age_55_plus"]
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Pie Chart**
|
|
181
|
+
```javascript
|
|
182
|
+
{
|
|
183
|
+
type: "pie-chart",
|
|
184
|
+
title: "Land Use Distribution",
|
|
185
|
+
value: {
|
|
186
|
+
fields: ["residential", "commercial", "industrial", "agricultural"]
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Line Chart**
|
|
192
|
+
```javascript
|
|
193
|
+
{
|
|
194
|
+
type: "line-chart",
|
|
195
|
+
title: "Monthly Sales",
|
|
196
|
+
value: {
|
|
197
|
+
fields: ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"]
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Column Chart**
|
|
203
|
+
```javascript
|
|
204
|
+
{
|
|
205
|
+
type: "column-chart",
|
|
206
|
+
title: "Revenue vs Expenses",
|
|
207
|
+
value: {
|
|
208
|
+
fields: ["revenue", "expenses"]
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Image**
|
|
214
|
+
```javascript
|
|
215
|
+
{
|
|
216
|
+
type: "image",
|
|
217
|
+
title: "Property Photo",
|
|
218
|
+
value: {
|
|
219
|
+
sourceURL: "{image_url}",
|
|
220
|
+
linkURL: "{detail_page_url}"
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Fixed image
|
|
225
|
+
{
|
|
226
|
+
type: "image",
|
|
227
|
+
value: {
|
|
228
|
+
sourceURL: "https://example.com/logo.png"
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### AttachmentsContent
|
|
234
|
+
|
|
235
|
+
Display file attachments.
|
|
236
|
+
|
|
237
|
+
```javascript
|
|
238
|
+
{
|
|
239
|
+
type: "attachments",
|
|
240
|
+
displayType: "preview", // preview, list, auto
|
|
241
|
+
title: "Photos"
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// With custom display
|
|
245
|
+
{
|
|
246
|
+
type: "attachments",
|
|
247
|
+
displayType: "list",
|
|
248
|
+
title: "Documents",
|
|
249
|
+
description: "Related files for this record"
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### ExpressionContent
|
|
254
|
+
|
|
255
|
+
Display Arcade expression results.
|
|
256
|
+
|
|
257
|
+
```javascript
|
|
258
|
+
// First, define expression in expressionInfos
|
|
259
|
+
layer.popupTemplate = {
|
|
260
|
+
expressionInfos: [
|
|
261
|
+
{
|
|
262
|
+
name: "population-density",
|
|
263
|
+
title: "Population Density",
|
|
264
|
+
expression: "Round($feature.population / $feature.area, 2)"
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
name: "age-category",
|
|
268
|
+
title: "Age Category",
|
|
269
|
+
expression: `
|
|
270
|
+
var age = $feature.building_age;
|
|
271
|
+
if (age < 25) return "New";
|
|
272
|
+
if (age < 50) return "Moderate";
|
|
273
|
+
return "Historic";
|
|
274
|
+
`
|
|
275
|
+
}
|
|
276
|
+
],
|
|
277
|
+
content: [
|
|
278
|
+
{
|
|
279
|
+
type: "expression",
|
|
280
|
+
expressionInfo: {
|
|
281
|
+
name: "population-density"
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
]
|
|
285
|
+
};
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### CustomContent
|
|
289
|
+
|
|
290
|
+
Create fully custom content with JavaScript.
|
|
291
|
+
|
|
292
|
+
```javascript
|
|
293
|
+
import CustomContent from "@arcgis/core/popup/content/CustomContent.js";
|
|
294
|
+
|
|
295
|
+
const customContent = new CustomContent({
|
|
296
|
+
outFields: ["*"],
|
|
297
|
+
creator: (event) => {
|
|
298
|
+
const div = document.createElement("div");
|
|
299
|
+
const graphic = event.graphic;
|
|
300
|
+
|
|
301
|
+
div.innerHTML = `
|
|
302
|
+
<div class="custom-popup">
|
|
303
|
+
<h3>${graphic.attributes.name}</h3>
|
|
304
|
+
<canvas id="chart-${graphic.attributes.OBJECTID}"></canvas>
|
|
305
|
+
</div>
|
|
306
|
+
`;
|
|
307
|
+
|
|
308
|
+
// Add custom logic, charts, etc.
|
|
309
|
+
return div;
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
layer.popupTemplate = {
|
|
314
|
+
title: "{name}",
|
|
315
|
+
content: [customContent]
|
|
316
|
+
};
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### RelationshipContent
|
|
320
|
+
|
|
321
|
+
Display related records.
|
|
322
|
+
|
|
323
|
+
```javascript
|
|
324
|
+
{
|
|
325
|
+
type: "relationship",
|
|
326
|
+
relationshipId: 0, // Relationship ID from the layer
|
|
327
|
+
title: "Related Inspections",
|
|
328
|
+
displayCount: 5,
|
|
329
|
+
orderByFields: [
|
|
330
|
+
{
|
|
331
|
+
field: "inspection_date",
|
|
332
|
+
order: "desc"
|
|
333
|
+
}
|
|
334
|
+
]
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## FieldInfo Configuration
|
|
339
|
+
|
|
340
|
+
### Basic FieldInfo
|
|
341
|
+
|
|
342
|
+
```javascript
|
|
343
|
+
const fieldInfo = {
|
|
344
|
+
fieldName: "population",
|
|
345
|
+
label: "Population",
|
|
346
|
+
visible: true,
|
|
347
|
+
isEditable: false,
|
|
348
|
+
statisticType: "sum" // For clustering: sum, min, max, avg, count
|
|
349
|
+
};
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Number Formatting
|
|
353
|
+
|
|
354
|
+
```javascript
|
|
355
|
+
{
|
|
356
|
+
fieldName: "revenue",
|
|
357
|
+
label: "Annual Revenue",
|
|
358
|
+
format: {
|
|
359
|
+
digitSeparator: true,
|
|
360
|
+
places: 2
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Currency (manual)
|
|
365
|
+
{
|
|
366
|
+
fieldName: "price",
|
|
367
|
+
label: "Price",
|
|
368
|
+
format: {
|
|
369
|
+
digitSeparator: true,
|
|
370
|
+
places: 2
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
// Use expression for currency symbol
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Date Formatting
|
|
377
|
+
|
|
378
|
+
```javascript
|
|
379
|
+
{
|
|
380
|
+
fieldName: "created_date",
|
|
381
|
+
label: "Created",
|
|
382
|
+
format: {
|
|
383
|
+
dateFormat: "long-month-day-year"
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Tooltip
|
|
389
|
+
|
|
390
|
+
```javascript
|
|
391
|
+
{
|
|
392
|
+
fieldName: "status",
|
|
393
|
+
label: "Status",
|
|
394
|
+
tooltip: "Current processing status of the request"
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
## RelatedRecordsInfo
|
|
399
|
+
|
|
400
|
+
Configure how related records are displayed.
|
|
401
|
+
|
|
402
|
+
```javascript
|
|
403
|
+
layer.popupTemplate = {
|
|
404
|
+
title: "{name}",
|
|
405
|
+
content: [{
|
|
406
|
+
type: "relationship",
|
|
407
|
+
relationshipId: 0
|
|
408
|
+
}],
|
|
409
|
+
relatedRecordsInfo: {
|
|
410
|
+
showRelatedRecords: true,
|
|
411
|
+
orderByFields: [{
|
|
412
|
+
field: "date",
|
|
413
|
+
order: "desc"
|
|
414
|
+
}]
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
## Actions
|
|
420
|
+
|
|
421
|
+
Add custom buttons to popups.
|
|
422
|
+
|
|
423
|
+
```javascript
|
|
424
|
+
layer.popupTemplate = {
|
|
425
|
+
title: "{name}",
|
|
426
|
+
content: "...",
|
|
427
|
+
actions: [
|
|
428
|
+
{
|
|
429
|
+
id: "zoom-to",
|
|
430
|
+
title: "Zoom To",
|
|
431
|
+
className: "esri-icon-zoom-in-magnifying-glass"
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
id: "edit",
|
|
435
|
+
title: "Edit",
|
|
436
|
+
className: "esri-icon-edit"
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
id: "delete",
|
|
440
|
+
title: "Delete",
|
|
441
|
+
className: "esri-icon-trash"
|
|
442
|
+
}
|
|
443
|
+
]
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
// Handle action clicks
|
|
447
|
+
view.popup.on("trigger-action", (event) => {
|
|
448
|
+
if (event.action.id === "zoom-to") {
|
|
449
|
+
view.goTo(view.popup.selectedFeature);
|
|
450
|
+
} else if (event.action.id === "edit") {
|
|
451
|
+
// Open editor
|
|
452
|
+
startEditing(view.popup.selectedFeature);
|
|
453
|
+
} else if (event.action.id === "delete") {
|
|
454
|
+
// Delete feature
|
|
455
|
+
deleteFeature(view.popup.selectedFeature);
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### Action Button Types
|
|
461
|
+
|
|
462
|
+
```javascript
|
|
463
|
+
// Icon button
|
|
464
|
+
{
|
|
465
|
+
id: "info",
|
|
466
|
+
title: "More Info",
|
|
467
|
+
className: "esri-icon-description"
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Text button
|
|
471
|
+
{
|
|
472
|
+
id: "report",
|
|
473
|
+
title: "Generate Report",
|
|
474
|
+
type: "button"
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Toggle button
|
|
478
|
+
{
|
|
479
|
+
id: "highlight",
|
|
480
|
+
title: "Highlight",
|
|
481
|
+
type: "toggle",
|
|
482
|
+
value: false
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
## Dynamic Content with Functions
|
|
487
|
+
|
|
488
|
+
### Content as Function
|
|
489
|
+
|
|
490
|
+
```javascript
|
|
491
|
+
layer.popupTemplate = {
|
|
492
|
+
title: "{name}",
|
|
493
|
+
content: (feature) => {
|
|
494
|
+
const attributes = feature.graphic.attributes;
|
|
495
|
+
|
|
496
|
+
// Conditional content
|
|
497
|
+
if (attributes.type === "residential") {
|
|
498
|
+
return `
|
|
499
|
+
<h3>Residential Property</h3>
|
|
500
|
+
<p>Bedrooms: ${attributes.bedrooms}</p>
|
|
501
|
+
<p>Bathrooms: ${attributes.bathrooms}</p>
|
|
502
|
+
`;
|
|
503
|
+
} else {
|
|
504
|
+
return `
|
|
505
|
+
<h3>Commercial Property</h3>
|
|
506
|
+
<p>Square Footage: ${attributes.sqft}</p>
|
|
507
|
+
<p>Zoning: ${attributes.zoning}</p>
|
|
508
|
+
`;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### Async Content Function
|
|
515
|
+
|
|
516
|
+
```javascript
|
|
517
|
+
layer.popupTemplate = {
|
|
518
|
+
title: "{name}",
|
|
519
|
+
content: async (feature) => {
|
|
520
|
+
const id = feature.graphic.attributes.OBJECTID;
|
|
521
|
+
|
|
522
|
+
// Fetch additional data
|
|
523
|
+
const response = await fetch(`/api/details/${id}`);
|
|
524
|
+
const data = await response.json();
|
|
525
|
+
|
|
526
|
+
return `
|
|
527
|
+
<h3>${data.title}</h3>
|
|
528
|
+
<p>${data.description}</p>
|
|
529
|
+
<img src="${data.imageUrl}" />
|
|
530
|
+
`;
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
## Arcade Expressions
|
|
536
|
+
|
|
537
|
+
### In Title
|
|
538
|
+
|
|
539
|
+
```javascript
|
|
540
|
+
layer.popupTemplate = {
|
|
541
|
+
title: {
|
|
542
|
+
expression: `
|
|
543
|
+
var name = $feature.name;
|
|
544
|
+
var status = $feature.status;
|
|
545
|
+
return name + " (" + status + ")";
|
|
546
|
+
`
|
|
547
|
+
},
|
|
548
|
+
content: "..."
|
|
549
|
+
};
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### Expression Infos
|
|
553
|
+
|
|
554
|
+
```javascript
|
|
555
|
+
layer.popupTemplate = {
|
|
556
|
+
expressionInfos: [
|
|
557
|
+
{
|
|
558
|
+
name: "formatted-date",
|
|
559
|
+
title: "Formatted Date",
|
|
560
|
+
expression: `
|
|
561
|
+
var d = $feature.created_date;
|
|
562
|
+
return Text(d, "MMMM D, YYYY");
|
|
563
|
+
`
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
name: "calculated-field",
|
|
567
|
+
title: "Density",
|
|
568
|
+
expression: "Round($feature.population / AreaGeodetic($feature, 'square-miles'), 1)"
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
name: "conditional-value",
|
|
572
|
+
title: "Status",
|
|
573
|
+
expression: `
|
|
574
|
+
var val = $feature.score;
|
|
575
|
+
if (val >= 80) return "Excellent";
|
|
576
|
+
if (val >= 60) return "Good";
|
|
577
|
+
if (val >= 40) return "Fair";
|
|
578
|
+
return "Poor";
|
|
579
|
+
`
|
|
580
|
+
}
|
|
581
|
+
],
|
|
582
|
+
content: [
|
|
583
|
+
{
|
|
584
|
+
type: "fields",
|
|
585
|
+
fieldInfos: [
|
|
586
|
+
{ fieldName: "expression/formatted-date", label: "Created" },
|
|
587
|
+
{ fieldName: "expression/calculated-field", label: "Population Density" },
|
|
588
|
+
{ fieldName: "expression/conditional-value", label: "Rating" }
|
|
589
|
+
]
|
|
590
|
+
}
|
|
591
|
+
]
|
|
592
|
+
};
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
## OutFields
|
|
596
|
+
|
|
597
|
+
Specify which fields to retrieve.
|
|
598
|
+
|
|
599
|
+
```javascript
|
|
600
|
+
layer.popupTemplate = {
|
|
601
|
+
title: "{name}",
|
|
602
|
+
content: "...",
|
|
603
|
+
outFields: ["name", "population", "area", "created_date"]
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
// All fields
|
|
607
|
+
layer.popupTemplate = {
|
|
608
|
+
title: "{name}",
|
|
609
|
+
content: "...",
|
|
610
|
+
outFields: ["*"]
|
|
611
|
+
};
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
## Last Edit Info
|
|
615
|
+
|
|
616
|
+
Show who last edited the feature.
|
|
617
|
+
|
|
618
|
+
```javascript
|
|
619
|
+
layer.popupTemplate = {
|
|
620
|
+
title: "{name}",
|
|
621
|
+
content: "...",
|
|
622
|
+
lastEditInfoEnabled: true
|
|
623
|
+
};
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
## Popup Template from JSON
|
|
627
|
+
|
|
628
|
+
```javascript
|
|
629
|
+
const popupTemplate = {
|
|
630
|
+
title: "{name}",
|
|
631
|
+
content: [{
|
|
632
|
+
type: "fields",
|
|
633
|
+
fieldInfos: [
|
|
634
|
+
{ fieldName: "category", label: "Category" },
|
|
635
|
+
{ fieldName: "value", label: "Value" }
|
|
636
|
+
]
|
|
637
|
+
}],
|
|
638
|
+
outFields: ["name", "category", "value"]
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
// Apply to layer
|
|
642
|
+
layer.popupTemplate = popupTemplate;
|
|
643
|
+
|
|
644
|
+
// Or create from class
|
|
645
|
+
import PopupTemplate from "@arcgis/core/PopupTemplate.js";
|
|
646
|
+
layer.popupTemplate = new PopupTemplate(popupTemplate);
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
## Layer-Specific Popup
|
|
650
|
+
|
|
651
|
+
### FeatureLayer with Popup
|
|
652
|
+
|
|
653
|
+
```javascript
|
|
654
|
+
const featureLayer = new FeatureLayer({
|
|
655
|
+
url: "https://services.arcgis.com/.../FeatureServer/0",
|
|
656
|
+
popupTemplate: {
|
|
657
|
+
title: "{NAME}",
|
|
658
|
+
content: [{
|
|
659
|
+
type: "fields",
|
|
660
|
+
fieldInfos: [
|
|
661
|
+
{ fieldName: "NAME", label: "Name" },
|
|
662
|
+
{ fieldName: "POP2020", label: "Population (2020)", format: { digitSeparator: true } }
|
|
663
|
+
]
|
|
664
|
+
}]
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
### GeoJSONLayer with Popup
|
|
670
|
+
|
|
671
|
+
```javascript
|
|
672
|
+
const geoJsonLayer = new GeoJSONLayer({
|
|
673
|
+
url: "data.geojson",
|
|
674
|
+
popupTemplate: {
|
|
675
|
+
title: "{properties/name}", // Note: GeoJSON uses properties/fieldName
|
|
676
|
+
content: "{properties/description}"
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
### GraphicsLayer Popup
|
|
682
|
+
|
|
683
|
+
```javascript
|
|
684
|
+
const graphic = new Graphic({
|
|
685
|
+
geometry: point,
|
|
686
|
+
attributes: {
|
|
687
|
+
name: "Location A",
|
|
688
|
+
value: 100
|
|
689
|
+
},
|
|
690
|
+
popupTemplate: {
|
|
691
|
+
title: "{name}",
|
|
692
|
+
content: "Value: {value}"
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
graphicsLayer.add(graphic);
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
## Clustering Popups
|
|
700
|
+
|
|
701
|
+
```javascript
|
|
702
|
+
layer.featureReduction = {
|
|
703
|
+
type: "cluster",
|
|
704
|
+
clusterRadius: 80,
|
|
705
|
+
popupTemplate: {
|
|
706
|
+
title: "Cluster of {cluster_count} features",
|
|
707
|
+
content: [{
|
|
708
|
+
type: "fields",
|
|
709
|
+
fieldInfos: [
|
|
710
|
+
{
|
|
711
|
+
fieldName: "cluster_count",
|
|
712
|
+
label: "Features in cluster"
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
fieldName: "cluster_avg_population",
|
|
716
|
+
label: "Average Population",
|
|
717
|
+
format: { digitSeparator: true, places: 0 }
|
|
718
|
+
}
|
|
719
|
+
]
|
|
720
|
+
}]
|
|
721
|
+
},
|
|
722
|
+
clusterMinSize: 16,
|
|
723
|
+
clusterMaxSize: 60,
|
|
724
|
+
fields: [{
|
|
725
|
+
name: "cluster_avg_population",
|
|
726
|
+
alias: "Average Population",
|
|
727
|
+
onStatisticField: "population",
|
|
728
|
+
statisticType: "avg"
|
|
729
|
+
}]
|
|
730
|
+
};
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
## Common Patterns
|
|
734
|
+
|
|
735
|
+
### Popup with Image Gallery
|
|
736
|
+
|
|
737
|
+
```javascript
|
|
738
|
+
layer.popupTemplate = {
|
|
739
|
+
title: "{name}",
|
|
740
|
+
content: [
|
|
741
|
+
{
|
|
742
|
+
type: "text",
|
|
743
|
+
text: "{description}"
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
type: "attachments",
|
|
747
|
+
displayType: "preview"
|
|
748
|
+
}
|
|
749
|
+
]
|
|
750
|
+
};
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
### Multi-Tab Style Popup
|
|
754
|
+
|
|
755
|
+
```javascript
|
|
756
|
+
layer.popupTemplate = {
|
|
757
|
+
title: "{name}",
|
|
758
|
+
content: [
|
|
759
|
+
{
|
|
760
|
+
type: "text",
|
|
761
|
+
text: "<b>General Information</b>"
|
|
762
|
+
},
|
|
763
|
+
{
|
|
764
|
+
type: "fields",
|
|
765
|
+
fieldInfos: [
|
|
766
|
+
{ fieldName: "category", label: "Category" },
|
|
767
|
+
{ fieldName: "status", label: "Status" }
|
|
768
|
+
]
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
type: "text",
|
|
772
|
+
text: "<hr><b>Statistics</b>"
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
type: "media",
|
|
776
|
+
mediaInfos: [{
|
|
777
|
+
type: "pie-chart",
|
|
778
|
+
title: "Distribution",
|
|
779
|
+
value: { fields: ["typeA", "typeB", "typeC"] }
|
|
780
|
+
}]
|
|
781
|
+
}
|
|
782
|
+
]
|
|
783
|
+
};
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
### Conditional Content
|
|
787
|
+
|
|
788
|
+
```javascript
|
|
789
|
+
layer.popupTemplate = {
|
|
790
|
+
title: "{name}",
|
|
791
|
+
expressionInfos: [{
|
|
792
|
+
name: "show-warning",
|
|
793
|
+
expression: "$feature.risk_level > 7"
|
|
794
|
+
}],
|
|
795
|
+
content: (feature) => {
|
|
796
|
+
const content = [{
|
|
797
|
+
type: "fields",
|
|
798
|
+
fieldInfos: [
|
|
799
|
+
{ fieldName: "name", label: "Name" },
|
|
800
|
+
{ fieldName: "risk_level", label: "Risk Level" }
|
|
801
|
+
]
|
|
802
|
+
}];
|
|
803
|
+
|
|
804
|
+
if (feature.graphic.attributes.risk_level > 7) {
|
|
805
|
+
content.unshift({
|
|
806
|
+
type: "text",
|
|
807
|
+
text: '<div style="background: #ffcccc; padding: 10px;">⚠️ High Risk Area</div>'
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
return content;
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
## TypeScript Usage
|
|
817
|
+
|
|
818
|
+
PopupTemplate content uses autocasting with `type` properties. For TypeScript safety, use `as const`:
|
|
819
|
+
|
|
820
|
+
```typescript
|
|
821
|
+
// Use 'as const' for type safety
|
|
822
|
+
layer.popupTemplate = {
|
|
823
|
+
title: "{name}",
|
|
824
|
+
content: [
|
|
825
|
+
{
|
|
826
|
+
type: "fields",
|
|
827
|
+
fieldInfos: [
|
|
828
|
+
{ fieldName: "name", label: "Name" },
|
|
829
|
+
{ fieldName: "population", label: "Population" }
|
|
830
|
+
]
|
|
831
|
+
},
|
|
832
|
+
{
|
|
833
|
+
type: "media",
|
|
834
|
+
mediaInfos: [{
|
|
835
|
+
type: "pie-chart",
|
|
836
|
+
value: { fields: ["typeA", "typeB"] }
|
|
837
|
+
}]
|
|
838
|
+
}
|
|
839
|
+
]
|
|
840
|
+
} as const;
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
> **Tip:** See [arcgis-core-maps skill](../arcgis-core-maps/SKILL.md) for detailed guidance on autocasting vs explicit classes.
|
|
844
|
+
|
|
845
|
+
## Common Pitfalls
|
|
846
|
+
|
|
847
|
+
1. **Field Names Case Sensitive**: Field names must match exactly
|
|
848
|
+
```javascript
|
|
849
|
+
// If field is "Population" (capital P)
|
|
850
|
+
content: "{Population}" // Correct
|
|
851
|
+
content: "{population}" // Wrong - shows literal {population}
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
2. **OutFields Required**: Fields used in popup must be in outFields
|
|
855
|
+
```javascript
|
|
856
|
+
popupTemplate: {
|
|
857
|
+
title: "{name}",
|
|
858
|
+
content: "{description}",
|
|
859
|
+
outFields: ["name", "description"] // Both required
|
|
860
|
+
}
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
3. **GeoJSON Field Path**: GeoJSON requires `properties/` prefix
|
|
864
|
+
```javascript
|
|
865
|
+
// GeoJSON
|
|
866
|
+
title: "{properties/name}"
|
|
867
|
+
// Regular FeatureLayer
|
|
868
|
+
title: "{name}"
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
4. **Expression Reference**: Use `expression/` prefix for Arcade expressions
|
|
872
|
+
```javascript
|
|
873
|
+
fieldInfos: [
|
|
874
|
+
{ fieldName: "expression/my-expression", label: "Calculated" }
|
|
875
|
+
]
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
5. **Async Content**: Function content must return a value or Promise
|
|
879
|
+
```javascript
|
|
880
|
+
// Wrong - no return
|
|
881
|
+
content: (feature) => {
|
|
882
|
+
const div = document.createElement("div");
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// Correct
|
|
886
|
+
content: (feature) => {
|
|
887
|
+
const div = document.createElement("div");
|
|
888
|
+
return div;
|
|
889
|
+
}
|
|
890
|
+
```
|
|
891
|
+
|