I am trying to batch-update multiple layers to all have the same Arcade content applied. I have found documentation on using a combination of Attribute Expressions / Text elements, but for the particular Arcade I am using, it only functions correctly if Arcade content is applied.
I have looked through the developer documentation and it does not appear to call out Arcade as a popupElement supported function.
Here is my current code, but ultimately I would like to remove the Attribute Expression and Text element and use Arcade as this works for hyperlinking out to URLs.
from arcgis.gis import GIS
from arcgis.mapping import WebMap
# Step 1: Connect to the Portal
gis = GIS("https://portal.website.com/portal/", "username", "password")
# Step 2: Access the Web Map
webmap_item = gis.content.get("itemid")
webmap = WebMap(webmap_item)
# Function to clean popupInfo, remove attribute expressions, text boxes, and fields list
def clean_popup(layer):
# Update the layer title to remove ":" and references to "fields"
if "title" in layer:
original_title = layer["title"]
new_title = original_title.replace(':', '').replace('fields', '', 1).strip()
layer["title"] = new_title
if original_title != new_title:
print(f"Updated layer title from '{original_title}' to '{new_title}'")
# Clean the popupInfo to remove attribute expressions, fields list, and text boxes
if "popupInfo" in layer:
popup_info = layer["popupInfo"]
# Clear popupElements to remove Fields list, text elements, and any other custom elements
popup_info["popupElements"] = []
# Remove fieldInfos and attribute expressions (expressionInfos) completely
popup_info.pop("fieldInfos", None)
popup_info.pop("expressionInfos", None) # Removes any existing attribute expressions
# Remove other potential field-related properties
field_related_keys = ['displayField', 'description', 'showAttachments', 'mediaInfos']
for key in field_related_keys:
popup_info.pop(key, None)
print(f"Cleaned popupInfo for layer: {layer.get('title', 'Unnamed Layer')}")
# Function to add an Arcade attribute expression and reference it in a text element
def add_arcade_expression_as_text(layer):
if "popupInfo" in layer:
popup_info = layer["popupInfo"]
# Define the Arcade attribute expression
arcade_expression = {
"name": "customContent",
"title": "Custom Content",
"expression": """
var fieldsToExclude = ["OBJECTID", "OBJECTID_1", "SHAPE", "LAYERNAME", "GLOBALID", "SHAPE.STAREA()", "SHAPE.STLENGTH()", "CREATED_USER", "CREATED_DATE", "LAST_EDITED_USER", "LAST_EDITED_DATE", "LASTMODBY", "LASTMODDATE", "LASTSYNDATE"];
var content = "";
var seenUrls = {};
function formatURL(label, value) {
return label + ": <a target='_blank' href='" + value + "'>Click here for more info.</a>";
}
Expects($feature, "*");
var schemaDict = Schema($feature);
var fieldsArray = schemaDict["fields"];
function getFieldAlias(fieldName) {
for (var j = 0; j < Count(fieldsArray); j++) {
var fieldDict = fieldsArray[j];
if (fieldDict['name'] == fieldName) {
return fieldDict['alias'];
}
}
return fieldName;
}
function formatDate(dateValue) {
var date = Date(dateValue);
return Text(date, "MM/DD/YYYY");
}
function getDomainDescription(fieldDict, value) {
if (HasKey(fieldDict, "domain")) {
var domain = fieldDict["domain"];
if (domain != null && HasKey(domain, "codedValues")) {
for (var k = 0; k < Count(domain["codedValues"]); k++) {
var codedValue = domain["codedValues"][k];
if (codedValue["code"] == value) {
return codedValue["name"];
}
}
}
}
return value;
}
for (var i = 0; i < Count(fieldsArray); i++) {
var fieldDict = fieldsArray[i];
var fieldName = fieldDict["name"];
var alias = fieldDict["alias"];
var value = $feature[fieldName];
if (IndexOf(fieldsToExclude, Upper(fieldName)) == -1 && !IsEmpty(value) && Upper(Text(value)) != "NULL") {
if (Upper(Left(Text(value), 7)) == "HTTP://" || Upper(Left(Text(value), 8)) == "HTTPS://") {
if (!HasKey(seenUrls, value)) {
content += formatURL(alias, value) + "\\n";
seenUrls[value] = true;
}
} else if (fieldDict.type == "esriFieldTypeDate") {
content += alias + ": " + formatDate(value) + "\\n";
} else if (HasKey(fieldDict, "domain") && fieldDict["domain"] != null) {
var domainDescription = getDomainDescription(fieldDict, value);
content += alias + ": " + Text(domainDescription) + "\\n";
} else {
content += alias + ": " + Text(value) + "\\n";
}
}
}
return content;
"""
}
# Add the Arcade expression to the expressionInfos
if "expressionInfos" not in popup_info:
popup_info["expressionInfos"] = []
popup_info["expressionInfos"].append(arcade_expression)
# Reference the Arcade expression in a text element
text_element = {
"type": "text",
"text": "{expression/customContent}"
}
# Add the text element to the popupElements
popup_info["popupElements"].append(text_element)
print(f"Added Arcade expression as a text element to layer: {layer.get('title', 'Unnamed Layer')}")
# Recursive function to process all layers, including nested layers
def process_layers(layers):
for layer in layers:
clean_popup(layer)
add_arcade_expression_as_text(layer)
# Process sublayers if the layer is a group
if "layers" in layer:
print(f"Processing sublayers of group layer: {layer.get('title', 'Unnamed Layer')}")
process_layers(layer["layers"])
# Step 3: Process all layers, including those within group layers
process_layers(webmap.layers)
# Step 4: Save the updated Web Map
webmap.update()
print("All attribute expressions, fields lists, and text boxes removed, titles updated, and Arcade content added as a text element.")
When I apply the below in an Arcade content section, the hyperlinks work. Whereas if I do it in an Attribute Expression and Text content, everything but the hyperlink works.
var fieldsToExclude = ["OBJECTID", "SHAPE", "LAYERNAME", "GLOBALID", "SHAPE.STAREA()", "SHAPE.STLENGTH()", "CREATED_USER", "CREATED_DATE", "LAST_EDITED_USER", "LAST_EDITED_DATE", "LASTMODBY", "LASTMODDATE", "LASTSYNDATE"];
var content = "";
var seenUrls = {};
// Function to format URL fields
function formatURL(label, value) {
return "<b>" + label + ":</b> <a target='_blank' href='" + value + "'>Click here for more info.</a><br/>";
}
// Ensure all fields are available
Expects($feature, "*");
// Get the schema of the feature's layer
var schemaDict = Schema($feature);
var fieldsArray = schemaDict["fields"];
// Function to get the alias of a field
function getFieldAlias(fieldName) {
for (var j = 0; j < Count(fieldsArray); j++) {
var fieldDict = fieldsArray[j];
if (fieldDict['name'] == fieldName) {
return fieldDict['alias'];
}
}
return fieldName; // Fallback to field name if alias is not found
}
// Function to format dates as mm/dd/yyyy
function formatDate(dateValue) {
var date = Date(dateValue);
return Text(date, "MM/DD/YYYY");
}
// Function to get domain description
function getDomainDescription(fieldDict, value) {
// First, ensure that the domain key exists and is not null
if (HasKey(fieldDict, "domain")) {
var domain = fieldDict["domain"];
if (domain != null && HasKey(domain, "codedValues")) {
for (var k = 0; k < Count(domain["codedValues"]); k++) {
var codedValue = domain["codedValues"][k];
if (codedValue["code"] == value) {
return codedValue["name"];
}
}
}
}
return value; // Fallback to original value if no domain or match is found
}
// Iterate over each field in the schema to respect the field order
for (var i = 0; i < Count(fieldsArray); i++) {
var fieldDict = fieldsArray[i];
var fieldName = fieldDict["name"];
var alias = fieldDict["alias"];
var value = $feature[fieldName];
// Exclude certain fields and check if value is not empty
if (IndexOf(fieldsToExclude, Upper(fieldName)) == -1 && !IsEmpty(value) && Upper(Text(value)) != "NULL") {
// Check if the field value is a URL and format accordingly
if (Upper(Left(Text(value), 7)) == "HTTP://" || Upper(Left(Text(value), 8)) == "HTTPS://") {
if (!HasKey(seenUrls, value)) {
content += formatURL(alias, value);
seenUrls[value] = true;
}
} else if (fieldDict.type == "esriFieldTypeDate") {
// Format date fields
content += "<b>" + alias + ":</b> " + formatDate(value) + "<br/>";
} else if (HasKey(fieldDict, "domain") && fieldDict["domain"] != null) {
// Use domain description if available
var domainDescription = getDomainDescription(fieldDict, value);
content += "<b>" + alias + ":</b> " + Text(domainDescription) + "<br/>";
} else {
content += "<b>" + alias + ":</b> " + Text(value) + "<br/>";
}
}
}
return {
type: 'text',
text: content // this property supports HTML tags
};