Ajax File & Folder Upload

The functionality described in this article is available in IT Hit WebDAV Ajax Library v5 Beta and later versions.

IT Hit WebDAV Ajax Library provides powerful upload functionality with pause and resume functionality. The library supports folders drag-and-drop and files multi-select functionality. You can upload files and folders in Chrome, FireFox, Edge, IE and Safari on Windows, Max OS X, Linux, iOS and Android.

During upload a developer is provided with almost any possible upload info, such as upload progress, remaining bytes, file size, remaining and elapsed time, error description, etc. You can fully program the look and fill of every detail of your upload UI, the library provides only core Ajax upload functionality and a sample code, leaving the UI implementation to the developer.

Getting Started With Upload

Here are the minimum steps required to create files and folders upload functionality. On your web page you will create Uploader class instance, set settings, assign drop zones (and inputs, if required), set event handlers to be triggered when file or folder is added to the upload queue. Than you will assign progress and item state change event handlers and render user interface.

  1. Create Uploader class instance and set upload URL using Uploader.SetUploadUrl() function. Optionally you can also set upload settings, such as FilesMultiselectUploadFolders, etc:

    var uploader = new ITHit.WebDAV.Client.Upload.Uploader();
    uploader.SetUploadUrl('https://webdavserver/path/');
    uploader.UploadFolders = true;
    uploader.FilesMultiselect = true;
  2. Set one or more drag-and-drop zones and file inputs. They will be used to initiate upload:

    uploader.DropZones.AddById('my-dropzone'); // ID of the HTML element used for drag-and-drop.
    uploader.Inputs.AddById('my-fileinput');   // File input ID.
  3. Assign Queue.OnQueueChanged even handler. This event will be fired when items are added or deleted from upload queue:

    uploader.Queue.AddListener('OnQueueChanged', '_CollectionChange', this);
    
    function _CollectionChange(oQueueChanged) {
        $.each(oQueueChanged.AddedItems, function (index, uploadItem) {
            CreateRow(uploadItem);
            ...
        }.bind(this));
    
        $.each(oQueueChanged.RemovedItems, function (index, uploadItem) {
            ...
        }.bind(this));
    };
  4. Assign UploadItem.OnProgressChanged and UploadItem.OnStateChanged event handlers to each item in the queue. You will typically do this in Uploader.OnQueueChanged event, when a new item is added to the queue: 

    function CreateRow(oUploadItem) {
        oUploadItem.AddListener('OnProgressChanged', '_OnProgress', this);
        oUploadItem.AddListener('OnStateChanged', '_OnStateChange', this);
        _Render(oUploadItem);
    };
    
    function _OnProgress(oProgressEvent) {
        _Render(oProgressEvent.Sender);
    };
    
    function _OnStateChange(oStateChangedEvent) {
        _Render(oStateChangedEvent.Sender);
    };
  5. Inside these event handlers you will get all required information about each item being uploaded and can update user interface. The UploadItem.GetProgress() function provides information about upload progress, the UploadItem.GetState() function provides info about item state:

    function _Render(oUploadItem) {
        var oProgress = oUploadItem.GetProgress();
        var columns = [
            oUploadItem.GetName(),
            oUploadItem.GetUrl(),
            oUploadItem.GetSize(),
            oProgress.UploadedBytes,
            oProgress.Completed,
            oProgress.ElapsedTime,
            oProgress.RemainingTime,
            oProgress.Speed,
            oUploadItem.GetState()
        ];
    ...
    }

Complete Ajax Upload Page Example

Below is a simple example of files and folders upload page implementation:

<!DOCTYPE html>
 <html lang="en">
 <head>
     <title>IT Hit WebDAV Uploader</title>
     <script src="ITHitWebDAVClient.js" type="text/javascript"></script>
     <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
     <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
     <script type="text/javascript">
         function UploaderGridView(sSelector) {
             this.Uploader = new ITHit.WebDAV.Client.Upload.Uploader();
             this.Uploader.DropZones.AddById('ithit-dropzone');
             this.Uploader = oUploader;
             this.Uploader.Queue.AddListener('OnQueueChanged', '_CollectionChange', this);
             this.$table = $(sSelector);
             this.rows = [];
         };

         /**
         * Observes adding and deleting of UploadItem and creates and removes rows in table.
         * @param {ITHit.WebDAV.Client.Upload.Queue#OnQueueChanged} oQueueChanged
         */
         UploaderGridView.prototype._CollectionChange = function(oQueueChanged) {
             $.each(oQueueChanged.AddedItems, function(index, value) {
                 var row = new UploaderGridRow(value);
                 this.rows.push(row);
                 this.$table.append(row.$el);
             }.bind(this));

             $.each(oQueueChanged.RemovedItems, function(index, value) {
                 var aRows = $.grep(this.rows, function(oElem) { return value === oElem.UploadItem; });
                 var iIndex = this.rows.indexOf(aRows[0]);
                 this.rows.splice(iIndex, 1);
                 aRows[0].$el.remove();
             }.bind(this));
         };

         /**
         * Represents table row and subscribes for upload change.
         * @param {ITHit.WebDAV.Client.Upload.oUploadItem} oUploadItem
         */
         function UploaderGridRow(oUploadItem) {
             this.$el = $('');
             this.oUploadItem = oUploadItem;
             this.oUploadItem.AddListener('OnProgressChanged', '_OnProgress', this);
             this.oUploadItem.AddListener('OnStateChanged', '_OnStateChange', this);
             this._Render(oUploadItem);
         };


         /**
         * Creates upload details view.
         * @param {ITHit.WebDAV.Client.Upload.oUploadItem} oUploadItem
         */
         UploaderGridRow.prototype._Render = function(oUploadItem) {
         /** @typedef {ITHit.WebDAV.Client.Upload.Progress} oProgress */
             var oProgress = oUploadItem.GetProgress();
             var columns = [
                 oUploadItem.GetName(),
                 oUploadItem.GetUrl(),
                 oUploadItem.GetSize(),
                 oProgress.UploadedBytes,
                 oProgress.Completed,
                 oProgress.ElapsedTime,
                 oProgress.RemainingTime,
                 oProgress.Speed,
                 oUploadItem.GetState()
             ];

             var $columns = [];
             columns.forEach(function(item) {
                 var $column = $('<td></td>');
                 $column.html(item);
                 $columns.push($column);
             });

             var $actions = $('<td></td>');
             this._RenderActions(oUploadItem).forEach(function(item) {
                 $actions.append(item);
             });

             $columns.push($actions);
             this.$el.empty();
             this.$el.append($columns);
         };

         /**
         * Creates upload actions view.
         * @param {ITHit.WebDAV.Client.Upload.oUploadItem} oUploadItem
         */
         UploaderGridRow.prototype._RenderActions = function(oUploadItem) {
             var actions = [];
             actions.push($('<a></a>').
                 html('<span class="glyphicon glyphicon-play"></span>').
                 attr('href', 'javascript:void(0)').
                 on('click', oUploadItem.Start.bind(oUploadItem)));

             actions.push($('<a></a>').
                 html('<span class="glyphicon glyphicon-stop"></span>').
                 attr('href', 'javascript:void(0)').
                 on('click',oUploadItem.Cancel.bind(oUploadItem)));
         };

         /**
         * Handles UploadItem state change.
         * @param {ITHit.WebDAV.Client.Upload.UploadItem#OnStateChanged} oStateChangedEvent
         */
         UploaderGridRow.prototype._OnStateChange = function(oStateChangedEvent) {
             this._Render(oStateChangedEvent.Sender);
         };

         /**
         * Handles UploadItem progress change.
         * @param ITHit.WebDAV.Client.Upload.UploadItem#OnProgressChanged} oProgressEvent
         */
         UploaderGridRow.prototype._OnProgress = function(oProgressEvent) {
             this._Render(oProgressEvent.Sender);
         };

         var sUploadUrl = 'https://ajaxbrowser.com/User173069d/';
         var oUploaderGrid = new UploaderGridView(oUploader, '.ithit-grid-uploads');
         oUploaderGrid.Uploader.SetUploadUrl(sUploadUrl);
     </script>
 </head>
 <body id="it-hit-dropzone">
     <table class="table table-responsive ithit-grid-uploads">
         <thead>
             <tr>
                 <th>Display Name</th>
                 <th>Download Url</th>
                 <th>Size</th>
                 <th>Uploaded Bytes</th>
                 <th>Completed</th>
                 <th>Elapsed TimeSpan</th>
                 <th>Remaining TimeSpan</th>
                 <th>Speed</th>
                 <th>State</th>
                 <th>Actions</th>
             </tr>
         </thead>
         <tbody>
         </tbody>
     </table>
 </body>
 </html>

Uploader Class

All classes required to implement upload are located in ITHit.WebDAV.Client.Upload namespace with the Uploader class being the starting point of your upload functionality implementation. It provides default upload settings and functions to add drop zones, file inputs and a list of files/folders being uploaded.

To set the URL to which files will be uploaded call Uploader.SetUploadUrl() function. In case of folders upload, new folders will be created under this URL.

Upload Queue and UploadItem Class

The Uploader.Queue property contains list of all files being uploaded. In case of folders upload the upload queue also contains a list of all folders that should be created. Each item in the list is an instance of UploadItem class and represents a file or folder being uploaded. Using UploadItem you can discover item state, get info about upload progress as well as pause, resume and cancel each item upload. 

To get file or folder upload state call UploadItem.GetState() function. It will return State enumeration indicating if the item is being uploaded, paused, canceled, error occurred or is in any other state.

To get item progress call UploadItem.GetProgress() function. It returns a Progress object that contains upload information: number of bytes uploaded, file size, upload percent completed, upload speed, elapsed and remaining time. 

Pausing Upload

To pause upload call UploadItem.PauseAsync() function. This call will break the connection with the server for this file. After that PauseAsync() function requests how many bytes was saved on the server side. Note that typically bytes are being transmitted and saved to your storage in blocks. When connection breaks the last one block will not be saved and as a result, you typically will see that the progress has rolled back slightly. Finally PauseAsync() will call the callback function passed as a parameter.

Resuming Upload

To resume upload call UploadItem.StartAsync() function. This call will request mow many bytes were successfully saved on the server and than restart upload from the next byte.

Canceling Upload

To stop item upload call UploadItem.CancelAsync() function. This will break the connection with the server. Than, if the item did not exist on server before upload started, it will delete the file sending a DELETE request. In many cases breaking connection with the server will not release the file on the server immediately, the server will continue holding the file and the delete operation will fail. That is why the CancelAsync() function will retry deleting a file several times. The maximum number of retries can be passed as a CancelAsync() function parameter.

Finally the CancelAsync() function will send CANCELUPLOAD request. In case the file existed on the server before upload started, this call will signal to the server that the client will not retry upload of this file and any content upload could be deleted. The server can also restore the old file content.