My web map layers all have optimal popups configured, because this is done automatically using arcpy (with CIM), before the layers are automatically published from ArcGIS Pro to AGOL (using the same arcpy script that configures the popups).
In the ArcGIS Online Field Maps Designer, I can interactively create nice forms for these layers by using the 'Convert from Popup' button.
However, I produce these web maps on a regular basis and they have a lot of layers. I would like to automate the 'Convert from Popup' action (and avoid the group element which this creates).
Is/are there any (set of) function(s) in ArcGIS API for Python to perform the 'Convert from Popup' or do I have to develop this myself? I'd like to avoid a lot of time re-inventing the wheel.
If I have to do this myself, are there any examples of how this can be done in Python? If I have to do it manually, I guess I would have to cycle through the popup elements, and convert each of them to form elements, but this is not something I've done before so it may take me some time to figure it out.
Hi @GISDataTasPorts,
There is no option with the ArcGIS API for Python that mimics that Convert popup (to form). If the maps are being produced regularly with the same layers/tables you can create template JSON files.
from arcgis.gis import GIS
import json
## access AGOL
agol = GIS("home")
## get the Web Map as an Item object
wm_item = agol.content.get("WM_ITEM_ID")
## get the Web Map JSON definition as a dictionary
wm_item_data = wm_item.get_data()
## set the output folder
form_output_fldr = r"C:\Path\to\template\folder"
## the layer names as they appear in the Web Map
layer_names = [
"Layer 1",
"Layer 2",
"Layer 3",
...,
"Layer N"
]
## the table names as they appear in the Web Map
table_names = [
"Table 1",
"Table 2",
"Table 3",
...,
"Table N"
]
## for each layer in the Web Map
for lyr in wm_item_data["operationalLayers"]:
## if the layer is in the list
if lyr["title"] in layer_names:
try:
## output file path
form_output_file = f"{form_output_fldr}\\{lyr['title']}.json"
## get the formInfo definition
form_def = lyr["formInfo"]
## export to JSON file with same name as the layer
with open(form_output_file, 'w', encoding='utf-8') as f:
json.dump(form_def, f, ensure_ascii=False, indent=4)
except KeyError:
print(f"No form for {lyr['title']}")
## for each table in the Web Map
for tbl in wm_item_data["tables"]:
## if the tableis in the list
if tbl["title"] in table_names:
try:
## output file path
form_output_file = f"{form_output_fldr}\\{tbl['title']}.json"
## get the formInfo definition
form_def = tbl["formInfo"]
## export to JSON file with same name as the layer
with open(form_output_file, 'w', encoding='utf-8') as f:
json.dump(form_def, f, ensure_ascii=False, indent=4)
except KeyError:
print(f"No form for {tbl['title']}")
from arcgis.gis import GIS
import json
agol = GIS("home")
folder = r"C:\Path\to\template\folder"
## the layer names as they appear in the Web Map
layer_names = [
"Layer 1",
"Layer 2",
"Layer 3",
...,
"Layer N"
]
## the table names as they appear in the Web Map
table_names = [
"Table 1",
"Table 2",
"Table 3",
...,
"Table N"
]
## the web maps to update
wm_ids = {
"WebMap 1" : "WM_ITEM_ID",
"WebMap 2" : "WM_ITEM_ID",
"WebMap 3" : "WM_ITEM_ID"
}
## for each web map
for wm_name, wm_item_id in wm_ids.items():
print(wm_name)
## get the web map as an Item object
wm_item = agol.content.get(wm_item_id)
## get the web map definition as a dictionary
wm_item_data = wm_item.get_data()
## iterate over the layers
for lyr in wm_item_data["operationalLayers"]:
try:
## if the layer name matches a layer in the list
if lyr["title"] in layer_names:
## get the matching JSON file
json_definition_file = f"{folder}\\{lyr['title']}.json"
## update the formInfo
with open(json_definition_file) as json_def:
form_def = json.load(json_def)
lyr["formInfo"] = form_def
except FileNotFoundError:
print(f"No form file for {lyr['title']}")
## do the same for tables
for tbl in wm_item_data["tables"]:
try:
if tbl["title"] in table_names:
json_definition_file = f"{folder}\\{tbl['title']}.json"
with open(json_definition_file) as json_def:
form_def = json.load(json_def)
tbl["formInfo"] = form_def
except FileNotFoundError:
print(f"No form file for {tbl['title']}")
item_properties = {"text":wm_item_data}
wm_item.update(item_properties=item_properties)
You could also write your own converter using the Form Input classes from the API.
All the best,
Glen
Here's how I've figured out how to do this (below). It took quite some trial and error, but it works nicely. It even handles both popups stored in the web map layer AND popups stored in the feature service layer which the web map layer uses.
However, I'm quite confused by what is required in the very last line to send the updates back to the web map. It does seem to work as-is, but so do all the other commented out versions of the last line(s). So which is the correct way to run that .update()? (It does not work with no parameters, of course.)
There are still some other tweaks I need to figure out to handle different fields in different ways. At present, the 'inputType' I'm sending seems to be getting ignored. I'm currently hard-coding text-box for all fields, but getting either a text-box, or (if using a domain) a combo-box. This actually is the result I want, but I'd like to know why I can't seem to control this. I will update this post, if I figure it out.
import arcpy, arcgis, json
gis = arcgis.GIS(<preferred_connection_details_here>)
map_id = "<web_map_id_here>"
system_expression_infos = [{'expression': 'false',
'name': 'expr/system/false',
'returnType': 'boolean',
'title': 'False'},
{'expression': 'true',
'name': 'expr/system/true',
'returnType': 'boolean',
'title': 'True'}]
input_type_date_only = {'includeTime': False,
'type': 'datetime-picker'}
input_type_combo_box = {'noValueOptionLabel': 'No value',
'showNoValueOption': True,
'type': 'combo-box'}
input_type_text_box = {'maxLength': 255,
'minLength': 0,
'type': 'text-box'}
def service_layer_for_map_layer(lyr):
layer_id = int(lyr['url'].split("/")[-1])
service_id = lyr['itemId']
service = gis.content.get(service_id)
service_data = service.get_data()
matching_layers = [l for l in service_data['layers'] if l['id'] == layer_id]
if not len(matching_layers):
arcpy.AddWarning(f"Failed to find service layer for map layer {lyr['title']}")
return None
return matching_layers[0]
def convert_popup_to_form(lyr):
popup_info = None
if "popupInfo" in lyr:
popup_info = lyr["popupInfo"]
else:
s_lyr = service_layer_for_map_layer(lyr)
if s_lyr is not None and "popupInfo" in s_lyr:
popup_info = s_lyr["popupInfo"]
if popup_info is None:
arcpy.AddWarning(f"Skipping layer with no 'popupInfo': {lyr['title']}")
return
arcpy.AddMessage(f"Converting popup to form for layer '{lyr['title']}'")
if 'popupElements' in popup_info and len([e for e in popup_info['popupElements'] if 'fieldInfos' in e]):
field_infos = [e for e in popup_info['popupElements'] if 'fieldInfos' in e][0]['fieldInfos'] # fieldInfos is overridden in popupElements
else:
field_infos = popup_info['fieldInfos'] # fieldInfos is NOT overridden in popupElements
form_elements = []
for f_info in field_infos:
input_type = input_type_text_box # UPDATE THIS DEPENDING ON THE TYPE/DOMAIN OF THE FIELD!!!
f_element = {
'editableExpression': 'expr/system/false' if 'isEditable' in f_info and f_info['isEditable'] is False else 'expr/system/true',
'fieldName': f_info['fieldName'],
'inputType': input_type,
'label': f_info['label'],
'type': 'field'
}
form_elements.append(f_element)
lyr['formInfo'] = {'title': popup_info['title'],
'expressionInfos': system_expression_infos,
'formElements': form_elements,
}
def convert_all_popups_to_forms(layers):
for lyr in layers:
if lyr["layerType"] == "GroupLayer":
convert_all_popups_to_forms(lyr["layers"])
if lyr["layerType"] == "ArcGISFeatureLayer":
convert_popup_to_form(lyr)
webmap_item = gis.content.get(map_id)
map_data = webmap_item.get_data() # Get map JSON
map_layers = map_data["operationalLayers"]
convert_all_popups_to_forms(map_layers)
arcpy.AddMessage("Saving changes to the web map")
map_data["operationalLayers"] = map_layers
webmap_item.update(data=map_data) # Update map JSON) # Update map JSON
# webmap_item.update(data=json.dumps(map_data)) # Update map JSON
# update_properties = {"text" : json.dumps(map_data)}
# webmap_item.update(item_properties=update_properties) # Update map JSON
# update_properties = {"text" : map_data}
# webmap_item.update(item_properties=update_properties) # Update map JSON