Dropzone: Dropzone.js drag-and-drop re-ordering of queue

Created on 25 Jun 2014  路  26Comments  路  Source: dropzone/dropzone

I'm using dropzone with autoProcessQueue: false to give the user a chance to remove files/etc.

Ideally, what I would like to allow is drag-n-drop reordering of the queue...so that if a user selects 10 images and drags them into the dropzone, they can then be ordered with the mouse.

Any ideas?

Most helpful comment

If anyone else stumbles onto here and is wondering how to do this, here is how I did it.

Every time a file is added store the file data onto the element:

dropzone_ins.on('addedfile', function(file) {
    $('.sortable_img li:last-of-type').data('file', file);
});

Then before I called the processQueue() function I cleared the queued files and then looped through them then re-added them in the custom order:

var current_queue = [];
$('.sortable_img > li').each( function(){
    current_queue.push($(this).data('file'));
});

dropzone_ins.removeAllFiles();

for(i=0;i<current_queue.length;i++){
    dropzone_ins.addFile(current_queue[i]);
}

dropzone_ins.processQueue();

All 26 comments

With jQuery UI sortable, you can try:

$("#myDropzone").sortable({
    items:'.dz-preview',
    cursor: 'move',
    opacity: 0.5,
    containment: "parent",
    distance: 20,
    tolerance: 'pointer',
    update: function(e, ui){
        // do what you want
    }
});

Hi, I have been able to reorder pictures with sortable, but when I click to upload all pictures, it uploads with same order as pictures are inserted. It does not remember new order.

Hi, i have working pretty well Dropzone but i cannot make work sortable option. Could you be more specific whit some example?

I already upload the images, rename it, redimension it, make 100x100px cropped thumb (for mockfile) and save record to mysql database.

@gusta21 I also facing the same issue. is any example related to sortable UI with Dropzone

When you drop files on dropzone, internally it creates a file structure (that holds thumbnail, name, size, etc) and stores it in a queue of files to be processed.

Reordering items with jqueryUI or other similar solution simpy reorders the HTML elements but not the dropzone queue of files. That task is yours.

Once you know when the sortable library has made a modification a possible way to reorder the files within the queue is:

var queueArray = dropzone.getQueuedFiles();
// Reorder files within the array
dropzone.removeAllFiles();
// Loop and add your files again
dropzone.addFile(file);

I solved the issue by assigning a unique auto incremented file id in a html5 dataset attribute (i.e. data-file-id) for each preview element when adding the file. Then in the stop method for jquery sortable, I reordered the dropzone files list directly which has the benefit of bypassing emit hooks (faster). So I don't think there's anything for the dropzone developers to do since it can all be done without modifying the dropzone library.

Could you please post your code so we could use id?

If anyone else stumbles onto here and is wondering how to do this, here is how I did it.

Every time a file is added store the file data onto the element:

dropzone_ins.on('addedfile', function(file) {
    $('.sortable_img li:last-of-type').data('file', file);
});

Then before I called the processQueue() function I cleared the queued files and then looped through them then re-added them in the custom order:

var current_queue = [];
$('.sortable_img > li').each( function(){
    current_queue.push($(this).data('file'));
});

dropzone_ins.removeAllFiles();

for(i=0;i<current_queue.length;i++){
    dropzone_ins.addFile(current_queue[i]);
}

dropzone_ins.processQueue();

Thank you @MrHunter, it works for me

@MrHunter could you please explain how you got .sortable_img li:last-of-type? Did you create a new list or is this an existing node? I can't find this!

@tiaanpat The list of queued files has the class "sortable_img" in my case, your situation is most likely different and will require a different selector.

@MrHunter When trying to implement your solution the stored file overwrites all other stored files before it. been pulling my hair out for couple of days now. help would be much appreciated. this is all very new to me, I am sure i am missing something obvious.

previewTemplate: "

\n
\n
\n
\n
\n
\n
\n
\n
\n
\n .....

this.on("addedfile", function (file) {
$('.cge-image-preview').data('file', file);
});

stop: function () {
console.log("Updating Queue");
newQueue = [];
$('.cge-image-preview').each(function () {
console.log($(this).data('file'))
//newQueue.push($(this).data('file'));
});
console.log(newQueue);

                        disableRemoveFile = true;
                        console.log("disable remove file")

                        productPhotoUploader.removeAllFiles();
                        console.log("que files deleted")

                        for (var i = 0; i < newQueue.length; i++) {
                            console.log("new que file added");
                            productPhotoUploader.addFile(newQueue[i]);
                            console.log(newQueue[i] + "Added")
                        };

New queue will print out same object(file) for as many times as there are files in queue.

Cheers
Cory

@HudsonCory Each time a new file is added you are writing the file information to each element with the class "cge-image-preview" using this code:

this.on("addedfile", function (file) {
    $('.cge-image-preview').data('file', file);
});

In my example I only add the file information to the last element which would look like this in your code:

this.on("addedfile", function (file) {
    $('.cge-image-preview:last-of-type').data('file', file);
});

Amazing! Thanks for the super fast response.

Cheers!

@MrHunter your solution is great !! It works perfect but what about already uploaded images ? I have mockfiles for previev. When i change mock files order or add some new images, Dropzone processQueue() method will only fetch recently added images. Any ideas ?

A simpler way to handle sorting would be:

// Get the queued files
var files = dropzone.getQueuedFiles();
// Sort theme based on the DOM element index
files.sort(function(a, b){
    return ($(a.previewElement).index() > $(b.previewElement).index()) ? 1 : -1;
})
// Clear the dropzone queue
dropzone.removeAllFiles();
// Add the reordered files to the queue
dropzone.handleFiles(files);
dropzone.processQueue();

How is everyone handling sort interactions with files that already exist? I'm running into a situation where:

  1. Go to page, upload/reorganize files. Save.
  2. Go to page later, existing files display as expected.
  3. Do nothing, existing files work just fine.
    a. Reorganize files - existing files break.
    b. Add another file - existing files break.

I assume the mock files that I've created for display on page load are missing something and/or are not correct in some fashion, but I'm not really sure how.

I started with a mock file like this: {'name': 'filename.jpg', size: 123456}
and then calling the addedFile, thumbnail, and complete options.

let mockFile = {'name': 'filename.jpg', size: 123456};
dropzone.emit( 'addedfile', mockFile );
dropzone.emit( 'thumbnail', mockFile, 'http://urltofilename.com/filename.jpg' );
dropzone.emit( 'complete', mockFile );

I was having some thumbnail creation issues (such and such function does not exist) so I moved to getting the base64 content for the image and creating a new File object. This resolved the issues I was experiencing with the simple object but still had the requeue failing issues:

let mockFile = new File(['base64 content'], 'filename.jpg', {type: 'image/jpeg'} );
mockFile.name = 'filename.jpg';
mockFile.accepted = true;
mockFile.size = 123456;
mockFile.status = Dropzone.SUCCESS;

dropzone.emit('addedfile', mockFile);
dropzone.emit('thumbnail', mockFile, 'http://urltofilename.com/filename.jpg');
dropzone.emit('complete', mockFile);

dropzone.files.push(mockFile);

Now I'm on to trying to download the file via XMLHttpRequest and creating a File object from that:

let xhr = new XMLHttpRequest();

xhr.onreadystatechange = function() {
    if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
        let mockFile = new File([xhr.response], 'filename.jpg', {type: 'image/jpeg'});
        mockFile.accepted = true;
        mockFile.processing = true;
        mockFile.size = 123456;
        mockFile.status = Dropzone.SUCCESS;

        dropzone.emit('addedfile', mockFile);
        dropzone.emit('thumbnail', mockFile, 'http://urltofilename.com/filename.jpg');
        dropzone.emit('complete', mockFile);

        dropzone.files.push(mockFile);
    }
};

xhr.open('GET', 'http://urltofilename.com/filename.jpg', true);
xhr.setRequestHeader('Cache-Control', 'no-cache');
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.send();

But in every instance, when the existing files are requeued either because new files are added or because they've been reordered, the upload of the existing files fails in some fashion and corrupts the file on the server.

I know I'm probably missing something simple, but I'm not sure what.

The only error I actually see in the console is a URL issue, where part of the url is being replaced with [object Event], causing a 404 issue.

@thomascharbit

Your code for sorting works fine while uploading the files. Do you have any idea how to sort the files after they get uploaded to the server and saved to the DB? Thanks.

@acrolink with my snippet files should be in correct order when you handle them on the backend?

// Get the queued files
var files = dropzone.getQueuedFiles();

this handles the files that are on their way to be uploaded. I am working on finding a way to sort the already and previously uploaded files.

@acrolink this is a different beast 馃槈

If you followed this: https://github.com/enyo/dropzone/wiki/FAQ#how-to-show-files-already-stored-on-server
You can have existing files in the dropzone and reorder them.
When doing so, ou can add a property to mockFiles (say mock: true) so you know these are mocks and not real upload.
Then when new files are uploaded i would add an hidden input containing the order info, for both existing and new files.
This input is submitted by dropzone along with the new files.
I also use filename as id to know what file is at which position on the backend.
This way on the backend you can deduce files need to be deleted, new files to be saved, and set the new order for the updated array of files.

This is my code when submitting form, hope that helps!

function handleSubmit(e) {
    e.preventDefault();
    e.stopPropagation();

    var dropzoneOrder = dropzone.files
                            // Get dropzone files ordered
                            .sort(function(a, b){
                                return ($(a.previewElement).index() > $(b.previewElement).index()) ? 1 : -1;
                            })
                            // Build object to handle order on backend
                            .map(function(file){
                                return {
                                    id: file.upload.filename,
                                    mock: file.hasOwnProperty('mock')
                                };
                            });
    // Save it as hidden field
    $('input[name="dropzone_order"]').val(JSON.stringify(dropzoneOrder));
    // Let dropzone handle the form submission if we have files to upload
    var queuedfiles = dropzone.getQueuedFiles();
    if (queuedfiles.length > 0) {
        dropzone.processQueue();
    }
}

This can be improved though..

@thomascharbit

I have tried your suggestion but this fires only if there are new files added, not if attempting to sort existing images. Even so, the images are not stored in the desired order. I see that you have never used the value of $('input[name="dropzone_order"]').val(), maybe something is missing in the code as it is outlined above.

Meanwhile, I have managed to sort currently uploaded images (condition: no images in queue) using custom POST call that modifies (raw SQL) the images string column in DB using the order of the images' previews in DOM.

I hope to arrive at a solution that works for mixed mode (already saved images and new ones in queue).

I've mashed up a bunch of code to come up with the following.

I had a few hurdles:

  1. I wanted the load files already uploaded on load.
  2. I wanted files, when uploaded, to reflect the name and size on server in the gui as I was renaming and resizing. This can only be achieved through removing the processed file after upload, and re-adding it with the proper attributes, but at the same time it had to reflect the proper gui position given by the user order. So it had to not only be removed then added, but also repositioned.

Here is the dz code (partial Codeigniter code in the drop).

Dropzone.autoDiscover = false;
    $(function () {
        /**
         * Only for duplicates (easiest way it seems)
         * @type Array
         */
        var current_queue = [];

        var dz_sort = new Dropzone('#dropzone', {
            url: '/neou_cms/project_image_handler/upload',
            autoProcessQueue: false,
            maxFiles: <?php echo $num_image_fields; ?>,
            acceptedFiles: 'image/*',
            addRemoveLinks: true,
            parallelUploads: 1,
            maxFilesize: <?php echo intval($max_img_file_size); ?>,
            init: function () {
                var submitButton = document.querySelector('#process');
                submitButton.addEventListener("click", function () {
                    if (dz_sort.getQueuedFiles().length < 1) {
                        disable_box();
                        sort(null, true);
                    } else {
                        disable_box();
                        dz_sort.processQueue();
                    }
                });
                $.getJSON('/neou_cms/projects/images_ajax/<?php echo $id; ?>', function (data) {
                    if (typeof data.status !== 'undefined') {
                        neou_cms.display_error_message($('#info'), data.msg);
                    } else {
                        $.each(data, function (key, value) {
                            add_to_dz(value.opts);
                        });
                    }
                });
            }
        });

        var add_to_dz = function (resp) {
            var file = {
                name: resp.name,
                size: resp.size,
                status: Dropzone.ADDED,
                accepted: true,
                order: resp.order
            };
            dz_sort.emit('addedfile', file, true);
            dz_sort.emit('thumbnail', file, resp.thumb);
            dz_sort.emit('complete', file, true);
            dz_sort.files.push(file);
        };

        var sort = function (curr_file_name = null, just_sort = false) {
            var sorting_queue = {};
            $.each(dz_sort.files, function (index, file) {
                if (curr_file_name !== null && file.name == curr_file_name) {
                    return true; // skip to next
                }
                sorting_queue[file.name] = file.order;
            });
            $.ajax({
                url: '/neou_cms/project_image_handler/sort',
                type: 'POST',
                dataType: 'json',
                data: {
                    id: <?php echo $id; ?>,
                    order: JSON.stringify(sorting_queue)
                },
                success: function (data) {
                    if (data.status === 'error') {
                        console.log('DZ: Error occured when sorting');
                        neou_cms.display_error_message($('#info'), data.msg);
                    } else if (just_sort) {
                        neou_cms.display_success_message($('#info'), data.msg);
                    }
                }
            });
            if (dz_sort.getUploadingFiles().length == 0) {
                enable_box();
            }
        };

        var move_last_to_pos = function (order) {
            // move newly added image to proper position
            var selector = $('#dropzone .dz-preview');
            var new_image = selector.last();
            var total = selector.length;
            // each starts at 0, count starts at 1, add 1 to count
            selector.each(function (count, el) {
                if (count + 1 === order) {
                    // if element isn't the same as the new image
                    // if element isn't the last
                    if (el !== new_image && order !== total) {
                        jQuery(new_image).detach().insertBefore(el);
                    }
                    return false; // break
                }
            });
        }

        dz_sort.on('maxfilesexceeded', function (file) {
            bootbox.alert('You can only upload <?php echo $num_image_fields; ?> files!');
            dz_sort.removeFile(file);
        });

        dz_sort.on('addedfile', function (file, start) {
            if ($.inArray(file.name, current_queue) !== -1) {
                errors.html('A file with this name already exists in the queue.');
                dz_sort.removeFile(file);
            } else {
                // order is already added for existing images onload
                if (!start) {
                    // add order as last by default
                    file.order = dz_sort.files.length;
                }
                current_queue.push(file.name);
            }
        });

        dz_sort.on('removedfile', function (file) {
            current_queue.splice($.inArray(file.name, current_queue), 1);
            // ajax call to delete file...
            $.ajax({
                url: '/neou_cms/project_image_handler/delete',
                type: 'POST',
                dataType: 'json',
                data: {
                    id: <?php echo $id; ?>,
                    order: file.order,
                    filename: file.name
                },
                success: function (data) {
                    if (data.status === 'error') {
                        console.log('DZ: Error occured deleting image file ' + file.name);
                        neou_cms.display_error_message($('#info'), data.msg);
                        //add_to_dz(file); // add file back
                        //move_last_to_pos(file.order); // reposition file
                    }
                }
            });
        });

        dz_sort.on('error', function (error) {
            console.log('on error triggered with error:');
            console.log(error);
        });

        dz_sort.on('sending', function (file, xhr, formData) {
            formData.append('id', <?php echo $id; ?>);
            formData.append('order', file.order);
            formData.append('<?php echo $this->security->get_csrf_token_name(); ?>', '<?php echo $this->security->get_csrf_hash(); ?>');
        });

        dz_sort.on('complete', function (file, start) {
            if (file.accepted === false) {
                neou_cms.display_error_message($('#info'), 'File not accepted.');
                dz_sort.removeFile(file);
                return false;
            }
            if (!start) {
                var resp = JSON.parse(file.xhr.response);
                if (resp.status === 'error') {
                    // an upload error occured
                    console.log('DZ: Error occured uploading ' + file.name);
                    neou_cms.display_error_message($('#info'), 'Upload ' + file.name + ' failed with error: ' + resp.msg);
                    dz_sort.removeFile(file);
                    // we still want to sort if changed order
                    sort();
                } else {
                    // remove file so we can all the new attributes
                    dz_sort.removeFile(file);
                    // set resp order as local order
                    resp.msg.order = file.order;
                    // add new file with up-to-date attributes
                    add_to_dz(resp.msg);
                    // move
                    move_last_to_pos(file.order);
                    // resort other files
                    sort(resp.msg.name);
                    // continue processing the queue...
                    dz_sort.processQueue();
                }
            }
        });

        var enable_box = function () {
            $('.dropzone').sortable('enable');
            neou_cms.remove_loading_button($('#process'));
        };

        var disable_box = function () {
            $('.dropzone').sortable('disable');
            neou_cms.loading_button($('#process'), 'Working...');
        };

        $('.dropzone').sortable({
            items: '.dz-preview',
            cursor: 'move',
            opacity: 0.5,
            containment: '.dropzone',
            distance: 20,
            tolerance: 'pointer',
            stop: function () {
                var queue = dz_sort.files;
                var new_queue = [];
                $('#dropzone .dz-preview .dz-filename [data-dz-name]').each(function (count, el) {
                    var name = el.innerHTML;
                    queue.forEach(function (file) {
                        if (file.name === name) {
                            file.order = count + 1;
                            new_queue.push(file);
                        }
                    });
                });
                dz_sort.files = new_queue;
            }
        });
    });
[dz_ci.zip](https://github.com/enyo/dropzone/files/1505415/dz_ci.zip)

@DNACode-ca instead of using
$('.sortable_img li:last-of-type').data('file', file);
i used
$('.sortable_img li').last().data('file', file);
last-of-type was misbehaving on microsoft edge

After having put a lot of hours into this I finally have a solution to make jquery sortable work with dropzone.js. I'll put the script of interest first and the full dropzone js script second. The commentary should explain what is happening.

    init: function() {
    // very important to make the sortable work
    var myDropzone = this;

    // In your drop zone you have your click handler event
    document.getElementById("submit").addEventListener("click", function(e) {
        // Make sure that the form isn't actually being sent.
        e.preventDefault();

        // the new array where we will put in the new files
        var current_queue = [];

        // the array we want to upgrade
        var oldArray = myDropzone.files;

        // on the webpage search for all the images that have been uploaded
        var imageTags = $('#myDropzone').find('div.dz-image img');

        // iterate through all the images that have been uploaded by the user
        imageTags.each(function( index, imageTag ) {
            // get the image name from the images
            imageName = imageTag.alt;

            // now we will iterate through the old array
            var i;
            for (i = 0; i < oldArray.length; i++) {
                /** if the name of the image on the website is the same as the image from the old array
                 * we will add it to the new array. You can see this as sorting the array.
                 */
                if(imageName === oldArray[i].name){
                    current_queue.push(oldArray[i]);
                }
            }
        });

        /** after everything is done we will update the old array with the
         *  new array so it knows that the files have been sorted.
         */
        myDropzone.files = current_queue;

        // dropzone will now submit the request
        e.stopPropagation();
        myDropzone.processQueue();
    });

if you are interested in the full dropzone js script:

$("#myDropzone").sortable({
    opacity: 0.7,
});

Dropzone.options.myDropzone = { 

// Configuration
url: '../somewhere',
method: 'post',
autoProcessQueue: false,
uploadMultiple: true,
parallelUploads: 100,
maxFiles: 100,
addRemoveLinks: true,
// The setting up of the dropzone
init: function() {
    // very important to make the sortable work
    var myDropzone = this;

    // In your drop zone you have your click handler event
    document.getElementById("submit").addEventListener("click", function(e) {
        // Make sure that the form isn't actually being sent.
        e.preventDefault();

        // the new array where we will put in the new files
        var current_queue = [];

        // the array we want to upgrade
        var oldArray = myDropzone.files;

        // on the webpage search for all the images that have been uploaded
        var imageTags = $('#myDropzone').find('div.dz-image img');

        // iterate through all the images that have been uploaded by the user
        imageTags.each(function( index, imageTag ) {
            // get the image name from the images
            imageName = imageTag.alt;

            // now we will iterate through the old array
            var i;
            for (i = 0; i < oldArray.length; i++) {
                /** if the name of the image on the website is the same as the image from the old array
                 * we will add it to the new array. You can see this as sorting the array.
                 */
                if(imageName === oldArray[i].name){
                    current_queue.push(oldArray[i]);
                }
            }
        });

        /** after everything is done we will update the old array with the
         *  new array so it knows that the files have been sorted.
         */
        myDropzone.files = current_queue;

        // dropzone will now submit the request
        e.stopPropagation();
        myDropzone.processQueue();
    });
    this.on('completemultiple', function(file, json) {
    });
    // sendingmultiple event
    this.on("sendingmultiple", function(data, xhr, formData) {
        formData.append("name", jQuery("#name").val());
        formData.append("sample1", jQuery("#sample1").val());
    });
    this.on("successmultiple", function(files, response) {
        // redirecting user on success. No message atm.
        var url = document.location.origin + "/somewhere_to_redirect";
        window.location.replace(url);
    });
    this.on("errormultiple", function(files, response) {
        // Gets triggered when there was an error sending the files.
        // Maybe show form again, and notify user of error
    });

}
}

@Hunter-WebDev
I'm having a problem where to put your code there is no event of after all sorting is over

Was this page helpful?
0 / 5 - 0 ratings

Related issues

lox picture lox  路  63Comments

rogamoore picture rogamoore  路  19Comments

KateMort picture KateMort  路  22Comments

dhardtke picture dhardtke  路  15Comments

mPisano picture mPisano  路  18Comments