0

My app can create previews of selected images, put them into a table cell and let me fill necessary information of each image (name, tags, source) in other cells of the same row before uploading. But there is a problem: I use FileReader and his function readAsDataURL which is asynchronous. This means that I recieve the previews in wrong order and after uploading they don't match with other information about photos. Here you can see the result on the screenshot.

I found some ideas about this issue, but I don't know how to implement them in my case, when I put every preview in separeted table cell and create those cells dynamically.

EDIT: the order of file uploading to the server is correct. Incorrect is only the order of previews I recieve before such uploading. I mean, if I have files 1, 2, 3 on my disc, I can recieve the previews in order 3, 1, 2. But to the server the files will be uploaded as 1, 2, 3.

Here is my function:

var newElem = document.createElement('table');
newElem.id = 'tl';
newElem.align = 'center';
newElem.border = 0;

    for (var i = 0; i < countFiles; i++) {
        var reader = new FileReader();


        reader.onload = function (e) {

    //create cells for each field

            var newRow = newElem.insertRow(0);
            var newCell1 = newRow.insertCell(0);
            newCell1.innerHTML = "<input type='text' class='form-control' " +
                "placeholder='Source' name='source' style='margin: 15px'>";
            var newCell2 = newRow.insertCell(0);
            newCell2.innerHTML = "<input type='text' class='form-control' " +
                "placeholder='Tags' name='tags' style='margin: 10px'>";
            var newCell3 = newRow.insertCell(0);
                newCell3.innerHTML = "<input type='text' class='form-control' " +
                "placeholder='Name' name='name' style='margin-left: 5px'>";
            var newCell4 = newRow.insertCell(0);
    //append the preview
            $("<img />", {
                "src": e.target.result,
                "class": "thumb-image"
            }).appendTo(newCell4);

        };

        document.getElementById("image-holder").appendChild(newElem);
        reader.readAsDataURL($(this)[0].files[i]);
        image_holder.show();
    }
Community
  • 1
  • 1
Oleg Shankovskyi
  • 131
  • 1
  • 13

2 Answers2

0

You can pass i to an IIFE. Not certain what this is expected to be at $(this)[0].files[i], though the element can also be passed as a parameter to the IIFE

for (var i = 0; i < countFiles; i++) {
  (function readFiles(n) { // `i` : `n`
    var reader = new FileReader();
    reader.onload = function(e) {
      //create cells for each field
      var newRow = newElem.insertRow(0);
      var newCell1 = newRow.insertCell(0);
      newCell1.innerHTML = "<input type='text' class='form-control' " +
        "placeholder='Source' name='source' style='margin: 15px'>";
      var newCell2 = newRow.insertCell(0);
      newCell2.innerHTML = "<input type='text' class='form-control' " +
        "placeholder='Tags' name='tags' style='margin: 10px'>";
      var newCell3 = newRow.insertCell(0);
      newCell3.innerHTML = "<input type='text' class='form-control' " +
        "placeholder='Name' name='name' style='margin-left: 5px'>";
      var newCell4 = newRow.insertCell(0);
      //append the preview
      $("<img />", {
        "src": e.target.result,
        "class": "thumb-image"
      }).appendTo(newCell4);

    };

    document.getElementById("image-holder").appendChild(newElem);
    reader.readAsDataURL($(this)[0].files[n]);
    image_holder.show();
  }(i))
}
guest271314
  • 1
  • 15
  • 104
  • 177
  • thank you for your answer, but it doesn't work this way – Oleg Shankovskyi May 30 '16 at 19:44
  • _"but it doesn't work this way"_ Can you include `html` at Question?, create a stacksnippets or jsfiddle http://jsfiddle.net to demonstrate? `this` probably not `event.target` of `change` event within `for` loop – guest271314 May 30 '16 at 19:45
  • @OlegShankovskyi You did not include jQuery at jsfiddle; you can do so by clicking "Javascript" and selecting library to load to at `document`. Also, ` – guest271314 May 30 '16 at 20:09
  • @OlegShankovskyi Does `javascript` at https://jsfiddle.net/skqt30v1/1/ return expected results? – guest271314 May 30 '16 at 20:20
  • thank you for your trying to help me. I tried your code and it works but just like mine does — the same problem stays. Maybe I explained the problem not very clear, I've added a bit more information to my post: the order of file uploading to the server is correct. Incorrect is only the order of previews I recieve before such uploading. I mean, if I have files 1, 2, 3 on my disc, I can recieve the previews in order 3, 1, 2. But to the server the files will be uploaded as 1, 2, 3. – Oleg Shankovskyi May 30 '16 at 20:27
  • the similar problem here http://stackoverflow.com/questions/19778843/order-issue-with-append-in-a-file-reader was solved. I tried to use that ideas, but I don't know how to implement them in my case with separeted cells. – Oleg Shankovskyi May 30 '16 at 20:30
  • @OlegShankovskyi _"the same problem stays. Maybe I explained the problem not very clear, I've added a bit more information to my post: the order of file uploading to the server is correct. Incorrect is only the order of previews I recieve before such uploading."_ ? Not certain what requirement is? What do you mean by the "the order of previews I recieve before such uploading"? Are you trying to order previews in the order selected at `Choose files` dialog? – guest271314 May 30 '16 at 20:33
  • I recieve the previews and describe them: http://prntscr.com/backfs But after uploading I become this: http://i.stack.imgur.com/O2aqL.png That is the problem. – Oleg Shankovskyi May 30 '16 at 20:38
  • @OlegShankovskyi What do you mean by "after uploading"? Do you have another event which changes `html` of `#image-holder` element? The `html` of `#image-holder` should be emptied at each `change` event when `image_holder.empty();` is called. Can you reproduce what you are describing? – guest271314 May 30 '16 at 20:46
  • this js-code is a part of big Java Spring MVC project. "After uploading" means that after recieving and describing the previews I click on Upload button and pass all this files with the descriptions to my UploadController (Spring) which uploads them to my Amazon S3 server. These previews aren't actual files, they are only facades and the problem is they don't match with the files. – Oleg Shankovskyi May 30 '16 at 20:55
  • @OlegShankovskyi _""After uploading" means that after recieving and describing the previews I click on Upload button and pass all this files with the descriptions to my UploadController (Spring) which uploads them to my Amazon S3 server."_ How is this related to actual Question? _"These previews aren't actual files"_ The previews are files. You can use the `src` of each `img` as a standalone file – guest271314 May 30 '16 at 20:57
  • `image_holder.empty();` is called only ones before choosing all the bunch of files, not before adding every preview to the table cell. – Oleg Shankovskyi May 30 '16 at 21:01
  • @OlegShankovskyi _"image_holder.empty(); is called only ones before choosing all the bunch of files, not before adding every preview to the table cell."_ The effective result is the same. The `html` within `#image-holder` should be removed before `for` loop begins. Can you reproduce names of files not matching file chosen at https://jsfiddle.net/skqt30v1/1/? – guest271314 May 30 '16 at 21:12
  • Note, you can also call `$("#image-holder").empty()` at `form` `submit` event – guest271314 May 30 '16 at 21:18
  • the file names do match the selected previews. The problem is that after that all files will be added to reader.readAsDataURL and that functions is asynchronous and adds the files into dataURL in other order. – Oleg Shankovskyi May 30 '16 at 21:47
  • @OlegShankovskyi _"the file names do match the selected previews."_ ? Where? At https://jsfiddle.net/skqt30v1/1/ ? What do you mean by _"after that all files will be added to reader.readAsDataURL"_ ? After what? The files match file names when tried at https://jsfiddle.net/skqt30v1/1/, when selecting multiple files multiple separate occasions. – guest271314 May 30 '16 at 21:51
  • @OlegShankovskyi Are you performing a separate process, not included at Question? You can also create a `javascript` object which includes both `File.name` and `data URI` representation of `File` object, with additional properties, values describing file if necessary; which appears to be what requirement is? See http://stackoverflow.com/questions/28856729/upload-multiple-image-using-ajax-php-and-jquery/ – guest271314 May 30 '16 at 21:56
  • thank you for your trying to help me. I found the solution, look at my answer with the working code. – Oleg Shankovskyi May 31 '16 at 22:18
0

I found the solution. For solving such problems with asynchronous code good works Promise.

Here is the whole working code:

$(document).ready(function () {

    $("#fileUpload").on('change', function () {

        //get count of selected files
        var countFiles = $(this)[0].files.length;
        var imgPath = $(this)[0].value;
        var extn = imgPath.substring(imgPath.lastIndexOf('.') + 1).toLowerCase();
        var image_holder = $("#image-holder");
        image_holder.empty();
        if (extn == "gif" || extn == "png" || extn == "jpg" || extn == "jpeg") {
            if (typeof(FileReader) != "undefined") {

                //create the table
                var newElem = document.createElement('table');
                newElem.id = 'tl';
                newElem.align = 'center';
                newElem.border = 0;


                //load all files using Promise
                function loadImage(image) {
                    return new Promise(function (resolve, reject) {
                        var fileReader = new FileReader();
                        fileReader.onload = function (e) {
                            resolve(e.target.result);
                        };
                        fileReader.readAsDataURL(image);
                    });
                }

                //put loaded files into the queue
                var queue = Promise.resolve();

                [].reduceRight.call(this.files, function (queue, file, index) {
                    return queue.then(function () {
                        return loadImage(file).then(function (imageAsDataUrl) {

                            //attach rows with cells to the table
                            var newRow = newElem.insertRow(0);
                            var newCell1 = newRow.insertCell(0);
                            newCell1.innerHTML = "<input type='text' class='form-control' " +
                                "placeholder='Source' name='source' style='margin: 15px'>";
                            var newCell2 = newRow.insertCell(0);
                            newCell2.innerHTML = "<input type='text' class='form-control' " +
                                "placeholder='Tags' name='tags' style='margin: 10px'>";
                            var newCell3 = newRow.insertCell(0);
                            newCell3.innerHTML = "<input type='text' class='form-control' " +
                                "placeholder='Name' name='name' style='margin-left: 5px'>";
                            var newCell4 = newRow.insertCell(0);

                            $("<img />", {
                                "src": imageAsDataUrl,
                                "class": "thumb-image"
                            }).appendTo(newCell4);
                        });
                    });
                }, Promise.resolve()).then(function () {

                    //everything is ready, we can attach the table to imageholder and show it
                    document.getElementById("image-holder").appendChild(newElem);
                    image_holder.show();
                });

            } else {
                alert("This browser does not support FileReader.");
            }
        } else {
            alert("Please select images only");
        }
    });
});
Oleg Shankovskyi
  • 131
  • 1
  • 13