Hello everyone, today I'm going to talk about Drag 'n Drop in modern spider web applications (someone said HTML5?).
I got interested in this topic because of a projection I'm currently working on: nosotros have a page where the user has to upload an Excel file to load some info into the organization and instead of having but a page with the former-fashioned input for browsing the filesystem to pick a file and so press submit, we decided it would have been fun & useful to allow the user to elevate and drop the desired file into the page, similar to what you can do with Gmail attachments. Afterwards googling, I found out that HTML 5 draft specification includes some new cool features that allow us to easily create a system that gets the job washed in a pretty straightforward mode. In that location are already a skillful number of (jQuery and non) plugins out in that location that practice get the task done (in a much more than clever way than my demo does), but I retrieve it can be good fun and very useful to learn how to do it yourself get-go.

Goal

I'm going to showcase a simple Asp.Net MVC 3 spider web app that lets a user drag one paradigm file at a time from the desktop and driblet it into a special "drop zone" into the spider web folio. The organization will then take care of uploading it to the server and, if upload was successfull, it will brandish to the user the link to access the uploaded paradigm.

What I'll use

  • Asp.Cyberspace MVC 3
  • HTML v DnD API, File API and XMLHttpRequest object to handle elevate&drop and async file upload
  • jQuery (version one.7.1 is the latest version at the moment)
  • Bootstrap for UI widgets

Important note

HTML5 and the FileAPI are still typhoon versions and as such they are discipline to change until the final revision comes out and are not supported in all major browsers.
To see exactly what can exist used and what non, have a look at the following two websites: http://caniuse.com/  http://html5please.us/.
All the code you will find below has been tested nether Google Chrome sixteen and IE 9.

Conceptually we tin can split the async file upload operation in ii different and consecutive moments:

  1. Drag and drib
  2. Async file upload and View update

Function i: Drag and Driblet

This role is all about creating a webpage with a really unproblematic layout: just 1 dropbox (HTML5 DnD API) where the user can driblet an image.
Subsequently dropping the image, the web page volition load the dropped file in memory (using JS FileAPI) and will show to the user a table with a thumbnail, some bones information like filename and size and a progress bar to provide feedback to the user virtually the upload operation.

Equally you tin meet in the beneath screenshot, the page I'm going to build is actually simple:

Landing page as displayed by Chrome

Landing folio as displayed past Chrome

We can start creating something very elementary merely not that ugly using Bootstrap (I won't evidence the Layout lawmaking here, you can download the source on github) and the Index page at the beginning looks something like this:

<course id="fileUpload" action="/Abode/Upload" method="POST" enctype="multipart/form-data">      <!-- Principal hero unit for a primary marketing bulletin or phone call to action dropzone="re-create f:image/png f:epitome/gif f:image/jpeg" f:awarding/vnd.openxmlformats-officedocument.spreadsheetml.sheet f:awarding/vnd.ms-excel-->     <div id="dropbox" dropzone="copy f:image/png f:image/gif f:paradigm/jpeg" class="hero-unit of measurement">         <h2 id="droplabel">Drop zone</h2>         <p id="dnd-notes">Only images file type are supported. Multiupload is not currentrly supported. Once you will drop your image in the dropzone, the upload will kickoff.</p>         <table id="files-list-table">             <thead>                 <tr>                     <thursday></thursday>                     <thursday>File name</th>                     <th>File size</th>                     <th>Upload status</thursday>                 </tr>             </thead>             <tbody>                 <tr>                     <td id="td-img-preview" class="tableData">                         <img id="preview" src="" alt="preview volition display here" />                     </td>                     <td id="td-file-proper noun" form="tableData"></td>                     <td id="td-file-size" class="tableData"></td>                     <td id="td-progress-bar" course="tableData">                         <div id="progressBarBox" course="progress progress-info progress-striped">                           <div form="bar"></div>                         </div>                     </td>                 </tr>             </tbody>         </table>          <br/>         <div id="multiupload-alert"class="alert warning-error">             <h4 grade="alert-heading">Alarm!</h4>             <span id="multiupload-alarm-bulletin"></span>         </div>         <input id="fileSelect" blazon="file" proper noun="fileSelect" />         <p><button id="fallback-submit" type="submit" grade="btn btn-primary btn-large">Upload</button></p>     </div> </form>                          

We start to run into the showtime HTML5 thing here with the "dropzone" attribute:

<div id="dropbox" dropzone="copy f:prototype/png f:image/gif f:image/jpeg" course="hero-unit of measurement">                            

Every bit the draft specification says, information technology'south purpose is to tell the browser that this element will accept files Drag&Drop.
This attribute can be placed on every HTML element.
Its value specifies which activeness must be taken upon dropping the file and which MIME types are accepted.
Possible values are:

  1. copy
  2. move
  3. link

I used copy, which indicates that dropping an accustomed item on the element will issue in a copy of the dragged data, and this value is also the default behaviour.
The second part of the value, "f:epitome/png f:image/gif f:prototype/jpeg" indicates the accepted MIME types.

That existence said, equally of now the dropzone attribute is not currently supported in any of the major browsers, this means that nosotros have to find another way to exercise it.
Luckily enough, some major browsers support the javascript events related to DnD, similar:
drop, dragover or dragexit, so the "logic" will be handled using  javascript!

You can see I also added a classic input type="file" forth with a submit push button:

<input id="fileSelect" type="file" proper noun="fileSelect" />         <p><button id="fallback-submit" type="submit" class="btn btn-main btn-large">Upload</button></p>                                  

This is at that place to provide compatibility whenever DnD or FileAPI or async upload (XMLHttpRequest object) are not supported by the browser, as it's the case for IE9, and then we are merely fallin back to classic file upload and the user is still able to use the service.
Whenever a fallback is needed, the page displays like the below epitome

Landing page as displayed by IE9

Landing page as displayed by IE9

So, how practice we handle the DnD events and how exercise we detect if the browser supports FileAPI and async upload?

<script blazon="text/javascript">     var fileName = cypher;     var fileToUpload = goose egg;      $(document).gear up(function () {         // init the widgets         $("#progressBarBox").hide();         $("#files-list-table").hide();          // Check if FileAPI and XmlHttpRequest.upload are supported, so that we tin hide the onetime fashion input method         if (window.File && window.FileReader && window.FileList && window.Blob && new XMLHttpRequest().upload) {             $("#fileSelect").hibernate();             $("#fallback-submit").hibernate();             $("#multiupload-warning").hibernate();             var dropbox = document.getElementById("dropbox")             // init event handlers             dropbox.addEventListener("dragenter", dragEnter, fake);             dropbox.addEventListener("dragexit", dragExit, false);             dropbox.addEventListener("dragleave", dragExit, false);             dropbox.addEventListener("dragover", dragOver, false);             dropbox.addEventListener("driblet", driblet, false);         }         else { 	$("#multiupload-alert").hide();             $("#dnd-notes").hide();         }     });      function dragEnter(evt) {         evt.stopPropagation();         evt.preventDefault();     }      function dragExit(evt) {         evt.stopPropagation();         evt.preventDefault();          $("#dropbox").removeClass("agile-dropzone");     }      office dragOver(evt) {         evt.stopPropagation();         evt.preventDefault();          $("#dropbox").addClass("active-dropzone");     }  continues...                                      

The check happens hither:

// Check if FileAPI and XmlHttpRequest.upload are supported, then that we can hide the former style input method         if (window.File && window.FileReader && window.FileList && window.Blob && new XMLHttpRequest().upload)                                        

The starting time 4 conditions are related to the JS File API, whereas the final is about the async upload. Then if the check is positive, we hide the archetype file upload fashion and we add event handlers for the dropbox to handle DnD

$("#fileSelect").hide(); $("#fallback-submit").hide(); $("#multiupload-alert").hide(); var dropbox = document.getElementById("dropbox") // init event handlers dropbox.addEventListener("dragenter", dragEnter, imitation); dropbox.addEventListener("dragexit", dragExit, false); dropbox.addEventListener("dragleave", dragExit, false); dropbox.addEventListener("dragover", dragOver, fake); dropbox.addEventListener("driblet", drop, false);                                          

Important note: it is not sufficient to but add a handler for the driblet upshot, every bit it will non properly fire if the others are not set (similar WPF DnD events).

I utilise the dragover and dragexit handlers to alter the dropbox advent.
At present on to the drop result handler:

function drop(evt) {         evt.stopPropagation();         evt.preventDefault();          $("#dropbox").removeClass("agile-dropzone");         var temp = $("#multiupload-alert");         // If there is an alert message displayed, we hide it         $("#multiupload-alarm").hide();         var files = evt.dataTransfer.files;         var count = files.length;          // Just phone call the handler if one file was dropped.         if (count == 1) {             handleFiles(files);         }         else if (count > one) {             var message = "Multiupload is not currently supported. Choose only one file.";             $("#multiupload-alert-message").html(bulletin);             $("#multiupload-alert").show();         }     }                                              

We recollect the dropped file in the following line of code

var files = evt.dataTransfer.files;                                                

Then if the user dropped only 1 file, nosotros tin can continue parsing

part handleFiles(files) {         var file = files[0];         if (file.type.lucifer('^image/')) {             $("#files-listing-tabular array").show();             fileToUpload = file;             fileName = file.name;             var reader = new FileReader();              // init the reader result handlers             //reader.onprogress = handleReaderProgress; Commented out equally we don't really demand it             reader.onloadend = handleReaderLoadEnd;              // brainstorm the read operation             reader.readAsDataURL(file);             this.UploadFile(file);         }         else {             var bulletin = "File type not valid. Only images are allowed.";             $("#multiupload-warning-message").html(message);             $("#multiupload-alert").show();         }     }                                                  

What I practice hither is to select the first (and only) element from the files assortment and check that it is of type paradigm (call up that the dropzone aspect doesn't work).
If the type matches, I can instantiate a new object of blazon FileReader().This interface provides such an asynchronous API.
The FileReader object has several events that can be catched (http://world wide web.w3.org/TR/FileAPI/#outcome-handler-attributes-section)

For the purpose of this tutorial I will only handle the loadend event with the following line of code

reader.onloadend = handleReaderLoadEnd;                                                    

FileReader includes 4 options for reading a file, asynchronously:

  • FileReader.readAsBinaryString(Blob|File) – The result holding will comprise the file/blob's data as a binary string. Every byte is represented by an integer in the range [0..255].
  • FileReader.readAsText(Hulk|File, opt_encoding) – The result property volition incorporate the file/blob's data equally a text cord. Past default the string is decoded as 'UTF-8'. Using the optional encoding parameter it is possible to specify a different format.
  • FileReader.readAsDataURL(Blob|File) – The upshot holding volition comprise the file/blob's data encoded as a data URL.
  • FileReader.readAsArrayBuffer(Blob|File) – The effect belongings volition contain the file/hulk'south data equally an ArrayBuffer object.

I'm going to utilise readAsDataURL() so that I can assign the result of the read functioning as the source of the image HTML element I use every bit a preview.

When the read operation is finished, the loadend effect is raised and the following piece of code is executed

office handleReaderLoadEnd(evt) {         var img = document.getElementById("preview");         img.src = evt.target.upshot;         var fileName = fileToUpload.proper name;         if (fileName.length > 20) {             fileName = fileName.substring(0, xx);             fileName = fileName + "...";         }         $("#td-file-name").text(fileName);         var size = fileToUpload.size / 1024;         size = Math.round(size * Math.pow(10, 2)) / Math.prisoner of war(10, 2);         $("#td-file-size").text(size + "Kb");         $("#progressBarBox").prove("fast");     }                                                          

The key here is the result attribute of the FileReader interface.
Having used the readAsDataURL() method, the specification says that
"On getting, if the readAsDataURL read method is used, the event aspect MUST return a DOMString that is a Data URL [DataURL] encoding of the File or Blob'south information."

That's why I'm able to practice the following:

var img = document.getElementById("preview"); img.src = evt.target.effect;                                                              

The rest of the code is to bear witness bones file info to the user into a tabular format.

Function ii: Async file upload and View update

While the file is beingness read into memory, I can asynchrounously upload the droppped file to the server using the following code

// Uploads a file to the server     role UploadFile(file) {         var xhr = new XMLHttpRequest();         xhr.upload.addEventListener("progress", function (evt) {             if (evt.lengthComputable) {                 var percentageUploaded = parseInt(100 - (evt.loaded / evt.total * 100));                 $(".bar").css("width", percentageUploaded + "%");             }         }, false);          // File uploaded         xhr.addEventListener("load", function () {             $(".bar").css("width", "100%");         }, false);          // file received/failed         xhr.onreadystatechange = function (eastward) {             if (xhr.readyState == 4) {                 if (xhr.status == 200) {                     var link = "<a href=\"" + xhr.responseText + "\" target=\"_blank\">" + fileName + "</a>";                     $("#td-file-name").html(link);                 }             }         };          xhr.open("Postal service", "/Dwelling/Upload", truthful);          // Ready advisable headers         xhr.setRequestHeader("Content-Type", "multipart/form-information");         xhr.setRequestHeader("X-File-Name", file.fileName);         xhr.setRequestHeader("X-File-Size", file.fileSize);         xhr.setRequestHeader("X-File-Type", file.blazon);          // Send the file         xhr.transport(file);     }                                                                  

This method is all about the XMLHttpRequest object, which to quote the W3.org spec "allows scripts to perform HTTP client functionality, such every bit submitting course information or loading data from a remote Web site."
I use the XMLHttpRequest to transport the file in async mode to the server, just before I actually kickoff to send the file, I need to subscribe to a few events which will let me to give the user feedback almost the operation progress.

xhr.upload.addEventListener("progress", part (evt) {             if (evt.lengthComputable) {                 var percentageUploaded = parseInt(100 - (evt.loaded / evt.total * 100));                 $(".bar").css("width", percentageUploaded + "%");             } }, false);                                                                    

It is of import not to confuse the XMLHttpRequest.upload progress event with the XMLHttpRequest progress issue, as they serve different purposes.
Having subscribed to this consequence, I'yard able to give feedback to the user about upload operation status.

// File uploaded         xhr.addEventListener("load", function () {             $(".bar").css("width", "100%");         }, false);          // file received/failed         xhr.onreadystatechange = role (e) {             if (xhr.readyState == 4) {                 if (xhr.status == 200) {                     var link = "<a href=\"" + xhr.responseText + "\" target=\"_blank\">" + fileName + "</a>";                     $("#td-file-name").html(link);                 }             }         };                                                                      

Subscribing to the load issue, I'm able to know when all the bytes take been sent.
onreadystatechange is an attribute that represents a part that must be invoked when readyState changes value.
The readyState aspect has 5 possible values

0 Uninitialized
The initial value.
1 Open
The open() method has been successfully called.
2 Sent
The UA successfully completed the asking, but no information has yet been received.
three Receiving
Immediately before receiving the message body (if any). All HTTP headers have been received.
four Loaded
The data transfer has been completed.

I desire to know when the file has been sent and processed by the server

if (xhr.readyState == four) {         if (xhr.status == 200) {                    var link = "<a href=\"" + xhr.responseText + "\" target=\"_blank\">" + fileName + "</a>";                     $("#td-file-proper name").html(link);      } }                                                                          

status lawmaking 200 means Success, then I tin safely read the server response using the responseText belongings.
I'g using this property to concord the virtual path to the uploaded image, so that the user volition be able to call up the link to admission the remote resource past clicking the link.At present that everything is setup, I can open a connexion in async mode (the terminal parameter), specifying which Controller and which Action will handle the POST

xhr.open("POST", "/Domicile/Upload", truthful);                                                                            

and finally I can start sending bytes

xhr.send(file);                                                                              

Now all there is left to do is to write the server side lawmaking that volition receive the file, relieve information technology locally and respond to the client with the virtual path to access the resource.
We simply take a small-scale problem to deal with: how do nosotros call back the posted file when we employ XMLHttpRequest.send() method?
HttpPostedFile in this situation will exist null, as the content is not held into Request.Files object, but instead it is stored into Asking.InputStream as a stream of bytes!

[HttpPost]         public string Upload()         {             UploadedFile file = RetrieveFileFromRequest();             cord savePath = string.Empty;             string virtualPath = SaveFile(file);              return virtualPath;         }          individual UploadedFile RetrieveFileFromRequest()         {             string filename = nada;             string fileType = null;             byte[] fileContents = nothing;              if (Request.Files.Count > 0)             { //we are uploading the old mode                 var file = Request.Files[0];                 fileContents = new byte[file.ContentLength];                 file.InputStream.Read(fileContents, 0, file.ContentLength);                 fileType = file.ContentType;                 filename = file.FileName;             }             else if (Request.ContentLength > 0)             {                 // Using FileAPI the content is in Request.InputStream!!!!                 fileContents = new byte[Request.ContentLength];                 Asking.InputStream.Read(fileContents, 0, Request.ContentLength);                 filename = Request.Headers["10-File-Proper noun"];                 fileType = Request.Headers["10-File-Type"];             }              render new UploadedFile()             {                 Filename = filename,                 ContentType = fileType,                 FileSize = fileContents != null ? fileContents.Length : 0,                 Contents = fileContents             };         }                                                                                

I'1000 non displaying the code for method SaveFile()  and class UploadedFile, you can download the suource lawmaking should you demand it.

And this is the event:

Result

Result

Conclusions

With this mail service I just scratched the surface, at that place's much more about FileAPI and XMLHttpRequest: for example using FileAPI it is possible to pause and resume an upload as it supports file chunking.
A useful jQuery plugin that leverage the full potential of the technologies shown here is available at http://blueimp.github.com/jQuery-File-Upload/