Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Drag,Drop and Submit files to upload

I am implementing drag and drop of files like this in asp.net mvc 5, but my requirement is when i drag files,it should not be immediately uploaded. First drag the files, Then click on a button("Enter MetaData") to enter few mandatory properties (metadata-name, category & etc...) for each file, Then click on other button(Submit) to submit to upload.

Normally when we drag a file, it immediately gets uploaded, I have to stop it and do it on button click(after filling the other fields). Even any third party js library that has similar functionality ?

I googled a lot but not getting expected results. Can someone please guide me how to go for this requirement or provide me some links to go for this requirement.

like image 680
F11 Avatar asked Sep 24 '15 14:09

F11


People also ask

How do I upload an HTML file?

The <input type="file"> defines a file-select field and a "Browse" button for file uploads. To define a file-select field that allows multiple files to be selected, add the multiple attribute. Tip: Always add the <label> tag for best accessibility practices!


2 Answers

The sample code you have linked to appears to be using jquery.filedrop.js, which is written by Weixi Yen. You need to download and use the latest version from its project home for this to work.

You should also download and use a later version of jquery than the one bundled with that sample code. I have tested this with jquery 1.9.1.

To use the jquery extension you've chosen, you need to take advantage of the beforeSend option, and supply your own function. You also need to store references to the done() functions that are provided to your custom function for each file, so that you can call them later, causing the files to be uploaded.

If you want meta boxes to appear for each file, then you need to append the appropriate html on a per file basis to allow the user to fill them in.

A summary of the code I would suggest is below:

var uploads_to_call = [];  // the global to store all the file upload callbacks

$('#dropzone').filedrop({
    fallback_id: 'upload_button',   // an identifier of a standard file input element, becomes the target of "click" events on the dropzone
    url: 'upload.php',              // upload handler, handles each file separately, can also be a function taking the file and returning a url
    // other important parameters related to upload, read the documentation for details

    // this is the important custom function you need to supply
    beforeSend: function(file, i, done) {
        // file is a file object
        // i is the file index
        // call done() to start the upload

        // this is just to clone meta boxes for the user to fill in for each file
        // it also fills in the filename so that it's obvious which meta boxes
        // are for which files
        $("#perfile").children().clone()
           .appendTo("#allmeta")
           .children(".filename").html(file.name);

        // this pushes the done() callback into the global mentioned earlier
        // so that we can call it later
        uploads_to_call.push(done);
    },
    afterAll: function() {
        // runs after all files have been uploaded or otherwise dealt with
        // you should possibly put code in here to clean up afterwards
    }
});

// set a handler to upload the files when the submit button is clicked
$("#submit").click(function(){
    $.each(uploads_to_call, function(i, upcall) {
        upcall();
    });
});

With html similar to the following:

<form>
    <div id="dropzone"></div>
    <div id="allmeta"></div>
    <button id="submit">submit</button>
</form>

<div id="perfile">
    <div class="meta">
        <span class="filename"></span>
        <input type="text" placeholder="meta"/>
        <input type="text" placeholder="meta"/>
    </div>
</div>

The div#perfile should have css to hide it, as it is only used to contain the div to be cloned into the form every time a file is dragged in.

I've created a proof of concept jsfiddle here, obviously this doesn't actually allow the files to be uploaded, but it shows the JS side of things working. You need to scroll down to the bottom of the javascript panel to see the custom code - the stuff in the top is just including the javascript extension you should have downloaded from the project home website.

This should really be good enough for you to get the asp.net side of things working. You just need to post the additional metadata provided by the user in a manner you see fit.

Obviously this is barebones, you should flesh it out as you see fit.

like image 172
enigma Avatar answered Sep 19 '22 02:09

enigma


Since you're looking for a library, I developed a full vanilla Javascript solution for you, as follows:

/* as soon as all the elements of the page has been loaded */
document.addEventListener('DOMContentLoaded', function() {
  /* we reference the most used elements into variables, making it easier to use later */
  var dropzone = document.getElementById('dropzone'),
      dialog = document.getElementById('dropzone-dialog'),
      submit = document.getElementById('submit'),
      progress = document.querySelector('progress'),
      _current = null; // we keep track what file the user is changing its metadata

  /* when the user drags something over our div, we must prevent the browser's default
     behavior and we change a CSS class only for usability reasons */
  dropzone.addEventListener('dragover', function(e) {
    e.preventDefault();
    dropzone.classList.add('dragging');
  });

  /* when the user leaves our div, we must remove the CSS class we put before */
  dropzone.addEventListener('dragleave', function(e) {
    e.preventDefault();
    dropzone.classList.remove('dragging');
  });

  /* that's the heart of our code,
     when the user effectively drops one or more files into the div */
  dropzone.addEventListener('drop', function(e) {
    /* again we must prevent the default browser's behavior,
       and we need to remove the CSS clas we put before */
    e.preventDefault();
    dropzone.classList.remove('dragging');
    submit.classList.add('hidden');

    /* we change our div's text */
    dropzone.textContent = 'File(s) dropped:';

    /* we create an UL element, so we can loop through the files make a list */
    var ul = document.createElement('ul');
    /* since the files property is not an array, but it is Array-like,
       we call the Array.prototype.forEach method passing the files to be the context */
    [].forEach.call(e.dataTransfer.files, function(file) {
      /* for each file the user has dropped, we create a LI and an anchor inside it */
      var li = document.createElement('li');
      var a = document.createElement('a');

      a.href = '#';
      a.textContent = file.name;
      a.title = 'Enter metadata';
      
      /* we create a property __file into our anchor to reference the dropped file */
      a.__file = file;

      li.appendChild(a);
      ul.appendChild(li);
    });
    dropzone.appendChild(ul);
  });

  /* here, we add a 'click' event handler to our div dropzone,
     so we can add the file's metadata behavior.
     the idea here is to make an event delegation, that is, when the user clicks anywhere
     inside the div, we see if the element that was clicked is one of the files */
  dropzone.addEventListener('click', function(e) {
    var el = e.target;

    /* if the element clicked is an anchor, it's because it's one of the files */
    if (el.tagName === 'A') {
      /* we prevent the browser's default behavior */
      e.preventDefault();
      /* we keep track that the current clicked file/anchor is the last clicked */
      _current = el;
      
      /* and then, we show our dialog, so the user can input the file's metadata */
      dialog.classList.remove('hidden');
      /* we set focus on the first element of the dialog, only for usability reasons */
      dialog.querySelector('#name').focus(); 
      
    }
  });

  /* we add an event handler to the dialog's close button,
     so it can effectively close the dialog */
  dialog.querySelector('.close').addEventListener('click', clearDialog);
  
  /* this is the second heart of our code.
     we add an event handler to the dialog's save button,
     so it can handle the saving of metadata */
  dialog.querySelector('#save').addEventListener('click', function() {
    /* here you can add any validation you want, e.g: required fields, etc */
    
    /* if everything was cool, we set a new property __dataFile to our anchor
       so, we can save the metadata for future use (the form submission) */
    _current.__dataFile = {
      name: dialog.querySelector('#name').value,
      category: dialog.querySelector('#category').value
      /* put here any other metadata property you will save */
    };
    
    /* when the user saves the metadata, we add a CSS class only for usability reasons */
    _current.classList.add('finished');

    /* then, we keep track if there is any file with no metadata saved yet */
    var btnsLeft = [].filter.call(dropzone.querySelectorAll('a'), function(el, i) {
      return !('__dataFile' in el);
    });

    /* if all the files' metadatas have been saved, we show the submit button */
    if (btnsLeft.length === 0)
      submit.classList.remove('hidden');

    /* and we clear/close our dialog */
    clearDialog();
  });

  function clearDialog() {
    /* when the dialog is closed, we set our current-clicked anchor to null */
    _current = null;

    /* we clean all the input fields of the dialog */
    [].forEach.call(dialog.querySelectorAll('input'), function(el, i) {
      el.value = '';
    });

    /* and we effectively hide/close the dialog */
    dialog.classList.add('hidden');
  }

  /* this is third heart of our code.
     we add a click event handler to our submit button,
     so we can effectively send our form to the server */
  submit.querySelector('button').addEventListener('click', function(e) {
    e.preventDefault(); // we must prevent the browser's default behavior

    /* we create a XMLHttpRequest to send our AJAX to the server */
    var xhr = new XMLHttpRequest();

    /* we add a 'load' event handler, so we can keep track when the upload is finished */
    xhr.addEventListener('load', finished);
    /* we do the same for progress and error, so we can inform our user what's going on */
    xhr.upload.addEventListener('progress', progress);
    xhr.addEventListener('error', error);
    
    /* now it's time to save all our data, so we create a FormData */
    var fd = new FormData();

    /* and for each anchor(file) inside our dropzone, we add new data to our FormData */
    [].forEach.call(dropzone.querySelectorAll('a'), function(el, i) {
      /* here we loop through our __dataFile previously created property,
         so we can add the file's metadata parameter */
      for (var prop in el.__dataFile) {
        /* since we are sending multiple files/metadatas,
           it's important to name our field with [] */
        fd.append(prop + '[]', el.__dataFile[prop]);
      }
      
      /* and then, we append the file itself - again, with the [] at the end */
      fd.append('file[]', e.__file);
    });

    /* now we open the xhr, and we effectively send the request to the server */
    xhr.open('POST', '/api/uploadfiles/'); // change for your API URL
    xhr.send(fd);
  });
  
  /* this is the xhr's progress, so we can update our HTMLProgressElement's percent */
  function progress(e) {
    progress.value = progress.innerHTML = (e.loaded / e.total * 100 | 0);
  }

  /* this is the xhr's finished event handler,
     we show a friendly message and hide the submit button */
  function finished(e) {
    progress.value = progress.innerHTML = 100;
    dropzone.innerHTML = '<h3>Files successfully uploaded</h3>';
    submit.classList.add('hidden');
  }
  
  /* this is the xhr's error event handler. If there is any known error, we show it */
  function error(e) {
    var xhr = e.target;
    submit.classList.add('hidden');
    dropzone.innerHTML = '<h3>Error while uploading</h3>' +
      (xhr.status ? '<span>' + xhr.status + ' - ' + xhr.statusText + '</span>' : '');
  }
});
#dropzone {
  width: 450px;
  height: 165px;
  background-color: #CCC;
  font: 16px Verdana;
  padding: 10px;
  box-sizing: border-box;
}
#dropzone.dragging {
  background-color: #EEE;
}
#dropzone > h3 {
  text-align: center;
}
#dropzone a {
  text-decoration: none;
}
#dropzone a:hover {
  text-decoration: underline;
}
#dropzone a.finished:after {
  content: ' √';
  color: green;
  font-weight: bold;
  text-decoration: none;
}
#dropzone ul {
  list-style: none;
  margin-left: 15px;
  padding: 0;
}
#dropzone li {
  margin-top: 8px;
}
#dropzone li:before {
  content: '> ';
}
.hidden {
  display: none;
}
.dialog {
  background-color: #AAAAFF;
  font-family: Verdana;
  padding: 12px;
  border-radius: 10px;
  width: 300px;
  height: 150px;
  position: absolute;
  top: 20px;
}
.dialog h3 {
  text-align: center;
}
.dialog .close {
  text-decoration: none;
  float: right;
}
.dialog .close:after {
  content: '✖';
  cursor: pointer;
}
.dialog > div {
  display: table;
}
.dialog > div > div {
  display: table-row;
}
.dialog > div > div > div,
.dialog > div > div > label {
  display: table-cell;
  padding: 2px 0 2px 10px;
}

#submit {
  height: 160px;
  width: 450px;
  text-align: right;
  position: fixed;
  top: 10px;
}
#submit button {
  font-size: 20px;
  padding: 10px;
  background-color: orange;
}

progress {
 width: 450px; 
}
<div id="dropzone">
  <h3>Drop your files here</h3>
</div>
<div>
  <progress min="0" max="100" value="0"></progress>
</div>
<div id="submit" class="hidden">
  <button>Submit</button>
</div>
<div id="dropzone-dialog" class="dialog hidden">
  <a class="close"></a>
  <h3>File Metadata</h3>
  <div>
    <div>
      <label for="name">
        Name
      </label>
      <div>
        <input id="name">
      </div>
    </div>
    <div>
      <label for="category">
        Category
      </label>
      <div>
        <input id="category">
      </div>
    </div>
    <div>
      <div></div>
      <div style="text-align: right">
        <button id="save">Save</button>
      </div>
    </div>
  </div>
</div>

The code above is fully commented, but if you need any help, please leave a comment.

like image 40
Buzinas Avatar answered Sep 21 '22 02:09

Buzinas