Add multiple attachments at once with FeatureLayer addAttachment method?

6270
7
06-01-2015 02:35 PM
AndrewMurdoch
Occasional Contributor

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");
                                  });








;
Tags (2)
0 Kudos
7 Replies
BrianWays1
New Contributor III

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?

0 Kudos
AndrewMurdoch
Occasional Contributor

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!

JoshVan_Kylen
New Contributor III

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.

0 Kudos
JoshVan_Kylen
New Contributor III

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();
            };
0 Kudos
JoshVan_Kylen
New Contributor III

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 = "";
        }
LindaDunklee
New Contributor III

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).files.length != 0){
document.getElementById(docInputs).name = "attachment";
var formNode = document.getElementById("newCheckForm");
relatedTable.addAttachment(objectId, formNode, function success(success){
console.log(success);
}, function error(err){
console.log(err);
});
document.getElementById(docInputs).name = '';
}

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.

0 Kudos
DanAllen
Occasional Contributor III

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.

0 Kudos