File Upload including Drag and Drop Upload

In this tutorial we will show you how to create a traditional upload form and then move on to creating a drag & drop uploader. All new browsers such as IE10, Firefox, Chrome, Safari, and Opera support the new HTML5 drag and drop upload standard. Drag and drop upload makes it much easier for the user to upload data. It’s also very easy to implement a progress bar for drag and drop upload.

Traditional upload form:

You must first build an HTML form that lets users select a file to upload before you can use server side LSP to manage your uploads. See our HTML Forms for Beginners tutorial if you are new to HTML forms. The following HTML form contains all HTML elements required for a file upload form.

<form enctype="multipart/form-data" method="post">
Choose a file to upload: <input name="thefile" type="file" />
<br/>
<input type="submit" value="Upload File" />
</form>

Example 1: HTML form that lets users select a file to upload.

  • The enctype attribute of the form tag specifies which content-type to use when submitting the form; "multipart/form-data" is used for file uploads.
  • The type="file" attribute of the input tag specifies that the input should be processed as a file.
  • The action tag points to the resource in the server that manages the upload. This has traditionally been sent to a CGI script such as action="/cgi-bin/myupload". However, LSP makes it possible to manage the upload in the same page as the HTML form; thus the action tag is therefore not needed. The browser will send the form data to the same resource in the server when this tag is not present.

After the user clicks submit, the data will be posted (sent back) to the same LSP page that presented the upload form. The following LSP code is all that is required on the server for receiving and saving the received data to a file.

<?lsp
local err
if request:method() == "POST" then
   -- Open a file pointer for the data we will receive.
   local fp
   fp,err=io:open("the-file-name","w")
   if not fp then
      err = "Cannot open file: "..err
   else
      -- filedata: save data chunks received from the client
      local function filedata(data) fp:write(data) end
      request:multipart{
         beginfile=function() end, -- Required, but not used.
         filedata=filedata,
         error=function(e) err=e end
      }
      fp:close() -- Upload complete
      if err then err = "Upload failed: "..err end
   end
end
if err then response:write('<p>',err,'</p>') end
?>

Example 2: LSP example code that shows how to save posted "multipart/form-data" to a file.

The above code executes if the HTTP method is POST. The method request:multipart decodes the multi-part stream received from the browser and calls the callback function ‘filedata’ for each file data chunk that has been decoded. The request:multipart method also requires a "beginfile" function, but we are not using this function in the above example so we have created a dummy function that does nothing. The above code uses the IO interface defined in the web application and the application can for this reason not be deployed, i.e. not be a ZIP file. You can change this code and use any of the Mako Server IO Interfaces. You can also use the standard Lua input/output functions.

Drag and Drop Upload

All modern browsers support the new XMLHttpRequest Level 2 specification which makes it possible to design drag and drop logic in JavaScript.

Drag Over Event:

To visualize that we support drag and drop, we’ll install a “dragover” JavaScript callback. The easiest way to do this is to use JQuery.

var dragover=false
$('body').bind('dragover',function(e) {
    e.preventDefault();
    if(dragover) return;
    dragover=true;
    $('#dropbox').fadeTo(300,.3,function(){
        $('#dropbox').fadeTo(300,1, function() {dragover=false;});});
});

Example 3: Shows how to install a dragover callback using JQuery.

The ‘dragover’ callback function, which we bind to the ‘body’ element, triggers when the user drags a file over the browser window. The ‘dropbox’ element, which we reference on the two last lines in the above code, is for a div element with the drop image to the right. The chained fadeTo functions make the drop box oscillate. You can test this by dragging a file over the image to the right. The image to the right should then start to oscillate by fading in and out. Note, do not drop the file since this page is not designed for accepting ‘drop’ events.

Drop box

The ‘dragover’ callback is not required, but it provides a visual clue to the user that we support drag and drop. You can make the ‘dragover’ callback perform any type of visual clue, and you do no have to use the code we provided above.

Drop Event:

The browser fires a ‘drop’ event when a user drops a file into a browser window. We must install a callback that catches this event and prevents the default browser action.

// The drop event callback i.e. drag and drop
function drop(e) {
     e.preventDefault(); //Prevent the default browser action
    // We accept one file. Additional files are ignored.
    var file=e.originalEvent.dataTransfer.files[0];
    var xhr = new XMLHttpRequest(); //Create the upload object
    //Bind a "progress" callback
    xhr.upload.addEventListener("progress", my_progress);
    //Open connection to origin i.e. to LSP page
    xhr.open("PUT", window.location.href);
    xhr.send(file); // Start the upload
};

$('body').bind('drop',drop); // Bind the drop function to the drop event

Example 4: Shows how to install a drop event callback function.

The above code is designed such that it accepts one dropped file. You can redesign the code and make it accept all files dropped into the browser window. As an example, the Web File Manager used in the How to Create a Cloud Server tutorial accepts all files dropped into the browser window.

The above code creates a XMLHttpRequest object and binds a “progress” event to the object. The code for this callback is not shown in the above code snippet. The complete JavaScript code for the above is provided in the example code presented at the end of this tutorial.

Notice that we use the HTTP PUT method in the above example and not HTTP POST when sending data to the server. The HTTP PUT method was designed specifically for uploading files and the Mako server provides an object that manages HTTP PUT uploads.

<?lsp
local err
if request:method() == "PUT" then
   -- Open a file pointer for the data we will receive.
   local fp
   fp,err=io:open("the-file-name","w")
   if not fp then
      err = "Cannot open file:"..err
   else
      -- filedata: save data chunks received from the client
      local function filedata(data) fp:write(data) end
      for data in request:rawrdr() do filedata(data) end
      fp:close() -- Upload complete
   end
end
?>

Example 5: Shows how to use request:rawrdr() for receiving HTTP PUT data.

The code in example 5 is similar to the code in example 2. The request:rawrdr() method returns a Lua iterator that makes it possible to iterate over the received HTTP PUT data. The data received is not buffered inside the request:rawrdr() method, but is instead returned as chunks as long as data trickles in.

You can easily combine the code in example 2 and 5. The example code, which you can download below, combines the code in example 2 and 5.  This code automatically detects if the data received should be parsed by request:multipart (example 2) or managed by the iterator returned by method request:rawrdr (example 5).

Download Example Code

The example code we provide includes all of the above, but the code is designed such that it only accepts the uploading of ZIP files. The uploaded ZIP file is then mounted by using the ZipIo. The purpose with the example is to show engineers how to use a browser for firmware upgrades; however, most of the example code is related to the topics covered above.

Download BlockingUpload.zip and start the example using the Mako Server as follows:

mako -l::BlockingUpload.zip

Note: you will see the drag and drop uploader and not the HTML upload form shown in example 1 above unless you use an older browser with no support for drag and drop. You can also use a modern browser and disable JavaScript if you want to test the HTML form upload.

Limitations in the above example (blocking v.s. non blocking sockets)

The above example code uses the server’s blocking API functions. This means that each active upload requires an executing thread. The Mako Server can be configured to use a large number of threads, but you will eventually run into limitations if you plan on supporting many concurrent uploads.

The Mako Server also provides a more advanced upload API that uses non blocking sockets. This API should be used if you have requirements where you need to support many concurrent uploads. The Web File Manager, which is introduced in the How to Create a Cloud Server tutorial, uses this API. We also provide a non blocking firmware upload example, which is identical to the code above, except for that the code uses the non blocking API.

Download the non-blocking example AsyncUpload.zip and start the example using the Mako Server as follows:

mako -l::AsyncUpload.zip

Posted in Tutorials by bd