0

What I want

People click add image button, they select an image, image is added to gallery.

They can delete images by clicking cross sign and re click add image button to add more images.

This all works, I've a reference to all File elements.

However, I can't figure out how to send the files in post request of form.

Problem the problem is that You can't create FileList out array of file, or set array of files as input.files = arrOfFiles.

Input element itself doesn't let you add more files, or remove files... it simply replaces old file with new file(s).

which is not what i want therefore i'm keep reference to file objects in js, and letting user remove images or add more.

I know i can send individual file as XHR, but I want to send them through form that already exists.

I wanted to know a way to send files through form not js, but apparently that's not possible

Muhammad Umer
  • 17,263
  • 19
  • 97
  • 168
  • Not possible. Just convert to base 64 and upload to server – Darkrum Feb 05 '18 at 01:31
  • @MikeMcCaughan no, that is asking how to send data via ajax. I'm asking how to send files through form but be able to modify it – Muhammad Umer Feb 05 '18 at 01:33
  • It's not clear what you're doing or why... If you want to send them through the form, send them through the form using `input type="file"` elements. You can manipulate the list of `input` elements using the DOM. No need to load the data into JavaScript. – Heretic Monkey Feb 05 '18 at 01:40
  • input element allows you to select multiple files, so if user wants to remove 1 of the file that was selected to upload, there is no way. yes i could only let user select 1 file per upload and delete that input element on user removing it from gallery – Muhammad Umer Feb 05 '18 at 01:42

2 Answers2

2

That's exactly what the FormData API is for: create from scratch a form's data that you will be able to upload to your server as if it were created from a <form> object, except that you can control what goes there or not.

So to append a File or a Blob in a FormData, the code is

var fd = new FormData();
fd.append(field_name, blob, file_name);

To append multiple files, you can call again fd.append, but note that backend often need to have the field_name formatted in such a way they can know multiple values are expected here. Usually this is done by adding [] after your fieldname.

var fd = new FormData();
fd.append('files[]', blob_1, file_name_1);
fd.append('files[]', blob_2, file_name_2);

And then you can send it through an AJAX request to your server, which won't make the difference between this request and a real one made froma single <input multiple name="files[]">.

Note that in case of File, file_name is optional and will default to the File's name if not set. However, it is needed for Blobs if you don't want a random name to be set.

var file_1 = new File(['foo'], 'file1.txt',{type:'text/plain'});
var file_2 = new File(['bar'], 'file2.txt', {type:'text/plain'});

var fd = new FormData();
fd.append('files[]', file_1);
fd.append('files[]', file_2);

console.log(...fd.entries());

// and to send it to your server
var xhr = new XMLHttpRequest();
xhr.open('POST', 'your_server_url');
xhr.send(fd);
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • so I guess ill intercept form submit, take form data and add files to form data and then send it, and upon sending it redirect to wherever server would tell – Muhammad Umer Feb 05 '18 at 01:45
  • 1
    @MuhammadUmer that's exactly it. In your form's `submit` event, call `event.preventDefault()`, then build your FormData. Note that if you have other fields in your `
    `, you can even let the browser create the FormData from its current state by calling `new FormData(your_form_element)`.
    – Kaiido Feb 05 '18 at 01:50
  • 1
    @MuhammadUmer so again a quick Google would have told you that input multiple is read only and overwrites if the user selects again. So kaiido is just telling you what most of us told you. IT IS NOT POSSIBLE. You have to roll your own. How you implement that is up to you. – Darkrum Feb 05 '18 at 02:47
  • @Darkrum I am not sure where you are coming from, but I am not saying at all that it's not possible, quite the contrary. Also, you seem to get the problem wrong: OP doesn't want to have a single ``, he wants to get multiple ``s so that he can handle these files with his own UI. And finally, yes [you can change the FileList of an input](https://stackoverflow.com/questions/47119426/how-to-set-file-objects-and-length-property-at-filelist-object-where-the-files-a/47172409#47172409), even if it's still a hack, and that FormData is still the best way to handle such case. – Kaiido Feb 05 '18 at 03:08
  • @Darkrum Yea, I see. However, There is functionality missing. If it were only security flaw then how come you can do it via Ajax, doesn't make sense. But yea i see, he is saying same thing, use js to send files, and can't do it through form and 1 input element. I could however use multiple inputs and have user select one file per input but that would suck – Muhammad Umer Feb 05 '18 at 03:40
  • So... pretty much what the duplicate says then. – Heretic Monkey Feb 05 '18 at 04:16
  • @MikeMcCaughan hum yes indeed almost the same... The only slight difference is that in the dupe target OP knew about FD and specifically asked how to do it with this API. But I agree, it's very similar, and even probably enough to close it. I am sorry I didn't saw your comment before leaving this answer, which was greatly motivated by the awful other answer... Now I feel a little bad about closing it with my mjolnir though, and can't remove my answer either... Maybe I should CW this answer and use mjolnir anyway. – Kaiido Feb 05 '18 at 04:52
-1

If you are just going to upload images. Consider using base64 encoding. That is what I did. Assign base64 encoded URL to stringify JSON, or whatever you wish that is easier for you.

When the data is POST-ed, do the conversion there, to get back your files for saving them. Decoding base64 will be dependent on your backend language though of which you did not mention in your question.

In JavaScript, you can use this function to load up the image in preview state like you mentioned.

 function previewImage (inputId, eleId) {
        if (window.FileReader) {
                var oPreviewImg = null, oFReader = new window.FileReader(),
                        rFilter = /^(?:image\/bmp|image\/cis\-cod|image\/gif|image\/ief|image\/jpeg|image\/jpeg|image\/jpeg|image\/pipeg|image\/png|image\/svg\+xml|image\/tiff|image\/x\-cmu\-raster|image\/x\-cmx|image\/x\-icon|image\/x\-portable\-anymap|image\/x\-portable\-bitmap|image\/x\-portable\-graymap|image\/x\-portable\-pixmap|image\/x\-rgb|image\/x\-xbitmap|image\/x\-xpixmap|image\/x\-xwindowdump)$/i;

                oFReader.onload = function (oFREvent) {
                    if (!oPreviewImg) {
                        var newPreview = document.getElementById(eleId);
                        oPreviewImg = new Image();
                        oPreviewImg.style.width = (newPreview.offsetWidth).toString() + "px";
                        oPreviewImg.style.height = (newPreview.offsetHeight).toString() + "px";
                        if(newPreview.children.length > 0)
                            newPreview.replaceChild(oPreviewImg, newPreview.children[0]);
                        else
                            newPreview.appendChild(oPreviewImg);
                    }
                    oPreviewImg.src = oFREvent.target.result;
                    // Add Bootstrap's img-thumbnail class to the image frame
                    oPreviewImg.classList.add("img-thumbnail");
                };

                return function () {
                    var aFiles = document.getElementById(inputId).files;
                    if (aFiles.length === 0) { return; }
                    if (!rFilter.test(aFiles[0].type)) { alert("You must select a valid image file!"); return; }
                    oFReader.readAsDataURL(aFiles[0]);
                }


        }
        if (navigator.appName === "Microsoft Internet Explorer") {
            return function () {
                document.getElementById(eleId).filters.item("DXImageTransform.Microsoft.AlphaImageLoader").src = document.getElementById(inputId).value;
            }
        }


    };

Then you can extract the base64 URL from the src attribute of the img element.

You may come out with your own preferred method of "cancelling" your upload by maybe destroying the img element.

Desmond
  • 149
  • 1
  • 2
  • 11