214

I currently have a HTML form which users fill in details of an advert they wish to post. I now want to be able to add a dropzone for uploading images of the item for sale.

I have found Dropzone.js which seems to do most of what I need. However, when looking into the documentation, it appears that you need to specify the class of the whole form as dropzone (as opposed to just the input element). This then means that my entire form becomes the dropzone.

Is it possible to use the dropzone in just part of my form, i.e. by only specifying the element as class "dropzone", rather than the whole form?

I could use separate forms, but I want the user to be able to submit it all with one button.

Alternatively, is there another library that can do this?

Many thanks

informatik01
  • 16,038
  • 10
  • 74
  • 104
Ben Thompson
  • 4,743
  • 7
  • 35
  • 52

13 Answers13

75

Here's another way to do it: add a div in your form with a classname dropzone, and implement dropzone programmatically.

HTML :

<div id="dZUpload" class="dropzone">
      <div class="dz-default dz-message"></div>
</div>

JQuery:

$(document).ready(function () {
    Dropzone.autoDiscover = false;
    $("#dZUpload").dropzone({
        url: "hn_SimpeFileUploader.ashx",
        addRemoveLinks: true,
        success: function (file, response) {
            var imgName = response;
            file.previewElement.classList.add("dz-success");
            console.log("Successfully uploaded :" + imgName);
        },
        error: function (file, response) {
            file.previewElement.classList.add("dz-error");
        }
    });
});

Note : Disabling autoDiscover, otherwise Dropzone will try to attach twice

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Satinder singh
  • 10,100
  • 16
  • 60
  • 102
  • 39
    With that, he can't use the default submit button, it doesn't respond to his question – clement Apr 09 '15 at 15:26
  • @clement: OP doesn't want to add 'dropzone' class in form tag, coz this make whole form as dropzone. So with my answer he can add separate div inside form tag or outside form like as popup modal and by jQuery initialize dropzone. – Satinder singh May 27 '15 at 06:23
  • 1
    Its Asynchronous so file get save without submit button, On submit he can save uploaded files name into db – Satinder singh May 27 '15 at 06:25
  • 6
    but this still doesn't use the original form to submit – dangel May 30 '15 at 03:20
  • 3
    this was my issue and you resolved it, ty @Satindersingh – Su4p Sep 14 '15 at 15:35
  • 1
    @Su4p : am glad it helps you, also you can check blog article link for details explanation along with resize image option while uploading – Satinder singh Sep 14 '15 at 16:13
  • 1
    @dangel I thought that was what I wanted too, but I am very happy with the resulting solution. I suggest you just submit the uploads right away. – DigitalDesignDj Oct 05 '15 at 22:59
  • 2
    This helped a lot, you can setup any element as a dropzone if you setup the url manually. I used the success handler to post the filename to a hidden/disabled field in the primary form. – DigitalDesignDj Oct 05 '15 at 23:00
  • To the beginners using Dropzone.js. To select a file, you will have to click on the div itself which is in this answer `
    `
    – Ikhlak S. May 16 '16 at 07:40
  • There is already an option within dropzone.js to set the autoDiscover = false. Setting this option within the document load was too late in the page execution cycle for me. – Radderz Mar 01 '19 at 14:57
  • Works like charm! – ucMedia Jan 18 '21 at 10:03
  • I got exactly what I'am looking for, thank you @Satindersingh – Santosh Dangare Sep 21 '21 at 06:15
  • `Dropzone.autoDiscover = false;` was what I looking for – Huy Phạm Apr 17 '23 at 08:37
60

I had the exact same problem and found that Varan Sinayee's answer was the only one that actually solved the original question. That answer can be simplified though, so here's a simpler version.

The steps are:

  1. Create a normal form (don't forget the method and enctype args since this is not handled by dropzone anymore).

  2. Put a div inside with the class="dropzone" (that's how Dropzone attaches to it) and id="yourDropzoneName" (used to change the options).

  3. Set Dropzone's options, to set the url where the form and files will be posted, deactivate autoProcessQueue (so it only happens when user presses 'submit') and allow multiple uploads (if you need it).

  4. Set the init function to use Dropzone instead of the default behavior when the submit button is clicked.

  5. Still in the init function, use the "sendingmultiple" event handler to send the form data along wih the files.

Voilà ! You can now retrieve the data like you would with a normal form, in $_POST and $_FILES (in the example this would happen in upload.php)

HTML

<form action="upload.php" enctype="multipart/form-data" method="POST">
    <input type="text" id ="firstname" name ="firstname" />
    <input type="text" id ="lastname" name ="lastname" />
    <div class="dropzone" id="myDropzone"></div>
    <button type="submit" id="submit-all"> upload </button>
</form>

JS

Dropzone.options.myDropzone= {
    url: 'upload.php',
    autoProcessQueue: false,
    uploadMultiple: true,
    parallelUploads: 5,
    maxFiles: 5,
    maxFilesize: 1,
    acceptedFiles: 'image/*',
    addRemoveLinks: true,
    init: function() {
        dzClosure = this; // Makes sure that 'this' is understood inside the functions below.

        // for Dropzone to process the queue (instead of default form behavior):
        document.getElementById("submit-all").addEventListener("click", function(e) {
            // Make sure that the form isn't actually being sent.
            e.preventDefault();
            e.stopPropagation();
            dzClosure.processQueue();
        });

        //send all the form data along with the files:
        this.on("sendingmultiple", function(data, xhr, formData) {
            formData.append("firstname", jQuery("#firstname").val());
            formData.append("lastname", jQuery("#lastname").val());
        });
    }
}
DVHeld
  • 77
  • 1
  • 7
mrtnmgs
  • 1,402
  • 14
  • 26
  • 4
    This solution is nice and works, but it doesn't redirect to the next page anymore, because of preventing the default submit behavior. – Felix G. Apr 12 '16 at 11:32
  • @TIIUNDER - its prepared for sending form info via ajax call without page reload - thats why there is e.preventDefault(); – born2fr4g Jun 22 '16 at 11:14
  • 1
    @TIUNDER you can add redirect in success event – doflamingo Jan 06 '17 at 13:49
  • Is it possible to submit the form after `processQueue()` call? I try to use `submit()` or `click()`, both not work. – Grey Li Jun 29 '18 at 14:16
  • 5
    +1 This seems to be the only working solution. Instead of doing formData.append one by one, you can also do ```$(":input[name]", $("form")).each(function () { formData.append(this.name, $(':input[name=' + this.name + ']', $("form")).val()); });``` (sorry I don't know how to put linebreaks here) – Aximili Nov 27 '18 at 22:32
  • how can i append another input file for a single image? – Leoh Jan 14 '19 at 01:59
  • 1
    Not sure why, but I have to use $('#myDropzone').dropzone({...}) instead of Dropzone.options.myDropzone. Other than that, worked nicely. – Sem Aug 27 '19 at 02:08
  • Perfect! This is the closest i need! – Ingus Mar 26 '20 at 12:20
  • @doflamingo I found it better to redirect/show confirmation inside the completemultiple event handler. Doing so in the success handler made it execute multiple times (for each file attached), which sometimes even interrupted the form upload altogether. – Bodo Nov 15 '20 at 16:44
  • I get `Uncaught ReferenceError: dzClosure is not defined` when using this :( Does anyone know how I could fix that? – Hodorogea Alexandru Mar 11 '22 at 21:13
  • is this ajax request? – mending3 Apr 20 '22 at 18:06
24

The "dropzone.js" is the most common library for uploading images. If you want to have the "dropzone.js" as just part of your form, you should do the following steps:

1) for the client side:

HTML :

    <form action="/" enctype="multipart/form-data" method="POST">
        <input type="text" id ="Username" name ="Username" />
        <div class="dropzone" id="my-dropzone" name="mainFileUploader">
            <div class="fallback">
                <input name="file" type="file" multiple />
            </div>
        </div>
    </form>
    <div>
        <button type="submit" id="submit-all"> upload </button>
    </div>

JQuery:

    <script>
        Dropzone.options.myDropzone = {
            url: "/Account/Create",
            autoProcessQueue: false,
            uploadMultiple: true,
            parallelUploads: 100,
            maxFiles: 100,
            acceptedFiles: "image/*",

            init: function () {

                var submitButton = document.querySelector("#submit-all");
                var wrapperThis = this;

                submitButton.addEventListener("click", function () {
                    wrapperThis.processQueue();
                });

                this.on("addedfile", function (file) {

                    // Create the remove button
                    var removeButton = Dropzone.createElement("<button class='btn btn-lg dark'>Remove File</button>");

                    // Listen to the click event
                    removeButton.addEventListener("click", function (e) {
                        // Make sure the button click doesn't submit the form:
                        e.preventDefault();
                        e.stopPropagation();

                        // Remove the file preview.
                        wrapperThis.removeFile(file);
                        // If you want to the delete the file on the server as well,
                        // you can do the AJAX request here.
                    });

                    // Add the button to the file preview element.
                    file.previewElement.appendChild(removeButton);
                });

                this.on('sendingmultiple', function (data, xhr, formData) {
                    formData.append("Username", $("#Username").val());
                });
            }
        };
    </script>

2) for the server side:

ASP.Net MVC

    [HttpPost]
    public ActionResult Create()
    {
        var postedUsername = Request.Form["Username"].ToString();
        foreach (var imageFile in Request.Files)
        {

        }

        return Json(new { status = true, Message = "Account created." });
    }
Varan Sinayee
  • 1,105
  • 2
  • 12
  • 26
  • 2
    Thanks for the post! Solved my problem. Another quick question, this does not work when no images selected(to upload), how to solve this? – Sato Mar 02 '16 at 13:33
  • BTW: if you use controller action with model binding and submit your form like this the model will be empty. For some reason this method doesn't bind the actual data to the model. – Ed C Sep 03 '16 at 20:35
  • 1
    when autoProcessQueue= false no events are fired – cyril Sep 08 '18 at 10:26
  • @Sato on click event of submit button you can check the length of accepted files on dropzone by using galleryfile.getAcceptedFiles().length and if there is no file uploaded you should submit your form. – Varan Sinayee Sep 20 '18 at 00:28
  • @EdwardChopuryan This is not related to the method of submiting data by dropzone. Likely the problem is on your "naming convention" of input tags on your platform such as ASP.Net MVC. – Varan Sinayee Sep 20 '18 at 00:33
16

I have a more automated solution for this.

HTML:

<form role="form" enctype="multipart/form-data" action="{{ $url }}" method="{{ $method }}">
    {{ csrf_field() }}

    <!-- You can add extra form fields here -->

    <input hidden id="file" name="file"/>

    <!-- You can add extra form fields here -->

    <div class="dropzone dropzone-file-area" id="fileUpload">
        <div class="dz-default dz-message">
            <h3 class="sbold">Drop files here to upload</h3>
            <span>You can also click to open file browser</span>
        </div>
    </div>

    <!-- You can add extra form fields here -->

    <button type="submit">Submit</button>
</form>

JavaScript:

Dropzone.options.fileUpload = {
    url: 'blackHole.php',
    addRemoveLinks: true,
    accept: function(file) {
        let fileReader = new FileReader();

        fileReader.readAsDataURL(file);
        fileReader.onloadend = function() {

            let content = fileReader.result;
            $('#file').val(content);
            file.previewElement.classList.add("dz-success");
        }
        file.previewElement.classList.add("dz-complete");
    }
}

Laravel:

// Get file content
$file = base64_decode(request('file'));

No need to disable DropZone Discovery and the normal form submit will be able to send the file with any other form fields through standard form serialization.

This mechanism stores the file contents as base64 string in the hidden input field when it gets processed. You can decode it back to binary string in PHP through the standard base64_decode() method.

I don't know whether this method will get compromised with large files but it works with ~40MB files.

Umair Ahmed
  • 2,420
  • 1
  • 21
  • 40
  • How do you decode and process data from other fields that will be submitted together with the images? – sam Jan 30 '18 at 10:24
  • @sam There is no need to decode the other fields. They are not getting encoded in the first place, only the file gets encoded. – Umair Ahmed Jan 31 '18 at 11:33
  • Can you share some sample code for the html ,javascript and how to retrieve in laravel php. – sam Jan 31 '18 at 15:30
  • the upload process with that html and javascript seems works to me, but not work when I want to process on server side script.. i dont understand how to decode using base64_decode() on codeigniter controller to upload file :D – bungdito Mar 14 '18 at 20:25
  • i used file_put_contents() to save decoded image file, it's works.. but the file can't open/view – bungdito Mar 14 '18 at 21:24
  • @bungdito Sorry, can't say anything about CI. I haven't done file handling in it. – Umair Ahmed Mar 15 '18 at 02:32
  • this will only work for one image, if you try to add more the $('#file').val(content); will override the input and only the last image will be sent to the server, how can I change this to send multiple images? – AleXzpm Nov 29 '18 at 14:43
  • @AleXzpm Sorry, that was the first and last time I used Dropzone, can't help you for the time being. – Umair Ahmed Nov 30 '18 at 15:21
  • 2
    If you want to add mutiple images you have to remove the html file input and add it quing js for each image $('#fileUpload').append('') where content is the base64 encoded image. – AleXzpm Dec 01 '18 at 16:44
  • @AleXzpm if you can suggest an edit, please do so, I'll accommodate it. Or you can post a separate answer extending mine. – Umair Ahmed Dec 02 '18 at 02:43
  • 1
    This won't work reliably, as the input fields value has a browser capped max size. In my case the uploaded file is capped at 384kb, due to the base64 string not fitting completely into the value. –  Jan 14 '20 at 16:30
  • This is the only answer that works with a plain form submit, so does not require JS to handle the form submit. – aross Apr 01 '21 at 10:00
8

Enyo's tutorial is excellent.

I found that the sample script in the tutorial worked well for a button embedded in the dropzone (i.e., the form element). If you wish to have the button outside the form element, I was able to accomplish it using a click event:

First, the HTML:

<form id="my-awesome-dropzone" action="/upload" class="dropzone">  
    <div class="dropzone-previews"></div>
    <div class="fallback"> <!-- this is the fallback if JS isn't working -->
        <input name="file" type="file" multiple />
    </div>

</form>
<button type="submit" id="submit-all" class="btn btn-primary btn-xs">Upload the file</button>

Then, the script tag....

Dropzone.options.myAwesomeDropzone = { // The camelized version of the ID of the form element

    // The configuration we've talked about above
    autoProcessQueue: false,
    uploadMultiple: true,
    parallelUploads: 25,
    maxFiles: 25,

    // The setting up of the dropzone
    init: function() {
        var myDropzone = this;

        // Here's the change from enyo's tutorial...

        $("#submit-all").click(function (e) {
            e.preventDefault();
            e.stopPropagation();
            myDropzone.processQueue();
        }); 
    }
}
Rick Smith
  • 9,031
  • 15
  • 81
  • 85
kablamus
  • 921
  • 2
  • 10
  • 17
  • 26
    You cannot have a form inside a form and submit. – paul Jan 20 '14 at 08:04
  • 1
    When I try this, the dropzone-previews container seems to be ignored. Dropzone just adds the previews to the bottom of the form. You will need to add 'previewsContainer: '.dropzone-previews',' to your configuration. – Aaron Hill Dec 04 '14 at 18:57
  • 9
    This doesn't answer the original question. The original question was how to use Dropzone with an existing form, not about the location of the button to trigger the action. – CSSian Feb 26 '15 at 00:21
8

Further to what sqram was saying, Dropzone has an additional undocumented option, "hiddenInputContainer". All you have to do is set this option to the selector of the form you want the hidden file field to be appended to. And voila! The ".dz-hidden-input" file field that Dropzone normally adds to the body magically moves into your form. No altering the Dropzone source code.

Now while this works to move the Dropzone file field into your form, the field has no name. So you will need to add:

_this.hiddenFileInput.setAttribute("name", "field_name[]");

to dropzone.js after this line:

_this.hiddenFileInput = document.createElement("input");

around line 547.

Codedragon
  • 101
  • 1
  • 5
6

I want to contribute an answer here as I too have faced the same issue - we want the $_FILES element available as part of the same post as another form. My answer is based on @mrtnmgs however notes the comments added to that question.

Firstly: Dropzone posts its data via ajax

Just because you use the formData.append option still means that you must tackle the UX actions - i.e. this all happens behind the scenes and isn't a typical form post. Data is posted to your url parameter.

Secondly: If you therefore want to mimic a form post you will need to store the posted data

This requires server side code to store your $_POST or $_FILES in a session which is available to the user on another page load as the user will not go to the page where the posted data is received.

Thirdly: You need to redirect the user to the page where this data is actioned

Now you have posted your data, stored it in a session, you need to display/action it for the user in an additional page. You need to send the user to that page as well.

So for my example:

[Dropzone code: Uses Jquery]

$('#dropArea').dropzone({
    url:        base_url+'admin/saveProject',
    maxFiles:   1,
    uploadMultiple: false,
    autoProcessQueue:false,
    addRemoveLinks: true,
    init:       function(){
        dzClosure = this;

        $('#projectActionBtn').on('click',function(e) {
            dzClosure.processQueue(); /* My button isn't a submit */
        });

        // My project only has 1 file hence not sendingmultiple
        dzClosure.on('sending', function(data, xhr, formData) {
            $('#add_user input[type="text"],#add_user textarea').each(function(){
                formData.append($(this).attr('name'),$(this).val());
            })
        });

        dzClosure.on('complete',function(){
            window.location.href = base_url+'admin/saveProject';
        })
    },
});
Antony
  • 3,875
  • 30
  • 32
  • It is working, But I am uploading 3 images, it is only sending 2 images on the backend. I have changed this below code: maxFiles: 10, uploadMultiple: true, Why it is sending only 2 images? – Shehroz Altaf Jan 25 '21 at 07:00
  • I would try with an alternative image and see if its a problem with the files. Alternatively does your PHP/ASP etc give you any errors? It could be the image is too big, not enough disc space, server limits/settings etc. – Antony Jan 25 '21 at 08:46
  • No. My PHP has no error. It is uploading images correctly. However, I got the solution. I changed below lines: url: myURL uploadMultiple: true, parallelUploads: 25, maxFiles: 25, autoProcessQueue: false, addRemoveLinks: true, – Shehroz Altaf Jan 26 '21 at 09:10
  • I find your explanations very interesting. I can't get the elements to go to the processing page. I think it has to do with sessions. Could you give an example of the php processing page or indicate why my _FILE and _POST elements are not resolving. – Rich Apr 16 '21 at 17:07
  • Just post to a page that does and analyse it through the Browser console > Networking tab (thats what its called in Chrome) and see what outputs. – Antony Apr 21 '21 at 07:36
5

You can modify the formData by catching the 'sending' event from your dropzone.

dropZone.on('sending', function(data, xhr, formData){
        formData.append('fieldname', 'value');
});
shawnrushefsky
  • 141
  • 2
  • 7
  • 2
    I like this answer - but it would assume that fieldname and value have been populated. This is triggered on the upload which may occur at a separate time to the form submission. In otherwords you can't assume that when sending the image the form is filled out. – Antony Sep 03 '19 at 11:02
5

In order to submit all files alongside with other form data in a single request you can copy Dropzone.js temporary hidden input nodes into your form. You can do this within addedfiles event handler:

var myDropzone = new Dropzone("myDivSelector", { url: "#", autoProcessQueue: false });
myDropzone.on("addedfiles", () => {
  // Input node with selected files. It will be removed from document shortly in order to
  // give user ability to choose another set of files.
  var usedInput = myDropzone.hiddenFileInput;
  // Append it to form after stack become empty, because if you append it earlier
  // it will be removed from its parent node by Dropzone.js.
  setTimeout(() => {
    // myForm - is form node that you want to submit.
    myForm.appendChild(usedInput);
    // Set some unique name in order to submit data.
    usedInput.name = "foo";
  }, 0);
});

Obviosly this is a workaround dependent on implementation details. Related source code.

Leonid Vasilev
  • 11,910
  • 4
  • 36
  • 50
  • I basically used this approach, but due to apparent processing delays, in the end hooked up the file content processing under the `myDropzone.on("thumbnail", () => {})` event. Doing the processing immediately on `"addedFile"` the file might still be `undefined` upon access. – Matti Aug 28 '17 at 13:21
  • I'm trying to use this and it works in bringing the hidden file input field to the form, and when I submit, the post data shows my field `files[]` but it is blank no matter what I do. Any ideas? Doing it in Laravel if it makes any difference. – zen Feb 19 '19 at 22:11
  • Hello! Why does selected file does upload but if file is dropped then not (error 4)? – Ingus Jun 11 '20 at 13:49
  • @Ingus Because the hidden input is only used in Dropzone for opening the file picker. For uploading the files, it uses a custom AJAX to the URL specified in config. For security reasons, we cannot modify the files attribute of the `` and hence to allow modifications we need to upload using AJAX. – Kahan Bhalani Mar 24 '23 at 08:25
3

Working solution for 5.7.0 version

<form id="upload" enctype="multipart/form-data">
    <input type="text" name="name" value="somename">
    <input type="checkbox" name="terms_agreed">
    <div id="previewsContainer" class="dropzone">
      <div class="dz-default dz-message">
        <button class="dz-button" type="button">
          Drop files here to upload
        </button>
      </div>
    </div>
    <input id="dz-submit" type="submit" value="submit">
</form>
Dropzone.autoDiscover = false;
new Dropzone("#upload",{
      clickable: ".dropzone",
      url: "upload.php",
      previewsContainer: "#previewsContainer",
      uploadMultiple: true,
      autoProcessQueue: false,
      init() {
        var myDropzone = this;
        this.element.querySelector("[type=submit]").addEventListener("click", function(e){
          e.preventDefault();
          e.stopPropagation();
          myDropzone.processQueue();
        });
      }
    });

2

Here is my sample, is based on Django + Dropzone. View has select(required) and submit.

<form action="/share/upload/" class="dropzone" id="uploadDropzone">
    {% csrf_token %}
        <select id="warehouse" required>
            <option value="">Select a warehouse</option>
                {% for warehouse in warehouses %}
                    <option value={{forloop.counter0}}>{{warehouse.warehousename}}</option>
                {% endfor %}
        </select>
    <button id="submit-upload btn" type="submit">upload</button>
</form>

<script src="{% static '/js/libs/dropzone/dropzone.js' %}"></script>
<script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
<script>
    var filename = "";

    Dropzone.options.uploadDropzone = {
        paramName: "file",  // The name that will be used to transfer the file,
        maxFilesize: 250,   // MB
        autoProcessQueue: false,
        accept: function(file, done) {
            console.log(file.name);
            filename = file.name;
            done();    // !Very important
        },
        init: function() {
            var myDropzone = this,
            submitButton = document.querySelector("[type=submit]");

            submitButton.addEventListener('click', function(e) {
                var isValid = document.querySelector('#warehouse').reportValidity();
                e.preventDefault();
                e.stopPropagation();
                if (isValid)
                    myDropzone.processQueue();
            });

            this.on('sendingmultiple', function(data, xhr, formData) {
                formData.append("warehouse", jQuery("#warehouse option:selected").val());
            });
        }
    };
</script>
Zhong Ri
  • 2,556
  • 1
  • 19
  • 23
1

This is just another example of how you can use Dropzone.js in an existing form.

dropzone.js :

 init: function() {

   this.on("success", function(file, responseText) {
     //alert("HELLO ?" + responseText); 
     mylittlefix(responseText);
   });

   return noop;
 },

Then, later in the file I put

function mylittlefix(responseText) {
  $('#botofform').append('<input type="hidden" name="files[]" value="'+ responseText +'">');
}

This assumes you have a div with id #botofform that way when uploading you can use the uploaded files' names.

Note: my upload script returned theuploadedfilename.jpeg dubblenote you also would need to make a cleanup script that checks the upload directory for files not in use and deletes them ..if in a front end non authenticated form :)

Flip
  • 6,233
  • 7
  • 46
  • 75
taggart
  • 29
  • 1
  • 1
    This doesn't submit dropzone images together with other form fields. What you're doing is uploading images normally, saving the image names, and then re-submitting rest of the form fields with image names. – zen Feb 19 '19 at 22:13
0

Try this

<div class="dropzone dz-clickable" id="myDrop">
  <div class="dz-default dz-message" data-dz-message="">
    <span>Drop files here to upload</span>
  </div>
</div>

JS

<script>
    Dropzone.autoDiscover = false;
    Dropzone.default.autoDiscover=false;
    $("div#myDrop").dropzone({
        url: "/file/post",
    });
</script>
Neeraj Sharma
  • 297
  • 1
  • 4
  • 16