Search code examples
javascriptjquerydropzone.jsdropzone

Only one Dropzone working when multiple dinamically initialized


I'm writing a web application where I need to initialize multiple dropzones basing on server content, I currently have a code similar to this:

<script src="/js/dropzone.min.js"></script>
<script>
Dropzone.autoDiscover = false;
window.dropzones = {};


function checkDropzones(container) {
    var possibleDropzones = container.querySelectorAll('.needs-to-be-dropzoned');
    possibleDropzones.forEach(function (zone) {
        if (zone.id.length === 0) {
            zone.id = 'dropzone_filled_' + (new Date()).getTime();
            window.dropzones[zone.id] = new Dropzone(
                '#' + zone.id,
                {
                    paramName: 'image',
                    addRemoveLinks: true,
                }
            );
        }
    })
}

function renderServerContent() {
    window.customSections.forEach(function (custom_section) {
        var container = document.getElementById(custom_section);
        $.ajax({
            method: 'GET',
            url: '/customRenderUrl?section=' + custom_section,
            headers: {
                Accept: 'application/json',
                'Content-Type': 'application/json'
            },
            success: function (response) {
                container.innerHTML = container.innerHTML + response.component;
                if (response.component_type === 'image_uploader') {
                    checkDropzones(container);
                }
                // ... other marginal stuff ... 
            },
            dataType: 'json'
        })
    })
}

// ... other scripts ... 

window.customSections = [/* server stuff */];
renderServerContent();
</script>

Basically, I have some stuff to dynamically render from the server, so I send a request asking for component rendered data and when I get the answer-back, I check if the inserted content contains an element with .needs-to-be-dropzoned, if so, I assign it a time-based ID (component rendering has a delay between each component so the IDs are unique) and instance the dropzone. This works well with just one dropzone, but when multiple are on the same page it won't generate errors but will work for just the last element on the page.

The server content is something like this:

<form
    class="col-12 container-fluid pb-2 dropzone needs-to-be-dropzoned"
    method="POST"
    action="/imageUploadUrl"
>
    <input type="hidden" name="_token" value="..." >

    <div class="dz-default dz-message">
        Upload files
    </div>
    <div class="fallback">
        <input name=image" type="file" />
    </div>
</form>

If I launch a console.dir(window.dropzones) I get 2 objects:

Object { dropzone_filled_1624370363574: {…}, dropzone_filled_1624370363803: {…} }
​
dropzone_filled_1624370363574: Object { element: form#dropzone_filled_1624370363574.col-12.container-fluid.pb-2.dropzone.needs-to-be-dropzoned.dz-clickable, version: "5.9.2", clickableElements: (1) […], … }
​
dropzone_filled_1624370363803: Object { element: form#dropzone_filled_1624370363803.col-12.container-fluid.pb-2.dropzone.needs-to-be-dropzoned.dz-clickable, version: "5.9.2", clickableElements: (1) […], … }

What am I doing wrong or missing?


Solution

  • I did various attempts to fix this, and finally, I fixed it by wrapping all server calls into promises, waited for all promises to solve and only after that, I checked for dropzones. The code is approximately this:

    <script src="/js/dropzone.min.js"></script>
    <script>
    Dropzone.autoDiscover = false;
    window.dropzones = {};
    window.containersToCheck = [];
    
    
    function checkDropzones(container) {
        var possibleDropzones = container.querySelectorAll('.needs-to-be-dropzoned');
        possibleDropzones.forEach(function (zone) {
            if (zone.id.length === 0) {
                zone.id = 'dropzone_filled_' + (new Date()).getTime();
                window.dropzones[zone.id] = new Dropzone(
                    '#' + zone.id,
                    {
                        paramName: 'image',
                        addRemoveLinks: true,
                    }
                );
            }
        })
    }
    
    function renderServerContent() {
        return new Promise(function(resolve, reject) {
            window.customSections.forEach(function (custom_section) {
                var container = document.getElementById(custom_section);
                $.ajax({
                    method: 'GET',
                    url: '/customRenderUrl?section=' + custom_section,
                    headers: {
                        Accept: 'application/json',
                        'Content-Type': 'application/json'
                    },
                    success: function (response) {
                        container.innerHTML = container.innerHTML + response.component;
                        if (response.component_type === 'image_uploader') {
                            window.containersToCheck.push(container);
                        }
                        resolve();
                        // ... other marginal stuff ... 
                    },
                    dataType: 'json'
                })
            })
        });
    }
    
    // ... other scripts ... 
    
    window.customSections = [/* server stuff */];
    var promises = [];
    promises.push(renderServerContent());
    // other renderings
    Promise.all(promises).then(function (results) {
        window.containersToCheck.forEach(function(container) {
            checkDropzones(container);
        });
    })
    </script>
    

    This way, all Dropzones work.