Is this possible? I have a working sample that uploads single selected attachments, but I would like to upload multiple attachments with one click. The file input HTML control supports multiple attachments, but when I try to pass an input form element with the "multiple" value into the FeatureLayer addAttachment method (FeatureLayer | API Reference | ArcGIS API for JavaScript ), I get this error message:
TypeError: Cannot read property 'attachmentId' of undefined
The failure seems to occur on this line:
attachmentId = result.attachmentId;
Here is some relevant code. apologies if the code looks wonky, the editor seems to be having issues...
//Add Input file components for file upload var fileInputForm = domConstruct.create("form"); var inputNode = domConstruct.create("input", { id: "files", type: "file", name: "attachment", multiple: "multiple" }); var fileList = domConstruct.create("output", { id: "list" }); domConstruct.place(inputNode, fileInputForm); domConstruct.place(fileInputForm, addDiv); gLayer.addAttachment(objIdVal, fileInputForm, function (result) { console.log(result); var fileInput = query("input", fileInputForm)[0]; array.forEach(fileInput.files, function (file) { var fileName = file.name //This code works for single attachments when the multiple keywork in the input form is removed.. //var fileName = fileInput.value; //fileName = fileName.substr(fileName.lastIndexOf("\\") + 1); //Do something with the file names here.. } }, function (error) { console.log(error); errorUploadMessage.innerHTML = "ERROR:" + error.message; domStyle.set(errorUploadMessage, "display", "block"); }); ;
This is interesting. Have you made any other conclusions from this?
I think this might be possible. I was thinking about trying to make a script to geocode the attachments based on file name (an address) then attach the attachment to the feature class polygon that the attachment is located in.
Do you know if it is possible to add attachments from an existing feature class to another with a spatial join?
Well, I pretty much put this aside and have accepted some limitations on the JSAPI web interface for adding attachments. I could probably do this with the REST API and AJAX requests, making a single add attachment request for each selected layer in my HTML file list. That should work, but it was not crucial for our application so I punted. I'm also not sure if the REST API puts a single file limit on add attachment requests, though I assume that is the case.
I don't know if a spatial join to an existing feature class would work to add attachments. I haven't tried.
Good luck!
It would be nice if addAttachment accepted the multiple files in the form node’s attachment input that is set to multiple. Currently my addAttachment only uploads the last selected file in a multiple selected form file input.
I’ve tried to create a form for each selected file (Similar to Andrew Murdoch's attempt) but for security reasons JS does not allow you to set the files as the values of the generated file inputs. If this was allowed then you could loop through the collection of selected files and call an addAttachment for each created form with the files set as that form’s input value.
Alternatively it would be nice if addAttachment accepted a file instead of the form node.
Additionally it would be useful if the callback was optional. As far as I can tell it is required, please correct me and let me know if it is avoidable.
So far I have a solution that has a few bugs but it does get multiple selected files uploaded/attached to the newly created feature. Here is the majority of the code. Known issues: the 2nd file in the collection has not been loaded in my tests. I'll update this solution if/when I get it ironed out. Also note, the various setTimeout were probably not necessary.
Uggg this was a frustrating solution. After all this wrestling with the addAttachment so much, I'm starting to wonder if I would be better off passing the selected files to a .NET handler and save them to the database from there.
On the page we have a form node (as the addAttatchment requires one) with a file type of input named attachment. When a selection is made it calls the fileUploadChange function.
<div id="divFileSelectComponent" class="fileinputs" title="Select File to Upload">
<form id="formFileUpload_0">
<input id="fileUploadControl" class="filehidden" accept="video/*,image/*," size="25" type="file" name="attachment" onchange="fileUploadChange('fileUploadControl')" />
</form>
</div>
Then when a file is selected we call a function called fileUploadChange that starts a list of the selected files and creates anew form with another input of type file named attach.
NOTE: This is derived from the following example from w3schools: Input FileUpload files Property
fileUploadChange = function (inputID, results) { intFileSelect++; console.log("intFileSelect", intFileSelect); x = document.getElementById(inputID); if ('files' in x) { if (x.files.length == 0) { txtFileSelect = "Select one or more files."; } else { if ('files' in x) { txtFileSelect += "<br><strong>" + intFileSelect + ". </strong>"; var file = x.files[0]; if ('name' in file) { txtFileSelect += file.name; } if ('size' in file) { txtFileSelect += " (" + file.size + " bytes )"; } } } } else { if (x.value == "") { txtFileSelect += "Select one or more files."; } else { txtFileSelect += "The files property is not supported by your browser!"; txtFileSelect += "<br>The path of the selected file: " + x.value; // If the browser does not support the files property, it will return the path of the selected file instead. } } document.getElementById("content2").innerHTML = txtFileSelect; var formID = "formFileUpload_" + intFileSelect, fileInputID = "file_" + intFileSelect, listID = "list_" + intFileSelect; require(["dojo/dom-construct"], function (domConstruct) { var fileInputForm = domConstruct.create("form", { id: formID }); var inputNode = domConstruct.create("input", { id: fileInputID, type: "file", name: "attachment", value: "", onchange: "fileUploadChange('" + fileInputID + "')" }); var fileList = domConstruct.create("output", { id: listID }); domConstruct.place(fileInputForm, "divFileSelectComponent"); domConstruct.place(inputNode, fileInputForm); }); };
Then upon submitting the form the "AddComment" function is called that calls a validation function, then creates a SimpleMarkerSymbol at the clicked point and then adds the comment to the feature layer via applyEdits. If this is successful the new OBJECTID is used to add the attachments with an association to the new feature/comment. If there are more than one files to upload/attach, upon adding the attachment the frist callback adds more attachments otherwise it uses a callback that will end the loop.
Within the "AddComment" function
if (addResults[0].success && intFileSelect > 0) {//(addResults[0].success && files.length > 0) var z = document.getElementById('fileUploadControl'); var file = z.files[0]; console.log(file); setTimeout(function () { if (intFileSelect > 1) { CommentLayer.addAttachment(addResults[0].objectId, dojo.byId('formFileUpload_0'), callbackEdits1, function (err) { console.log("File #1", err); }); } else { CommentLayer.addAttachment(addResults[0].objectId, dojo.byId('formFileUpload_0'), callbackEdits2, function (err) { console.log("File #1", err); }); } }, 250); }
Here are the two callbacks, one to continue looping through the collection of files. The second to be called after the last file has been added.
function callbackEdits1() { intCurrentFile++; var formID = "formFileUpload_" + intCurrentFile; console.log("newObjectId", newObjectId, "formID", formID, "intCurrentFile", intCurrentFile, "/", intFileSelect); if (intCurrentFile < intFileSelect) { setTimeout(function () { CommentLayer.addAttachment(newObjectId, dojo.byId(formID), callbackEdits1, function (err) { console.log("File #", intCurrentFile, newObjectId, formID, err); }); }, 250); } else { setTimeout(function () { CommentLayer.addAttachment(newObjectId, dojo.byId(formID), callbackEdits2, function (err) { console.log("Last File", newObjectId, formID, err); }); }, 250); } }; function callbackEdits2() { console.log("callbackEdits2", intCurrentFile, newObjectId); if (intFileSelect > 0) { dom.byId("userFeedbackMessage").innerHTML = "Your comment and " + intFileSelect + " attachments has been submitted for review.\nPlease allow up to 48 hours for it to be posted onto the map."; } else { dom.byId("userFeedbackMessage").innerHTML = "Your comment has been submitted for review.\nPlease allow up to 48 hours for it to be posted onto the map."; } hideMapBusyIndicator(); showUserFeedbackMessage(); } function errorEdits() { console.log("!!! Error !!!", intCurrentFile, newObjectId); hideMapBusyIndicator(); dom.byId("userFeedbackMessage").innerHTML = "Hello, I am Error. <br/>"; showUserFeedbackMessage(); };
Here is an updated solution that works rather well.
Known issue/s: Selecting a file and then changing your mind will cause a new file to be added to the list rather than changing the selected file.
Potential Improvements: Give the user a way to remove selected files if they change their minds.
Portion of the HTML form
<input id="txtFileName" type="text" class="txtFileName" readonly="readonly" style="margin-left: 0px; width: 100%" required />
<div id="divFileSelectComponent" class="fileinputs" title="Select File to Upload">
<form id="formFileUpload_1">
<input id="file_1" class="filehidden" accept="video/*,image/*," size="25" type="file" name="attachment" onchange="fileUploadChange('file_1')" />
</form>
</div>
<div id="SelectedFilesList"> </div>
<div id="CommentValidationMsg"> </div>
<div class="dijitDialogPaneActionBar" id="CommentButtons">
<button data-dojo-type="dijit/form/Button" type="button" data-dojo-props="onClick:function(){AddComment();}" id="SubmitComment">Submit</button><!--ValidateComment AddComment();-->
<button data-dojo-type="dijit/form/Button" type="button" data-dojo-props="onClick:function(){CancelComment();}" id="cancelComment">Cancel</button>
</div>
JS
var x, txtFileSelect = "", intFileUploads = 1, intSelectedFiles = 0, intCurrentFile, newObjectId = 0; //Portion (2nd half) of the AddComment Function //... CommentLayer.applyEdits([theCommentGraphic], null, null, function (addResults) { console.log("add success", addResults[0].success); console.log("objectId", addResults[0].objectId); CommentActivityLog(addResults[0].objectId, "Comment Added"); setTimeout(function () { newObjectId = addResults[0].objectId; console.log("!!! Comment Added", newObjectId); if (IEversion > 9 || IEversion == 0) { //console.log("IE is greater than 9 or other browser (IEversion:" + IEversion + ")"); if (dojo.byId('file_1').value != "") { intCurrentFile = 1; //intCurrentFile++; if (addResults[0].success && intFileUploads > 0) { if (intFileUploads > 1) { CommentLayer.addAttachment(addResults[0].objectId, dojo.byId('formFileUpload_1'), callbackEdits1, function (err) { console.log("File #1", err); }); } else { CommentLayer.addAttachment(addResults[0].objectId, dojo.byId('formFileUpload_1'), callbackEdits2, function (err) { console.log("Only File (#1)", err); }); } } } else { //console.log("!!! file_1 empty"); dom.byId("userFeedbackMessage").innerHTML = "Your comment has been submitted for review.\nPlease allow up to 48 hours for it to be posted onto the map."; hideMapBusyIndicator(); showUserFeedbackMessage(); } } else { //IE 9 or less //console.log("IE Version ", IEversion); if (dojo.byId('file_1').value != "") { dom.byId("userFeedbackMessage").innerHTML = "Your comment has been submitted for review.\nUnfortunately the file you selected to upload could not be saved.\nPlease allow up to 48 hours for it to be posted onto the map."; } else { dom.byId("userFeedbackMessage").innerHTML = "Your comment has been submitted for review.\nPlease allow up to 48 hours for it to be posted onto the map."; } hideMapBusyIndicator(); showUserFeedbackMessage(); } }, 250); }, function (err) { //when an error occurs console.log("applyEdits error ", err.message); console.log("applyEdits err ", err); //alert("Apply Edits Failed: " + err.message); errorEdits(err); }); function callbackEdits() { newObjectId = addResults[0].objectId; console.log("!!!", addResults[0].success, String(addResults[0].objectId)); theMap.graphics.clear(); theMap.infoWindow.hide(); } }; function callbackEdits1() { //console.log("!!! newObjectId", newObjectId); //console.log("newObjectId", newObjectId, "intFileUploads", intFileUploads); intCurrentFile++; var formID = "formFileUpload_" + intCurrentFile; var fileInputID = "file_" + intCurrentFile; if (dojo.byId(fileInputID).value != "") { console.log(dojo.byId(fileInputID).value); var z = document.getElementById(fileInputID); var file = z.files[0]; console.log("newObjectId", newObjectId, "file: ", file.name, file.size, "formID", formID, "intCurrentFile", intCurrentFile, "/", intFileUploads); if (intCurrentFile < intSelectedFiles) { //(intFileUploads - 1) CommentLayer.addAttachment(newObjectId, dojo.byId(formID), callbackEdits1, function (err) { console.log("File #", intCurrentFile, newObjectId, formID, fileInputID, err); errorEdits(err); }); } else { CommentLayer.addAttachment(newObjectId, dojo.byId(formID), callbackEdits2, function (err) { console.log("Last File", intCurrentFile, newObjectId, formID, fileInputID, err); errorEdits(err); }); } } else { console.log("newObjectId", newObjectId, "file: No file", "formID", formID, "intCurrentFile", intCurrentFile, "/", intFileUploads); if (intCurrentFile == intSelectedFiles) { //intFileUploads callbackEdits2(); } } }; function callbackEdits2() { console.log("callbackEdits2", newObjectId, intCurrentFile, intSelectedFiles); if (intFileUploads > 0) { dom.byId("userFeedbackMessage").innerHTML = "Your comment and " + intFileUploads + " attachments has been submitted for review.\nPlease allow up to 48 hours for it to be posted onto the map."; } else { dom.byId("userFeedbackMessage").innerHTML = "Your comment has been submitted for review.\nPlease allow up to 48 hours for it to be posted onto the map."; } hideMapBusyIndicator(); showUserFeedbackMessage(); DestroyFileUploads(); } function errorEdits(err) { console.log("!!! Error !!! newObjectId", newObjectId, "intCurrentFile", intCurrentFile, "intSelectedFiles", intSelectedFiles, "intFileUploads", intFileUploads, "\n", err); var formID = "formFileUpload_" + intCurrentFile; var fileInputID = "file_" + intCurrentFile; var z = document.getElementById(fileInputID); //document.getElementById(formID); var file = z.files[0]; var FileName = "NA", FileSize = 0; if (z.files.length == 0) { console.log("!!! No file !!!", formID, fileInputID); } else { FileName = file.name; FileSize = file.size; console.log("!!! file !!!", file.name, file.size, "form & file", formID, fileInputID); } //email notification about the error try { var EmailHandler = "/Map/HandlerEmail.ashx?Error=" + err + "&FileName=" + FileName + "&FileSize=" + FileSize + "&Misc=intCurrentFile: " + intCurrentFile + ", newObjectId: " + newObjectId + ", intCurrentFile: " + intCurrentFile + ", intFileUploads: " + intFileUploads; dojo.xhrGet({ url: EmailHandler, load: function (result) { console.log("!!! Sending Email", result); } }); } catch (err) { console.log("!!! Email ERROR:", err); } hideMapBusyIndicator(); dom.byId("userFeedbackMessage").innerHTML = "Something Happened <br/>" + err.message; showUserFeedbackMessage(); DestroyFileUploads(); }; fileUploadChange = function (inputID, results) { intSelectedFiles++; intFileUploads++; console.log("intFileUploads", intFileUploads, "intSelectedFiles", intSelectedFiles); //console.log("inputID", inputID); //console.log("results size", results.size); x = document.getElementById(inputID); if ('files' in x) { if (x.files.length == 0) { txtFileSelect = "Select a file."; } else { if ('files' in x) { txtFileSelect += "<br><strong>" + intSelectedFiles + ". </strong>"; var file = x.files[0]; if ('name' in file) { txtFileSelect += file.name; } if ('size' in file) { txtFileSelect += " (" + file.size + " bytes )"; } } } } else { if (x.value == "") { txtFileSelect += "Select a file."; //Select one or more files. } else { txtFileSelect += "The files property is not supported by your browser!"; txtFileSelect += "<br>The path of the selected file: " + x.value; // If the browser does not support the files property, it will return the path of the selected file instead. } } document.getElementById("SelectedFilesList").innerHTML = txtFileSelect; var formID = "formFileUpload_" + intFileUploads, fileInputID = "file_" + intFileUploads, listID = "list_" + intFileUploads; var fileInputForm = domConstruct.create("form", { id: formID }); var inputNode = domConstruct.create("input", { id: fileInputID, type: "file", name: "attachment", value: "", accept: "video/*,image/*,", class: "filehidden", onchange: "fileUploadChange('" + fileInputID + "')" }); var fileList = domConstruct.create("output", { id: listID }); domConstruct.place(fileInputForm, "divFileSelectComponent"); domConstruct.place(inputNode, fileInputForm); }; DestroyFileUploads = function () { console.log("!!!DestroyFileUploads!!! intFileUploads", intFileUploads, "intSelectedFiles", intSelectedFiles); //Clean up as preparation for another comment submittal //Loop through starting with the 2nd file upload control for (var i = 2; i < intFileUploads + 1; i++) { var formID = "formFileUpload_" + i; var fileInputID = "file_" + i; console.log("formID", formID, "fileInputID", fileInputID); if (dojo.byId(fileInputID).value != "") { var x = document.getElementById(fileInputID); var file2 = x.files[0]; console.log("file name", file2.name, "size", file2.size); } else { console.log("No file"); } //Destroy the generated forms and file upload controls. domConstruct.destroy(fileInputID); domConstruct.destroy(formID); } //clear the list of files and reset the variables dom.byId("SelectedFilesList").innerHTML = " "//SelectedFilesList intFileUploads = 1; intSelectedFiles = 0; txtFileSelect = ""; }
Sort of hacky, but I made this work with:
<form id="newForm">
<div class="form-group" id="crDoc1">
<input class="form-control" id="crDocInput1" type="file"/>
</div>
<div class="form-group" id="crDoc2">
<input class="form-control" id="crDocInput2" type="file"/>
</div>
<div class="form-group" id="crDoc3">
<input class="form-control" id="crDocInput3" type="file"/>
</div>
</form>
Several inputs for file inside of one form node, with no name="attachment" on any. Then on a button click:
docInputs = ["crDocInput1", "crDocInput2", "crDocInput3"];
for (k=0;k<docInputs.length;k++){
if (document.getElementById(docInputs
document.getElementById(docInputs
var formNode = document.getElementById("newCheckForm");
relatedTable.addAttachment(objectId, formNode, function success(success){
console.log(success);
}, function error(err){
console.log(err);
});
document.getElementById(docInputs
}
This takes a list of the file inputs, loops through them and if they are not empty, assigns the "attachment" name to the input, submits to the appropriate objectID, and then removes the "attachment" name.
I'm not sure if this is relevant or not, but I had an issue with publishing a feature class to ArcGIS.com from Desktop with feature attachments, whereas the attachments would disappear once they reached ArcGIS.com. As soon as I removed the field in my attribute table called "AttributeID", the attachments would no longer fail on the upload. So this post came up in my google search, i just thought i'd mention to try removing any field names called "AttributeID" and see if it fixes your problem.