Hi,
I have been working on a custom Arcade expression that displays a table of related inspections in the popup for a feature in ArcGIS Online Map Viewer. The code is working perfectly in the Map Viewer, but when I try to use it in ArcGIS Field Maps, I'm encountering an error (Error code 7018) that states "Arcade expression is invalid".
I've included the Arcade code below for reference:
Has anyone encountered a similar issue or has insights on what might be causing this error specifically in ArcGIS Field Maps? Any help or suggestions would be greatly appreciated.
Guy Nizry
Solved! Go to Solution.
Apologies. As I said it wasn't tested. This time a spun up a service with a related table based on what you have in your expression. I created a feature class called Shepard with a 1:M relationship to a table called Inspections. The Shepard FC had globalID and the Inspections TBL had a guid field called ShepardParentGlobalID.
The error you are seeing from the previous example was the result of setting the outfields variable to an Array of Arrays (i.e [[field1, field2...]] instead of an Array of Strings [field1, field2...]). Then our "record" variable is an Array instead of a field and you can't make an Array Lower Case as an Array object has no method (function) named Lower (Arcade name) or toLowerCase(JavaScript Name) .
At any rate, I made that update and tested the code and it works in Map Viewer and Field Maps.
Field Maps
And the final code:
/*
vvv CONSTANTS vvv
*/
var COLORS_HEX = {
'01': '#D3D3D3', /* INSPECTION ROW */
'02': '#CCFFCC', /* INSPECTION VALID */
'03': '#FFCCCC', /* INSPECTION NOT VALID */
};
/* STYLE INFO */
var TABLE_INSPECTION = ` style="border: 1px solid; padding: 5px; width: auto; direction: rtl;"`;
var TR_HEADER_INSPECTION = ` style="background-color:` + COLORS_HEX['01'] + `;"`;
var TR_VALID_INSPECTION = ` style="background-color:` + COLORS_HEX['02'] + `;"`;
var TR_INVALID_INSPECTION = ` style="background-color:` + COLORS_HEX['03'] + `;"`;
var TD_INSPECTION = ` style="width: auto; text-align: center;"`;
var TD_NO_INSPECTION = ` colspan="3" style="width: auto; text-align: center;"`;
/*
^^^ CONSTANTS ^^^
*/
var html, inspectionTable; /* Alway have an output null or otherwise */
var outFields = ['InspDate', 'InspReporterName', 'InspStatus'];
var parentGlobalID = $feature.GlobalID;
Console(parentGlobalID)
if (!IsEmpty(parentGlobalID)) { /* if parentGloabalID is a thing... continue */
var relatedRecords = Filter(FeatureSetByName($map, "Inspections", outFields, false), 'ShepardParentGlobalID = @parentGlobalID');
var row;
var inspectionRows = [];
if (Count(relatedRecords) > 0) {
var orderedRecords = OrderBy(relatedRecords, "InspDate DESC");
for (var record in orderedRecords) {
/* Assume a valid inspection */
var trStyle = TR_VALID_INSPECTION;
/* Check if not valid and flag with style change */
if (Lower(record.InspStatus) == "not valid") { trStyle = TR_INVALID_INSPECTION };
/* Format the date field to DD/MM/YYYY */
var formattedDate = IIF(!IsEmpty(record.InspDate), Text(Date(record.InspDate), 'DD/MM/YYYY'), null);
/* Create the inspection row */
row = `
<tr` + trStyle + `>
<td` + TD_INSPECTION + `>` + formattedDate + `</td>
<td` + TD_INSPECTION + `>` + record.InspReporterName + `</td>
<td` + TD_INSPECTION + `>` + record.InspStatus + `</td>
</tr>`;
/* Add the row to the Array of rows */
Push(inspectionRows, row);
};
} else {
row = `
<tr>
<td` + TD_NO_INSPECTION + `>Inspections not found</td>
</tr>`;
/* Add the row to the Array of rows */
Push(inspectionRows, row);
};
inspectionTable = `
<table` + TABLE_INSPECTION + `>
<tr` + TR_HEADER_INSPECTION + `>
<th>Inspection Date</th>
<th>Inspector Name</th>
<th>Status</th>
</tr>` + Concatenate(inspectionRows) +
`</table>`;
};
html = inspectionTable; /* if this is null then parentGlobalID is null */
return {
type : 'text',
text : html
};
My rewrite works, but that didn't explain the root cause of the issue. I had to know, so I went line by line in your original expression. It works, but for two extra commas in your code. I have them highlighted in your snippet below.
The 'Filter' and 'OrderBy' FeatureSet functions define two required parameters (features and sqlExpression). You are passing those correctly to those functions, except that you added a comma. This is interpreted by the ArcGIS Runtime, which powers the Field Maps App), as a 3rd argument. That is propagating to you as the 7018 error. It just so happens that the JavaScript API, which powers Map Viewer, is a bit more forgiving, as it is ignoring this issue. The ArcGIS Runtime is apparently a bit more strict/ sensitive with regards to syntax issues.
Hey Guy
Throwing your code snippet into a Javascript block to make a bit more readable.
var parentGlobalID = $feature.GlobalID;
var tbl = "<table border='1' cellpadding='5' cellspacing='0' style='width:100%; direction:rtl;'>";
tbl += "<tr style='background-color: #D3D3D3;'><th>Inspection Date</th><th>Inspector Name</th><th>Status</th></tr>";
// Get related records sorted by date
var relatedRecords = filter(FeatureSetByName($map, "Inspections", ["InspDate","InspReporterName","InspStatus"], false),
'ShepardParentGlobalID = @parentGlobalID',);
if (Count(relatedRecords) > 0) {
for (var i in (OrderBy(relatedRecords, "InspDate DESC",))) {
var rowBackgroundColor = "background-color: #CCFFCC;";
// add backround to row if InspStatus is "Not Valid"
if (i.InspStatus == "Not Valid") {
rowBackgroundColor = "background-color: #FFCCCC;";
}
// format date field to DD/MM/YYYY
var formattedDate = Text(Date(i.InspDate), 'DD/MM/YYYY');
tbl += "<tr style='" + rowBackgroundColor + "'><td style='text-
align:center;'>" + formattedDate + "</td><td style='text-
align:center;'>" + i.InspReporterName + "</td><td style='text-
align:center;'>" + i.InspStatus + "</td></tr>";
}
} else {
tbl += "<tr><td colspan='3'>Inspections not found</td></tr>";
}
tbl += "</table>";
return {
type : 'text',
text : tbl //this property supports html tags
}
I just ran encountered the same issue where it works in Map Viewer, but FM gives (Error code 7018) that states "Arcade expression is invalid". FM didn't like something in an expression that was rough 1200 lines of code. To find what the issue was, I return some simple html at the bottom of my code, then commented out everything else. Starting from the top of my code I slowly uncommented out small sections until I got that error again to isolate what it didn't like. Then within that code section I again isolated the precise statement. For me I was creating an Array directly in a return of a function. It neither liked me doing this in the return nor in general. I ended up defining an empty Array then Push each element to it.
Field Maps runtime did not like the following, but the Map Viewer was fine with it.:
I changed it to
And then the Popup rendered in FM just fine.
Nothing in particular sticks out to me in your expression, except maybe where you define your Array of fields in your Related Records. If that is the line that breaks your expression in FM, then perhaps build that Array of fields in another manner and pass the variable instead of the explicit array for that parameter.
Hey Justin,
Thank you so much for sharing your experience and the detailed troubleshooting steps. It's incredibly helpful!
I've been following your advice and I understand the approach you took to isolate the issue. However, I have no programming experience, and I'm not sure how to implement your suggestion to build the array of fields in another manner and pass the variable instead of the explicit array for that parameter.
Could you possibly provide some guidance on how I can achieve this? Your expertise would be invaluable in helping me resolve this issue.
Thanks again for your time and assistance.
Best regards,
Guy Nizry
No problem.
My team utilizes some fairly complex popups, 1000+ lines of code, building a lot of dynamic HTML. I refactored your expression to loosely match our structure. I did not get a chance to test it as I don't have anything with related records.
/*
vvv CONSTANTS vvv
*/
var COLORS_HEX = {
'01': '#D3D3D3', /* INSPECTION ROW */
'02': '#CCFFCC', /* INSPECTION VALID */
'03': '#FFCCCC', /* INSPECTION NOT VALID */
};
/* STYLE INFO */
var TABLE_INSPECTION = ` style="border: 1px solid; padding: 5px; width: auto; direction: rtl;"`;
var TR_HEADER_INSPECTION = ` style="background-color:` + COLORS_HEX['01'] + `;"`;
var TR_VALID_INSPECTION = ` style="background-color:` + COLORS_HEX['02'] + `;"`;
var TR_INVALID_INSPECTION = ` style="background-color:` + COLORS_HEX['03'] + `;"`;
var TD_INSPECTION = ` style="width: auto; text-align: center;"`;
var TD_NO_INSPECTION = ` colspan="3" style="width: auto; text-align: center;"`;
/*
^^^ CONSTANTS ^^^
*/
var html, inspectionTable; /* Alway have an output null or otherwise */
// html = `<p>If you can see this your popup works!</p>` /* For testing uncomment this, comment out everything below (except the final return!) */
/* Get related records sorted by date */
/* If FM is thowing a fit with how the Array of outfields is created */
var outFields = [];
Push(outFields, Split('InspDate, InspReporterName, InspStatus', ', '));
/* if not, then just define the array and delete lines 28 & 29 above */
// var outFields = ['InspDate', 'InspReporterName', 'InspStatus'];
var parentGlobalID = $feature.GlobalID;
if (!IsEmpty(parentGlobalID)) { /* if parentGloabalID is a thing... continue */
var relatedRecords = Filter(
FeatureSetByName(
$map, "Inspections", outFields, false
),
'ShepardParentGlobalID = @parentGlobalID'
);
var row;
var inspectionRows = [];
if (Count(relatedRecords) > 0) {
var orderedRecords = OrderBy(relatedRecords, "InspDate DESC");
for (var record in orderedRecords) {
/* Assume a valid inspection */
var trStyle = TR_VALID_INSPECTION;
/* Check if not valid and flag with style change */
if (Lower(record.InspStatus) == "not valid") { trStyle = TR_INVALID_INSPECTION };
/* Format the date field to DD/MM/YYYY */
var formattedDate = IIF(!IsEmpty(record.InspDate), Text(Date(record.InspDate), 'DD/MM/YYYY'), null);
/* Create the inspection row */
row = `
<tr` + trStyle + `>
<td` + TD_INSPECTION + `>` + formattedDate + `</td>
<td` + TD_INSPECTION + `>` + record.InspReporterName + `</td>
<td` + TD_INSPECTION + `>` + record.InspStatus + `</td>
</tr>`;
/* Add the row to the Array of rows */
Push(inspectionRows, row);
};
} else {
row = `
<tr>
<td` + TD_NO_INSPECTION + `>Inspections not found</td>
</tr>`;
/* Add the row to the Array of rows */
Push(inspectionRows, row);
};
inspectionTable = `
<table` + TABLE_INSPECTION + `>
<tr` + TR_HEADER_INSPECTION + `>
<th>Inspection Date</th>
<th>Inspector Name</th>
<th>Status</th>
</tr>` + Concatenate(inspectionRows) +
`</table>`;
};
html = inspectionTable; // if this is null then parentGlobalID is null;
return {
type : 'text',
text : html
};
Thank you for your prompt response, Justin.
I implemented the code you provided, but encountered an error message: 'Test execution error: c.toLowerCase is not a function. Verify test data.' It seems to be related to the use of 'Lower' in the code. I'll continue troubleshooting and let you know if I make any progress.
Best regards,
Guy Nizry
Apologies. As I said it wasn't tested. This time a spun up a service with a related table based on what you have in your expression. I created a feature class called Shepard with a 1:M relationship to a table called Inspections. The Shepard FC had globalID and the Inspections TBL had a guid field called ShepardParentGlobalID.
The error you are seeing from the previous example was the result of setting the outfields variable to an Array of Arrays (i.e [[field1, field2...]] instead of an Array of Strings [field1, field2...]). Then our "record" variable is an Array instead of a field and you can't make an Array Lower Case as an Array object has no method (function) named Lower (Arcade name) or toLowerCase(JavaScript Name) .
At any rate, I made that update and tested the code and it works in Map Viewer and Field Maps.
Field Maps
And the final code:
/*
vvv CONSTANTS vvv
*/
var COLORS_HEX = {
'01': '#D3D3D3', /* INSPECTION ROW */
'02': '#CCFFCC', /* INSPECTION VALID */
'03': '#FFCCCC', /* INSPECTION NOT VALID */
};
/* STYLE INFO */
var TABLE_INSPECTION = ` style="border: 1px solid; padding: 5px; width: auto; direction: rtl;"`;
var TR_HEADER_INSPECTION = ` style="background-color:` + COLORS_HEX['01'] + `;"`;
var TR_VALID_INSPECTION = ` style="background-color:` + COLORS_HEX['02'] + `;"`;
var TR_INVALID_INSPECTION = ` style="background-color:` + COLORS_HEX['03'] + `;"`;
var TD_INSPECTION = ` style="width: auto; text-align: center;"`;
var TD_NO_INSPECTION = ` colspan="3" style="width: auto; text-align: center;"`;
/*
^^^ CONSTANTS ^^^
*/
var html, inspectionTable; /* Alway have an output null or otherwise */
var outFields = ['InspDate', 'InspReporterName', 'InspStatus'];
var parentGlobalID = $feature.GlobalID;
Console(parentGlobalID)
if (!IsEmpty(parentGlobalID)) { /* if parentGloabalID is a thing... continue */
var relatedRecords = Filter(FeatureSetByName($map, "Inspections", outFields, false), 'ShepardParentGlobalID = @parentGlobalID');
var row;
var inspectionRows = [];
if (Count(relatedRecords) > 0) {
var orderedRecords = OrderBy(relatedRecords, "InspDate DESC");
for (var record in orderedRecords) {
/* Assume a valid inspection */
var trStyle = TR_VALID_INSPECTION;
/* Check if not valid and flag with style change */
if (Lower(record.InspStatus) == "not valid") { trStyle = TR_INVALID_INSPECTION };
/* Format the date field to DD/MM/YYYY */
var formattedDate = IIF(!IsEmpty(record.InspDate), Text(Date(record.InspDate), 'DD/MM/YYYY'), null);
/* Create the inspection row */
row = `
<tr` + trStyle + `>
<td` + TD_INSPECTION + `>` + formattedDate + `</td>
<td` + TD_INSPECTION + `>` + record.InspReporterName + `</td>
<td` + TD_INSPECTION + `>` + record.InspStatus + `</td>
</tr>`;
/* Add the row to the Array of rows */
Push(inspectionRows, row);
};
} else {
row = `
<tr>
<td` + TD_NO_INSPECTION + `>Inspections not found</td>
</tr>`;
/* Add the row to the Array of rows */
Push(inspectionRows, row);
};
inspectionTable = `
<table` + TABLE_INSPECTION + `>
<tr` + TR_HEADER_INSPECTION + `>
<th>Inspection Date</th>
<th>Inspector Name</th>
<th>Status</th>
</tr>` + Concatenate(inspectionRows) +
`</table>`;
};
html = inspectionTable; /* if this is null then parentGlobalID is null */
return {
type : 'text',
text : html
};
My rewrite works, but that didn't explain the root cause of the issue. I had to know, so I went line by line in your original expression. It works, but for two extra commas in your code. I have them highlighted in your snippet below.
The 'Filter' and 'OrderBy' FeatureSet functions define two required parameters (features and sqlExpression). You are passing those correctly to those functions, except that you added a comma. This is interpreted by the ArcGIS Runtime, which powers the Field Maps App), as a 3rd argument. That is propagating to you as the 7018 error. It just so happens that the JavaScript API, which powers Map Viewer, is a bit more forgiving, as it is ignoring this issue. The ArcGIS Runtime is apparently a bit more strict/ sensitive with regards to syntax issues.
Hello Justin,
I wanted to acknowledge your help in addressing the issue I encountered with ArcGIS Field Maps. Your insights were crucial in identifying the root cause.
Both your initial suggestion and subsequent fix proved effective. Your explanation regarding the extra commas in my original expression was insightful. I appreciate your effort in reviewing the code line by line.
Thanks to your guidance, the error is now resolved. Your expertise and willingness to share knowledge are highly valued.
Thank you for your assistance.
Best regards,
Guy Nizry