In this article, we will discuss how to do ajax file upload with drag and drop box.
File input
We can use HTML input tag with type=“file” and attribute “multiple” to enable multiple file selection.
<input type=”file” id=”files” multiple style=”display: none;” />
It display as a button like following:
Click this button will open the “File Open Dialog”. However, there are no easy way to customize the look and feel of this button. In most of cases, we hide this button and trigger the “File Open Dialog” using other element. We will show you shortly.
Drop Zone
Drop zone in the web page can be just a div element. Normally we would decorate this element to visually indicate this is a drop zone. Browsers default drop behavior is to open the file. We need to disable this default behavior and provide our own logic to handle the files drop. In order for the drop zone to receive the files drop, it would need to capture following events:
- drop event: This event fires when an item is dropped on a valid drop target. The files are in property of
event.dataTransfer.files
. This property contains an array of File object. - dragover event: This event fires when an item is being dragged over a valid drop target. If the item is not released, this event will keep firing every few hundred milliseconds. In this event handler, we need to call
preventDefault()
to disable browser default behavior (open the file). We can also do some styling changes to visually indicate the files are over the drop zone and ready to drop, such as change the background color and border width. - dragenter event: This event fires only once when a dragged item enters a valid drop target. In this event handler, we need to call
preventDefault()
to disable browser default behavior. - dragleave event: This event fires only once when a dragged item leaves a valid drop target. We can remove the drop zone style changes we did in the dragover event handler.
- click event: This event fires when user click in any area inside the drop zone. In this event handler, we can trigger the “File Open Dialog” programmatically so user can select files without drag and drop.
Handle Drop Event
As previously mentioned, the drop event has files inside event.dataTransfer.files
property. This property contains an array of File object. Each file object has following properties:
- name: The name of the file
- size: The size of the file in bytes
- type: The MIME type of the file
Inside the drop event, we need to do following things:
- Restore the dropzone style to normal.
- Call
pereventDefault()
function to prevent browser default behavior for the file drop (opening the file). - Go through the
event.dataTransfer.files
collection to make sure the file type is what you expected, the file size does not exceed your maximum allowed size, etc. - Once the files validation passed, display all the file details and Upload button for user to confirm and upload.
Prevent Drop Outside DropZone
When user drag the files, we do not want user to drop the files outside the dropzone we defined. The browser default behavior is to open these files. We should add logic if user drop the file outside the drop zone, nothing would happen. In order to achieve this, we need to capture the all the events we mentioned above in Window level.
window.addEventListener(“dragenter”,function(e){
if (e.target.id != “dropZone” && e.target.id != “dropZoneText”)
{
e = e || event;
e.dataTransfer.dropEffect = “none”;
e.preventDefault();
}
},false);window.addEventListener(“dragover”,function(e){
if (e.target.id != “dropZone” && e.target.id != “dropZoneText”)
{
e = e || event;
e.dataTransfer.dropEffect = “none”;
e.preventDefault();
}
},false);window.addEventListener(“drop”,function(e){
if (e.target.id != “dropZone” && e.target.id != “dropZoneText”)
{
e = e || event;
e.preventDefault();
}
},false);
Inside the dragover and dragenter event handlers, we check if the element is not inside the drop zone, we set the e.dataTransfer.dropEffect = "none"
which will change the mouse cursor to “not allowed”. We also call the e.preventDefault()
to prevent default browser behavior.
File Upload
The ajax file upload uses FormData interface to upload file. FormData interface has append() method:
formData.append(name, value, filename);
We can add the file to FormData object as following:
const formData = new FormData();
formData.append(“file”, file.data, file.data.name);
The formData object can then be provided to JQuery ajax call as data property.
$.ajax({
type: "POST",
url: 'https://localhost:44384/file/upload',
xhr: function () {
var xhr = $.ajaxSettings.xhr();
if (xhr.upload) {
xhr.upload.addEventListener('progress', self.progressHandling(file.fileIndex), true);
}
return xhr;
},
success: function(result) {
// Add success logic here
},
error: function(error) {
console.log(error);
// Add error handling here
},
async: true,
data: formData,
cache: false,
contentType: false,
processData: false,
timeout: 60000
});
Please note you have to set contentType
to false
and processData
to false
in order for it to work:
- Setting the
contentType
tofalse
tells jQuery to not set any content type header - Setting
processData
tofalse
will prevent jQuery from transforming the data into a string. See the jQuery docs for more info.
Tracking Upload Progress
For large file upload, we may want to show the upload progress to user. In order to do that, you need to capture the jQuery xhr callback function. Inside the callback function, add the event listener for the progress
event. The progress
event has only one parameter of type ProgressEvent
, which as two properties: loaded
and total
. Base on these two properties, we can easily calculate the uploading percentage.
progressHandling = function (index) {
const self = this;
return function (event) {
var percent = 0;
var position = event.loaded || event.position;
var total = event.total;
if (event.lengthComputable) {
percent = Math.ceil(position / total * 100);
}
console.log(self.files[index].data.name, “upload percentage: “, percent);
$("#fileList tr[id='fileNum-" + index + "'] .progress-bar").css("width", +percent + "%");
$("#fileList tr[id='fileNum-" + index + "'] .progress-bar").text(percent + "%");
}
}
In case of multiple file uploads, we need to know which file we are working one, so we need extra parameter: file index. However, the progress
event accept only one parameter of type ProgressEvent.
We need to use JavaScript closure technique to achieve our goal. The progressHandling
function accept file index as parameter and return a function to handle the progress
event. The function that handles the progress
event will be able to access the file index parameter in the outer function.
You can see how the page looks like in JSFiddler: https://jsfiddle.net/wnms2000/aqL7pfcm/3. Of course, the Upload button would not work since there is no backend API to handle the upload request.
Web API
On the web api side, we can define a method that accept IFormFile parameter. The parameter name should be the same as the name parameter we used in formData.append()
method (in our case, it is called “file”). Following is the code snippet to handle the ajax file upload in asp.net core 3.1 Web API :
You can download the code (front end and back end) from Github: https://github.com/jason-ge/AjaxFileUpload. For testing, open the solution with Visual Studio 2019 and run the Web API project. Open the ajax_fileupload.html file in browser. Drag and drop multiple files into the drop zone and click the “Upload” button to see how it works.
Happy coding!