Howdy!
I'm trying to figure out how to edit multiple feature layers within the Esri JS API 4 ecosystem. I have the editing of a single feature layer nailed down:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<title>Street Closures and Detours Editor</title>
<link rel="stylesheet" href="https://js.arcgis.com/4.8/esri/css/main.css">
<script src="https://js.arcgis.com/4.8/"></script>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
.editArea-container {
background: #fff;
font-family: "Avenir Next W00", "Helvetica Neue", Helvetica, Arial, sans-serif;
line-height: 1.5em;
overflow: auto;
padding: 12px 15px;
width: 300px;
height:100%;
}
.edit-button:hover,
.edit-button:focus {
background-color: #e4e4e4;
}
.inputInfo {
font-size: 12px;
height: 32px;
margin-bottom: 6px;
padding: 0 6px;
width: 100%;
}
.list-heading {
font-weight: normal;
margin-top: 20px;
margin-bottom: 10px;
color: #323232;
}
.edit-button {
font-size: 14px;
height: 32px;
margin-top: 10px;
width: 100%;
background-color: transparent;
border: 1px solid #0079c1;
color: #0079c1;
}
.or-wrap {
background-color: #e0e0e0;
height: 1px;
margin: 2em 0;
overflow: visible;
}
.or-text {
background: #fff;
line-height: 0;
padding: 0 1em;
position: relative;
top: -.75em;
}
input:invalid {
border: 1px solid red;
}
input:valid {
border: 1px solid green;
}
</style>
<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/layers/Layer",
"esri/layers/FeatureLayer",
"esri/Graphic",
"esri/widgets/Expand",
"esri/widgets/Home",
"esri/widgets/Search",
"esri/geometry/Extent",
"esri/Viewpoint",
"esri/core/watchUtils",
"esri/views/2d/draw/Draw",
"esri/geometry/Polyline",
"esri/geometry/geometryEngine",
"dojo/on",
"dojo/dom",
"dojo/domReady!"
],
function(
Map, MapView, Layer, FeatureLayer, Graphic, Expand,
Home, Search, Extent, Viewpoint, watchUtils, Draw, Polyline, geometryEngine,
on, dom
) {
var closureFL, editExpand;
var editArea, attributeEditing, inputDescription,
inputUserInfo, updateInstructionDiv;
var map = new Map({
basemap: "dark-gray"
});
var initialExtent = new Extent({
xmin: -9024777,
xmax: -8968329,
ymin: 4179026,
ymax: 4210862,
spatialReference: 102100
});
var view = new MapView({
container: "viewDiv",
map: map,
extent: initialExtent
});
closureLayer = new FeatureLayer({
url: "https://someserver.domain/arcgis/rest/services/serviceFolder/serviceName/FeatureServer/0",
outFields: ["*"]
});
addLayer(closureLayer);
setupEditing();
setupView();
function addLayer(closureLayer) {
closureFL = closureLayer;
map.add(closureLayer);
}
function applyEdits(params) {
unselectFeature();
var promise = closureFL.applyEdits(params);
editResultsHandler(promise);
}
function editResultsHandler(promise) {
promise
.then(function(editsResult) {
var extractObjectId = function(result) {
return result.objectId;
};
if (editsResult.addFeatureResults.length > 0) {
var adds = editsResult.addFeatureResults.map(
extractObjectId);
newIncidentId = adds[0];
selectFeature(newIncidentId);
}
})
.catch(function(error) {
console.log("===============================================");
console.error("[ applyEdits ] FAILURE: ", error.code, error.name,
error.message);
console.log("error = ", error);
});
}
view.on("click", function(event) {
unselectFeature();
view.hitTest(event).then(function(response) {
if (response.results.length > 0 && response.results[0].graphic) {
var feature = response.results[0].graphic;
selectFeature(feature.attributes[closureFL.objectIdField]);
closureOID.value = feature.attributes[
"OBJECTID"];
closureBlockName.value = feature.attributes[
"BLOCKNM"];
closureLoc.value = feature.attributes[
"LOCDESC"];
closureBlockType.value = feature.attributes[
"BLOCKTYPE"];
closureComm.value = feature.attributes[
"COMMENT"];
closureClose.value = feature.attributes[
"FULLCLOSE"];
closureAltRoute.value = feature.attributes[
"ALTROUTE"];
closureStartDate.value = feature.attributes[
"STARTDATE"];
closureStartHour.value = feature.attributes[
"StartHour"];
closureEndDate.value = feature.attributes[
"ENDDATE"];
closureEndHour.value = feature.attributes[
"EndHour"];
closureContact.value = feature.attributes[
"CONTACT"];
closureDir.value = feature.attributes[
"DIRECTION"];
closureActive.value = feature.attributes[
"ACTIVE"];
closureLink.value = feature.attributes[
"Hyperlink"];
closureCreationDate.value = feature.attributes[
"CreationDate"];
closureCreator.value = feature.attributes[
"Creator"];
closureEditDate.value = feature.attributes[
"EditDate"];
closureEditor.value = feature.attributes[
"Editor"];
closureCATS.value = feature.attributes[
"CATS"];
closureDateComm.value = feature.attributes[
"DateCommunicated"];
closureUpdated.value = feature.attributes[
"Updated"];
closureType.value = feature.attributes[
"ClosureType"];
closureRespDept.value = feature.attributes[
"ResponsibleDepartment"];
closureContactComm.value = feature.attributes[
"ContactCommunications"];
closureMapURL.value = feature.attributes[
"ClosureMapURL"];
closureGlobalID.value = feature.attributes[
"GlobalID"];
closureAuthCreator.value = feature.attributes[
"created_user"];
closureAuthCreationDate.value = feature.attributes[
"created_date"];
closureAuthEditor.value = feature.attributes[
"last_edited_user"];
closureAuthEditDate.value = feature.attributes[
"last_edited_date"];
closureApproval.value = feature.attributes[
"ApproveNotification"];
closureID.value = feature.attributes[
"ClosureID"];
closureProject.value = feature.attributes[
"SpecialProject"];
closureProjectURL.value = feature.attributes[
"SpecialProjectURL"];
attributeEditing.style.display = "block";
updateInstructionDiv.style.display = "none";
}
});
});
function selectFeature(objectId) {
var selectionSymbol = {
type: "simple-marker",
color: [0, 0, 0, 0],
style: "square",
size: "40px",
outline: {
color: [0, 255, 255, 1],
width: "3px"
}
};
var query = closureFL.createQuery();
query.where = closureFL.objectIdField + " = " + objectId;
closureFL.queryFeatures(query).then(function(results) {
if (results.features.length > 0) {
editFeature = results.features[0];
editFeature.symbol = selectionSymbol;
view.graphics.add(editFeature);
}
});
}
function unselectFeature() {
attributeEditing.style.display = "none";
updateInstructionDiv.style.display = "block";
closureOID.value = null;
closureBlockName.value = null;
closureLoc.value = null;
closureBlockType.value = null;
closureComm.value = null;
closureClose.value = null;
closureAltRoute.value = null;
closureStartDate.value = null;
closureStartHour.value = null;
closureEndDate.value = null;
closureEndHour.value = null;
closureContact.value = null;
closureDir.value = null;
closureActive.value = null;
closureLink.value = null;
closureCreationDate.value = null;
closureCreator.value = null;
closureEditDate.value = null;
closureEditor.value = null;
closureCATS.value = null;
closureDateComm.value = null;
closureUpdated.value = null;
closureType.value = null;
closureRespDept.value = null;
closureContactComm.value = null;
closureMapURL.value = null;
closureGlobalID.value = null;
closureAuthCreator.value = null;
closureAuthCreationDate.value = null;
closureAuthEditor.value = null;
closureAuthEditDate.value = null;
closureApproval.value = null;
closureID.value = null;
closureProject.value = null;
closureProjectURL.value = null;
view.graphics.removeAll();
}
function setupView() {
var homeButton = new Home({
view: view,
viewpoint: new Viewpoint({
targetGeometry: initialExtent
})
});
view.ui.add(homeButton, "top-left");
editExpand = new Expand({
expandIconClass: "esri-icon-edit",
expandTooltip: "Expand Edit",
expanded: true,
view: view,
content: editArea
});
view.ui.add(editExpand, "top-right");
var searchWidget = new Search({
view: view
});
view.ui.add(searchWidget, {
position: "top-left",
index: 0
});
}
function setupEditing() {
editArea = dom.byId("editArea");
updateInstructionDiv = dom.byId("updateInstructionDiv");
attributeEditing = dom.byId("featureUpdateDiv");
closureOID = dom.byId("closureOID");
closureBlockName = dom.byId("closureBlockName");
closureLoc= dom.byId("closureLoc");
closureBlockType = dom.byId("closureBlockType");
closureComm = dom.byId("closureComm");
closureClose = dom.byId("closureClose");
closureAltRoute = dom.byId("closureAltRoute");
closureStartDate = dom.byId("closureStartDate");
closureStartHour = dom.byId("closureStartHour");
closureEndDate = dom.byId("closureEndDate");
closureEndHour = dom.byId("closureEndHour");
closureContact = dom.byId("closureContact");
closureDir = dom.byId("closureDir");
closureActive = dom.byId("closureActive");
closureLink = dom.byId("closureLink");
closureCreationDate = dom.byId("closureCreationDate");
closureCreator = dom.byId("closureCreator");
closureEditDate = dom.byId("closureEditDate");
closureEditor = dom.byId("closureEditor");
closureCATS = dom.byId("closureCATS");
closureDateComm = dom.byId("closureDateComm");
closureUpdated = dom.byId("closureUpdated");
closureType = dom.byId("closureType");
closureRespDept = dom.byId("closureRespDept");
closureContactComm = dom.byId("closureContactComm");
closureMapURL = dom.byId("closureMapURL");
closureGlobalID = dom.byId("closureGlobalID");
closureAuthCreator = dom.byId("closureAuthCreator");
closureAuthCreationDate = dom.byId("closureAuthCreationDate");
closureAuthEditor = dom.byId("closureAuthEditor");
closureAuthEditDate = dom.byId("closureAuthEditDate");
closureApproval = dom.byId("closureApproval");
closureID = dom.byId("closureID");
closureProject = dom.byId("closureProject");
closureProjectURL = dom.byId("closureProjectURL");
on(dom.byId("btnUpdate"), "click", function(event) {
if (editFeature) {
editFeature.attributes["OBJECTID"] = closureOID.value;
editFeature.attributes["BLOCKNM"] = closureBlockName.value;
editFeature.attributes["LOCDESC"] = closureLoc.value;
editFeature.attributes["BLOCKTYPE"] = closureBlockType.value;
editFeature.attributes["COMMENT"] = closureComm.value;
editFeature.attributes["FULLCLOSE"] = closureClose.value;
editFeature.attributes["ALTROUTE"] = closureAltRoute.value;
editFeature.attributes["STARTDATE"] = closureStartDate.value;
editFeature.attributes["StartHour"] = closureStartHour.value;
editFeature.attributes["ENDDATE"] = closureEndDate.value;
editFeature.attributes["EndHour"] = closureEndHour.value;
editFeature.attributes["CONTACT"] = closureContact.value;
editFeature.attributes["DIRECTION"] = closureDir.value;
editFeature.attributes["ACTIVE"] = closureActive.value;
editFeature.attributes["Hyperlink"] = closureLink.value;
editFeature.attributes["CreationDate"] = closureCreationDate.value;
editFeature.attributes["Creator"] = closureCreator.value;
editFeature.attributes["EditDate"] = closureEditDate.value;
editFeature.attributes["Editor"] = closureEditor.value;
editFeature.attributes["CATS"] = closureCATS.value;
editFeature.attributes["DateCommunicated"] = closureDateComm.value;
editFeature.attributes["Updated"] = closureUpdated.value;
editFeature.attributes["ClosureType"] = closureType.value;
editFeature.attributes["ResponsibleDepartment"] = closureRespDept.value;
editFeature.attributes["ContactCommunications"] = closureContactComm.value;
editFeature.attributes["ClosureMapURL"] = closureMapURL.value;
editFeature.attributes["GlobalID"] = closureGlobalID.value;
editFeature.attributes["created_user"] = closureAuthCreator.value;
editFeature.attributes["created_date"] = closureAuthCreationDate.value;
editFeature.attributes["last_edited_user"] = closureAuthEditor.value;
editFeature.attributes["last_edited_date"] = closureAuthEditDate.value;
editFeature.attributes["ApproveNotification"] = closureApproval.value;
editFeature.attributes["ClosureID"] = closureID.value;
editFeature.attributes["SpecialProject"] = closureProject.value;
editFeature.attributes["SpecialProjURL"] = closureProjectURL.value;
var edits = {
updateFeatures: [editFeature]
};
applyEdits(edits);
}
});
view.when(function(event) {
var draw = new Draw({
view: view
});
var newClosureButton = document.getElementById("addClosureButton");
newClosureButton.onclick = function() {
view.graphics.removeAll();
enableCreateLine(draw, view);
}
});
function enableCreateLine(draw, view) {
var action = draw.create("polyline", {
mode: "click"
});
view.focus();
action.on("vertex-add", updateVertices);
action.on("vertex-remove", updateVertices);
action.on("cursor-update", createGraphic);
action.on("draw-complete", completeGraphic);
}
function updateVertices(event) {
var result = createGraphic(event);
}
function createGraphic(event) {
var vertices = event.vertices;
view.graphics.removeAll();
var graphic = new Graphic({
geometry: new Polyline({
paths: vertices,
spatialReference: view.spatialReference
}),
symbol: {
type: "simple-line",
color: [4, 90, 141],
width: 3,
cap: "round",
join: "round"
}
});
view.graphics.add(graphic);
}
function completeGraphic(event) {
var vertices = event.vertices;
view.graphics.removeAll();
var graphic = new Graphic({
geometry: new Polyline({
paths: vertices,
spatialReference: view.spatialReference
})
});
var edits = {
addFeatures: [graphic]
}
applyEdits(edits);
}
on(dom.byId("btnDelete"), "click", function() {
var edits = {
deleteFeatures: [editFeature]
};
applyEdits(edits);
});
view.when(function() {
watchUtils.whenTrue(view, "stationary", function() {
if (editExpand) {
if (view.zoom <= 8) {
editExpand.domNode.style.display = "none";
} else {
editExpand.domNode.style.display = "block";
}
}
});
});
}
function handleLayerLoadError(error) {
console.log("Layer failed to load: ", error);
}
});
</script>
</head>
<body>
<div id="editArea" class="editArea-container" style="overflow-y:auto;">
<div id="addFeatureDiv">
<h3 class="list-heading">Report Road Closures or Detours</h3>
<ul style="font-size: 13px; padding-left: 1.5em;">
<li>Click the Appropriate Button</li>
<li>Draw your Feature on the Map</li>
</ul>
<div style="text-align:center;">
<span>
<input type="button" class="edit-button" value="Report Closure" id="addClosureButton" style="width:45%;margin-right:5px">
<input type="button" class="edit-button" value="Report Detour" id="addDetourButton" style="width:45%;margin-left:5px;">
</span>
</div>
</div>
<div id="updateInstructionDiv" style="text-align:center">
<p class="or-wrap">
<span class="or-text">Or</span>
</p>
<p>Select an incident to edit or delete.</p>
</div>
<div id="featureUpdateDiv" style="display:none; margin-top: 1em;">
<h3 class="list-heading">Enter the incident information</h3>
<div id="attributeArea">
<label for="closureOID">Object ID:</label>
<input class="inputInfo" readonly type="text" id="closureOID">
<br>
<label for="closureID">Closure ID:</label>
<input class="inputInfo" readonly type="text" id="closureID">
<br>
<label for="closureGlobalID">Global ID:</label>
<input class="inputInfo" readonly type="text" id="closureGlobalID">
<br>
<label for="closureBlockName">Closure Name:</label>
<input class="inputInfo" required type="text" id="closureBlockName" placeHolder="Please enter the name of the closure">
<br>
<label for="closureLoc">Closure Location:</label>
<input class="inputInfo" required id="closureLoc" placeHolder="Please describe the closure's location">
<br>
<label for="closureBlockType">Closure Type:</label>
<select class="inputInfo" required type="text" id="closureBlockType" placeHolder="Please choose the appropriate closure type">
<option value="Construction">Construction</option>
<option value="Event">Event</option>
<option value="Incident">Incident</option>
<option value="Other">Other</option>
</select>
<br>
<label for="closureComm">Comment:</label>
<input class="inputInfo" required type="text" id="closureComm" placeHolder="Please provide additional information on the closure">
<br>
<label for="closureClose">Closure Type:</label>
<select class="inputInfo" required type="text" id="closureClose" placeHolder="Please select the appropriate closure type">
<option value="Yes">Full Closure</option>
<option value="No">Partial Closure</option>
</select>
<br>
<label for="closureAltRoute">Alternate Route:</label>
<input class="inputInfo" required type="text" id="closureAltRoute" placeHolder="Please provide the alternate route">
<br>
<label for="closureStartDate">Start Date:</label>
<input class="inputInfo" required type="date" id="closureStartDate" placeHolder="Please indiciate what day the closure will start">
<br>
<label for="closureStartHour">Start Time:</label>
<input class="inputInfo" required type="time" id="closureStartHour" placeHolder="Please indicate when the closure will start">
<br>
<label for="closureEndDate">End Date:</label>
<input class="inputInfo" required type="date" id="closureEndDate" placeHolder="Please indiciate what day the closure will end">
<br>
<label for="closureEndHour">End Time:</label>
<input class="inputInfo" required type="time" id="closureEndHour" placeHolder="Please indicate when the closure will end">
<br>
<label for="closureContact">Contact:</label>
<input class="inputInfo" required type="text" id="closureContact" placeHolder="Please provide the name and email of the primary contact for this closure">
<br>
<label for="closureDir">Direction:</label>
<input class="inputInfo" type="text" id="closureDir" placeHolder="Please specify the direction of the closure (if applicable)">
<br>
<label for="closureActive">Active:</label>
<select class="inputInfo" required type="text" id="closureActive" placeHolder="Please choose whether this closure is active or not">
<option value="Yes">Yes</option>
<option value="No">No</option>
</select>
<br>
<label for="closureLink">Hyperlink:</label>
<input class="inputInfo" required type="url" id="closureLink" placeHolder="Please provide the hyperlink that shows this location">
<br>
<label for="closureCreationDate">Creation Date:</label>
<input class="inputInfo" required type="date" id="closureCreationDate" placeHolder="Please specify the date this record is being created">
<br>
<label for="closureCreator">Creator:</label>
<input class="inputInfo" type="text" id="closureCreator" placeHolder="Please provide your name">
<br>
<label for="closureEditDate">Edit Date:</label>
<input class="inputInfo" type="date" id="closureEditDate" placeHolder="Please specify the date this record was updated">
<br>
<label for="closureEditor">Editor:</label>
<input class="inputInfo" type="text" id="closureEditor" placeHolder="Please specify your name (if updating)">
<br>
<label for="closureCATS">CATS:</label>
<select class="inputInfo" type="text" id="closureCATS" placeHolder="Please select the appropriate CATS statement">
<option value="CATS will be affected.">CATS will be affected.</option>
<option value="CATS will not be affected.">CATS will not be affected.</option>
</select>
<br>
<label for="closureDateComm">Date Communicated:</label>
<input class="inputInfo" type="date" id="closureDateComm" placeHolder="Please specify the date this closure was communicated">
<br>
<label for="closureUpdated">Updated:</label>
<input class="inputInfo" type="date" id="closureUpdated" placeHolder="Please specify the date this record was updated">
<br>
<label for="closureType">Closure Type:</label>
<select class="inputInfo" type="text" id="closureType" placeHolder="Please select the appropriate closure type">
<option value="Lane">Lane</option>
<option value="Median">Median</option>
<option value="Resurfacing">Resurfacing</option>
<option value="Street">Street</option>
</select>
<br>
<label for="closureRespDept">Responsible Department:</label>
<select class="inputInfo" type="text" id="closureRespDept" placeHolder="Please select the department responsible for this closure">
<option value="Charlotte Water">Charlotte Water</option>
<option value="Solid Waste Services">Solid Waste Services</option>
<option value="Stormwater">Stormwater</option>
<option value="Transportation">Transportation</option>
</select>
<br>
<label for="closureContactComm">Contact Communications:</label>
<input class="inputInfo" type="text" id="closureContactComm" placeHolder="Enter description" value="Judy Dellert-O'Keef, CDOT Community Engagement Manager at 704-432-0105 or jdellert-okeef@charlottenc.gov.">
<br>
<label for="closureMapURL">Closure Map URL:</label>
<input class="inputInfo" required type="text" id="closureMapURL" placeHolder="Please input the URL for this closure">
<br>
<input class="inputInfo" type="hidden" id="closureAuthCreator" placeHolder="Enter description">
<br>
<input class="inputInfo" type="hidden" id="closureAuthCreationDate" placeHolder="Enter description">
<br>
<input class="inputInfo" type="hidden" id="closureAuthEditor" placeHolder="Enter description">
<br>
<input class="inputInfo" type="hidden" id="closureAuthEditDate" placeHolder="Enter description">
<br>
<label for="closureApproval">Closure Approval:</label>
<select class="inputInfo" type="text" id="closureApproval" placeHolder="Please indicate whether this closure has been approved">
<option value="Yes">Yes</option>
<option value="No">No</option>
</select>
<br>
<label for="closureProject">Special Project:</label>
<select class="inputInfo" type="text" id="closureProject" placeHolder="Please select the special project causing this closure (if applicable)">
<option value="Blue Line Extension">Blue Line Extension</option>
<option value="Cross Charlotte Trail">Cross Charlotte Trail</option>
<option value="Gold Line Extension">Gold Line Extension</option>
<option value="Gold Line Extension (phase two)">Gold Line Extension (phase two)</option>
</select>
<br>
<label for="closureProjectURL">Special Project URL:</label>
<input class="inputInfo" type="url" id="closureProjectURL" placeHolder="Please enter the project URL (if applicable)">
<br>
<input type="button" class="edit-button" value="Update incident info" id="btnUpdate">
</div>
<div id="deleteArea">
<input type="button" class="edit-button" value="Delete incident" id="btnDelete">
</div>
</div>
</div>
<div id="viewDiv"></div>
<div id="line-button" class="esri-widget esri-widget--button esri-interactive" title="Draw polyline">
<span class="esri-icon-polyline"></span>
</div>
</body>
</html>
But when I add a second Feature Layer and attempt to put an if/else route in the JS, I'm not getting any luck. Even more simply, when I add a second FeatureLayer the promise that is issued runs into an issue handling multiple layers. The least elegant solution I can think of is to literally just duplicate the functions and promise-strings for the second FeatureLayer, but that seems pretty heavy-handed.
If anyone has any samples or insight into editing multiple Feature Layers in the Esri JS API 4 environment, I'd be really grateful to see 'em!
Robert Scheitlin, GISP -- any insight from your ginormous brain?