Skip to content
Snippets Groups Projects
filedrop.field.js 24.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • !function($) {
      "use strict";
    
      var FileDropbox = function(element, options) {
        this.$element = $(element);
        this.uploads = [];
    
        var events = {
          uploadStarted: $.proxy(this.uploadStarted, this),
          uploadFinished: $.proxy(this.uploadFinished, this),
          progressUpdated: $.proxy(this.progressUpdated, this),
          speedUpdated: $.proxy(this.speedUpdated, this),
          dragOver: $.proxy(this.dragOver, this),
    
          dragLeave: $.proxy(this.dragLeave, this),
    
          beforeSend: $.proxy(this.beforeSend, this),
    
          beforeEach: $.proxy(this.beforeEach, this),
    
          error: $.proxy(this.handleError, this),
    
          afterAll: $.proxy(this.afterAll, this)
    
        };
    
        this.options = $.extend({}, $.fn.filedropbox.defaults, events, options);
        this.$element.filedrop(this.options);
    
        if (this.options.shim) {
          $('input[type=file]', this.$element).attr('name', this.options.name)
              .addClass('shim').css('display', 'inline-block').show();
          $('a.manual', this.$element).hide();
        }
    
        (this.options.files || []).forEach($.proxy(this.addNode, this));
      };
    
      FileDropbox.prototype = {
    
        dragLeave: function(e) {
            this.$element.removeAttr('style');
        },
    
        drop: function(e) {
            this.$element.removeAttr('style');
        },
        dragOver: function(box, e) {
            this.$element.css('background-color', 'rgba(0, 0, 0, 0.3)');
        },
    
        beforeEach: function (file) {
    
          if (this.options.maxfiles && this.uploads.length >= this.options.maxfiles) {
              // This file is not allowed to be added to the list. It's over the
              // limit
              this.handleError('TooManyFiles', file);
              return false;
          }
    
          var node = this.addNode(file).data('file', file);
          node.find('.progress').show();
    
          node.find('.progress-bar').width('100%').addClass('progress-bar-striped active');
    
          node.find('.trash').hide();
    
        },
        beforeSend: function (file, i, reader) {
    
          var URL = window.webkitURL || window.URL;
    
          this.uploads.some(function(e) {
            if (e.data('file') == file) {
              if (file.type.indexOf('image/') === 0 && file.size < 1e6) {
    
                var img = e.find('.preview')
                  .tooltip({items:'img',
    
                    tooltipClass: 'tooltip-preview',
    
                    content:function(){ return $(this).clone().wrap('<div>'); }}
                  )
                  .get()[0];
                  img.src = URL.createObjectURL(file);
    
        speedUpdated: function(i, file, speed) {
          var that = this;
          this.uploads.some(function(e) {
            if (e.data('file') == file) {
              e.find('.upload-rate').text(that.fileSize(speed * 1024)+'/s');
              return true;
            }
          });
        },
        progressUpdated: function(i, file, value) {
          this.uploads.some(function(e) {
            if (e.data('file') == file) {
              e.find('.progress-bar')
                .attr({'aria-valuenow': value})
    
                .width(value + '%');
              if (value == 100)
    
                e.find('.progress-bar').addClass('progress-bar-striped active');
    
              return true;
            }
          });
        },
    
        uploadStarted: function(i, file, n, xhr) {
    
          var that = this;
          this.uploads.some(function(e) {
            if (e.data('file') == file) {
              e.data('xhr', xhr);
              e.find('.cancel').show();
    
              that.lockSubmit(1);
    
              that.progressUpdated(i, file, 0);
              setTimeout(function() {
                e.find('.progress-bar')
                 .removeClass('progress-bar-striped active');
              }, 50);
    
        uploadFinished: function(i, file, json, time, xhr) {
    
          var that = this;
          this.uploads.some(function(e) {
            if (e.data('file') == file) {
    
              if (!json || !json.id)
    
                // Upload failed. TODO: Add a button to the UI to retry on
                // HTTP 500
    
              e.find('[name="'+that.options.name+'"]').val(''+json.id+','+file.name);
    
              e.data('fileId', json.id);
    
              e.find('.progress-bar')
                .width('100%')
    
                .attr({'aria-valuenow': 100});
    
              e.find('.trash').show();
              e.find('.upload-rate').hide();
    
              e.find('.cancel').hide();
    
              setTimeout(function() { e.find('.progress').hide(); }, 600);
              return true;
            }
          });
        },
        fileSize: function(size) {
          var sizes = ['k','M','G','T'],
              suffix = '';
          while (size > 900) {
            size /= 1024;
            suffix = sizes.shift();
          }
    
          return (suffix ? size.toPrecision(3) + suffix : size) + 'B';
    
        findNode: function(file) {
          var nodes = $.grep(this.uploads, function(e) {
            return e.data('file') == file;
          });
          return nodes ? nodes[0] : null;
        },
    
        addNode: function(file) {
    
          // Check if the file is already in the list of files for this dropbox
          var already_added = false;
          this.uploads.some(function(e) {
            if (file.id && e.data('fileId') == file.id) {
              already_added = true;
              return true;
            }
          });
          if (already_added)
              return;
    
    
          var filenode = $('<div class="file"></div>');
          filenode
    
              .append($('<div class="filetype"></div>').addClass())
    
              .append($('<img class="preview" />'))
    
              .append($('<span class="filename ltr"></div>')
    
                .append($('<span class="filesize"></span>').text(
    
                  this.fileSize(parseInt(file.size))
    
                ))
                .append($('<div class="pull-right cancel"></div>')
                  .append($('<i class="icon-remove"></i>')
                    .attr('title', __('Cancel'))
                  )
                  .click($.proxy(this.cancelUpload, this, filenode))
                  .hide()
                )
                .append($('<div class="upload-rate pull-right"></div>'))
    
              ).append($('<div class="progress"></div>')
                .append($('<div class="progress-bar"></div>'))
                .attr({'aria-valuemin':0,'aria-valuemax':100})
                .hide())
              .append($('<input type="hidden"/>').attr('name', this.options.name)
    
          if (this.options.deletable) {
            filenode.prepend($('<span><i class="icon-trash"></i></span>')
              .addClass('trash pull-right')
              .click($.proxy(this.deleteNode, this, filenode))
            );
          }
    
          if (file.id)
            filenode.data('fileId', file.id);
    
          if (file.download_url) {
    
            filenode.find('.filename').prepend(
              $('<a class="no-pjax" target="_blank"></a>').text(file.name)
    
                .attr('href', file.download_url)
    
            filenode.find('.filename').prepend($('<span>').text(file.name));
    
          this.$element.parent().find('.files').append(filenode);
    
          this.uploads.push(filenode);
    
          return filenode;
        },
        deleteNode: function(filenode, e) {
    
          if (!e || confirm(__('You sure?'))) {
            var i = this.uploads.indexOf(filenode);
            if (i !== -1)
                this.uploads.splice(i,1);
    
            filenode.slideUp('fast', function() { $(this).remove(); });
    
        },
        cancelUpload: function(node) {
          if (node.data('xhr')) {
            node.data('xhr').abort();
    
            var img = node.find('.preview').get()[0];
            if (img) (window.webkitURL || window.URL).revokeObjectURL(img.src);
    
          return this.deleteNode(node, false);
    
        handleError: function(err, file, i, status) {
          var message = $.fn.filedropbox.messages[err],
              filenode = this.findNode(file);
    
          if (file instanceof File) {
    
            message = '<b>' + file.name + '</b><br/>' + message + '<br/>' + status;
    
          }
          $.sysAlert(__('File Upload Error'), message);
    
          if (filenode) this.cancelUpload(filenode);
    
        afterAll: function() {
    
          var submit = this.$element.closest('form').find('input[type=submit]'),
              $submit = $(submit);
    
          if ($submit.data('original')) {
    
            $submit.val($submit.data('original')).prop('disabled', false);
          }
    
        },
        lockSubmit: function() {
          var submit = this.$element.closest('form').find('input[type=submit]'),
              $submit = $(submit);
          if (!$submit.data('original')) {
            $submit.data('original', $submit.val());
          }
          $submit.val(__('Uploading ...')).prop('disabled', true);
    
        }
      };
    
      $.fn.filedropbox = function ( option ) {
        return this.each(function () {
          var $this = $(this),
            data = $this.data('dropbox'),
            options = typeof option == 'object' && option;
          if (!data) $this.data('dropbox', (data = new FileDropbox(this, options)));
          if (typeof option == 'string') data[option]();
        });
      };
    
      $.fn.filedropbox.defaults = {
        files: [],
    
        deletable: true,
    
        shim: !window.FileReader,
    
        queuefiles: 1
    
      $.fn.filedropbox.messages = {
        'BrowserNotSupported': __('Your browser is not supported'),
        'TooManyFiles': __('You are trying to upload too many files'),
        'FileTooLarge': __('File is too large'),
        'FileTypeNotAllowed': __('This type of file is not allowed'),
        'FileExtensionNotAllowed': __('This type of file is not allowed'),
        'NotFound': __('Could not find or read this file'),
        'NotReadable': __('Could not find or read this file'),
        'AbortError': __('Could not find or read this file')
      };
    
    
      $.fn.filedropbox.Constructor = FileDropbox;
    
    }(jQuery);
    
    /*
     * Default text - jQuery plugin for html5 dragging files from desktop to browser
     *
     * Author: Weixi Yen
     *
     * Email: [Firstname][Lastname]@gmail.com
     *
     * Copyright (c) 2010 Resopollution
     *
     * Licensed under the MIT license:
     *   http://www.opensource.org/licenses/mit-license.php
     *
     * Project home:
     *   http://www.github.com/weixiyen/jquery-filedrop
     *
     * Version:  0.1.0
     *
     * Features:
     *      Allows sending of extra parameters with file.
     *      Works with Firefox 3.6+
     *      Future-compliant with HTML5 spec (will work with Webkit browsers and IE9)
     * Usage:
     *  See README at project homepage
     *
     */
    ;(function($) {
    
      var default_opts = {
          fallback_id: '',
          link: false,
          url: '',
          refresh: 1000,
          paramname: 'userfile',
          requestType: 'POST',    // just in case you want to use another HTTP verb
          allowedfileextensions:[],
          allowedfiletypes:[],
          maxfiles: 25,           // Ignored if queuefiles is set > 0
          maxfilesize: 1,         // MB file size limit
          queuefiles: 0,          // Max files before queueing (for large volume uploads)
          queuewait: 200,         // Queue wait time if full
          data: {},
          headers: {},
          drop: empty,
          dragStart: empty,
          dragEnter: empty,
          dragOver: empty,
          dragLeave: empty,
          docEnter: empty,
          docOver: empty,
          docLeave: empty,
          beforeEach: empty,
          afterAll: empty,
          rename: empty,
          error: function(err, file, i, status) {
            alert(err);
          },
          uploadStarted: empty,
          uploadFinished: empty,
          progressUpdated: empty,
          globalProgressUpdated: empty,
          speedUpdated: empty
          },
    
          errors = ["BrowserNotSupported", "TooManyFiles", "FileTooLarge", "FileTypeNotAllowed", "NotFound", "NotReadable", "AbortError", "ReadError", "FileExtensionNotAllowed"],
          Blob = window.WebKitBlob || window.MozBlob || window.Blob;
    
    
      $.fn.filedrop = function(options) {
        var opts = $.extend({}, default_opts, options),
            global_progress = [],
            doc_leave_timer, stop_loop = false,
            files_count = 0,
            files;
    
    
        if (window.FileReader)
          $('#' + opts.fallback_id).css({
            display: 'none',
            width: 0,
            height: 0
          });
    
    
        this.on('drop', drop).on('dragstart', opts.dragStart).on('dragenter', dragEnter).on('dragover', dragOver).on('dragleave', dragLeave);
        $(document).on('drop', docDrop).on('dragenter', docEnter).on('dragover', docOver).on('dragleave', docLeave);
    
    
        (opts.link || this).click(function(e) {
          $('#' + opts.fallback_id).trigger('click');
    
        });
    
        $('#' + opts.fallback_id).change(function(e) {
          opts.drop(e);
          files = e.target.files;
          files_count = files.length;
          upload();
        });
    
        function drop(e) {
          if( opts.drop.call(this, e) === false ) return false;
    
          if(!e.originalEvent.dataTransfer)
    
          files = e.originalEvent.dataTransfer.files;
    
          if (files === null || files === undefined || files.length === 0) {
            opts.error(errors[0]);
            return false;
          }
          files_count = files.length;
          upload();
          e.preventDefault();
          return false;
        }
    
        function getBuilder(filename, filedata, mime, boundary) {
          var dashdash = '--',
              crlf = '\r\n',
    
              paramname = opts.paramname;
    
    
          if (opts.data) {
            var params = $.param(opts.data).replace(/\+/g, '%20').split(/&/);
    
            $.each(params, function() {
              var pair = this.split("=", 2),
                  name = decodeURIComponent(pair[0]),
                  val  = decodeURIComponent(pair[1]);
    
              if (pair.length !== 2) {
                  return;
              }
    
    
              builder.push(dashdash
                  + boundary
                  + crlf
                  + 'Content-Disposition: form-data; name="' + name + '"'
                  + crlf
                  + crlf
                  + val
                  + crlf);
    
            });
          }
    
          if (jQuery.isFunction(paramname)){
            paramname = paramname(filename);
          }
    
    
          builder.push(dashdash
              + boundary
              + crlf
              + 'Content-Disposition: form-data; name="' + (paramname||"") + '"'
              + '; filename="' + encodeURIComponent(filename) + '"'
              + crlf
    
              + 'Content-Type: ' + mime
              + crlf
              + crlf);
    
          builder.push(filedata);
          builder.push(crlf
              + dashdash
              + boundary
              + dashdash
              + crlf);
          return new Blob(builder);
    
        }
    
        function progress(e) {
          if (e.lengthComputable) {
    
            var percentage = ((e.loaded * 100) / e.total).toFixed(1);
            if (this.currentProgress != percentage) {
    
    
              this.currentProgress = percentage;
              opts.progressUpdated(this.index, this.file, this.currentProgress);
    
              global_progress[this.global_progress_index] = this.currentProgress;
              globalProgress();
    
              var elapsed = new Date().getTime();
              var diffTime = elapsed - this.currentStart;
              if (diffTime >= opts.refresh) {
                var diffData = e.loaded - this.startData;
                var speed = diffData / diffTime; // KB per second
                opts.speedUpdated(this.index, this.file, speed);
                this.startData = e.loaded;
                this.currentStart = elapsed;
              }
            }
          }
        }
    
        function globalProgress() {
          if (global_progress.length === 0) {
            return;
          }
    
          var total = 0, index;
          for (index in global_progress) {
            if(global_progress.hasOwnProperty(index)) {
              total = total + global_progress[index];
            }
          }
    
          opts.globalProgressUpdated(Math.round(total / global_progress.length));
        }
    
        // Respond to an upload
        function upload() {
          stop_loop = false;
    
          if (!files) {
            opts.error(errors[0]);
            return false;
          }
    
          if (typeof Blob === "undefined") {
            opts.error(errors[0]);
            return false;
          }
    
    
          if (opts.allowedfiletypes.push && opts.allowedfiletypes.length) {
            for(var fileIndex = files.length;fileIndex--;) {
              if(!files[fileIndex].type || $.inArray(files[fileIndex].type, opts.allowedfiletypes) < 0) {
    
                opts.error(errors[3], files[fileIndex], fileIndex);
    
                return false;
              }
            }
          }
    
          if (opts.allowedfileextensions.push && opts.allowedfileextensions.length) {
            for(var fileIndex = files.length;fileIndex--;) {
              var allowedextension = false;
              for (i=0;i<opts.allowedfileextensions.length;i++){
                if (files[fileIndex].name.substr(files[fileIndex].name.length-opts.allowedfileextensions[i].length).toLowerCase()
                        == opts.allowedfileextensions[i].toLowerCase()
                ) {
                  allowedextension = true;
                }
              }
              if (!allowedextension){
    
                opts.error(errors[8], files[fileIndex], fileIndex);
    
                return false;
              }
            }
          }
    
          var filesDone = 0,
              filesRejected = 0;
    
          if (files_count > opts.maxfiles && opts.queuefiles === 0) {
            opts.error(errors[1]);
            return false;
          }
    
          // Define queues to manage upload process
          var workQueue = [];
          var processingQueue = [];
          var doneQueue = [];
    
          // Add everything to the workQueue
          for (var i = 0; i < files_count; i++) {
            workQueue.push(i);
          }
    
          // Helper function to enable pause of processing to wait
          // for in process queue to complete
          var pause = function(timeout) {
            setTimeout(process, timeout);
            return;
          };
    
          // Process an upload, recursive
          var process = function() {
    
            var fileIndex;
    
            if (stop_loop) {
              return false;
            }
    
            // Check to see if are in queue mode
            if (opts.queuefiles > 0 && processingQueue.length >= opts.queuefiles) {
              return pause(opts.queuewait);
            } else {
              // Take first thing off work queue
              fileIndex = workQueue[0];
              workQueue.splice(0, 1);
    
              // Add to processing queue
              processingQueue.push(fileIndex);
            }
    
            try {
              if (beforeEach(files[fileIndex]) !== false) {
                if (fileIndex === files_count) {
                  return;
                }
    
                var reader = new window.FileReader(),
    
                    max_file_size = 1048576 * opts.maxfilesize;
    
                reader.index = fileIndex;
                if (files[fileIndex].size > max_file_size) {
                  opts.error(errors[2], files[fileIndex], fileIndex);
                  // Remove from queue
                  processingQueue.forEach(function(value, key) {
                    if (value === fileIndex) {
                      processingQueue.splice(key, 1);
                    }
                  });
                  filesRejected++;
                  return true;
                }
    
                reader.onerror = function(e) {
                    switch(e.target.error.code) {
                        case e.target.error.NOT_FOUND_ERR:
    
                            opts.error(errors[4], files[fileIndex], fileIndex);
    
                            return false;
                        case e.target.error.NOT_READABLE_ERR:
    
                            opts.error(errors[5], files[fileIndex], fileIndex);
    
                            return false;
                        case e.target.error.ABORT_ERR:
    
                            opts.error(errors[6], files[fileIndex], fileIndex);
    
                            return false;
                        default:
    
                            opts.error(errors[7], files[fileIndex], fileIndex);
    
                reader.onloadend = function(e) {
                  if (!opts.beforeSend
                      || false !== opts.beforeSend(files[fileIndex], fileIndex, e.target))
                    return send(e);
    
                reader.readAsArrayBuffer(files[fileIndex]);
    
    
              } else {
                filesRejected++;
              }
            } catch (err) {
              // Remove from queue
              processingQueue.forEach(function(value, key) {
                if (value === fileIndex) {
                  processingQueue.splice(key, 1);
                }
              });
    
              opts.error(errors[0], files[fileIndex], fileIndex, err);
    
              return false;
            }
    
            // If we still have work to do,
            if (workQueue.length > 0) {
              process();
            }
          };
    
          var send = function(e) {
    
            var fileIndex = (e.srcElement || e.target).index;
    
            // Sometimes the index is not attached to the
            // event object. Find it by size. Hack for sure.
            if (e.target.index === undefined) {
              e.target.index = getIndexBySize(e.total);
            }
    
            var xhr = new XMLHttpRequest(),
                upload = xhr.upload,
                file = files[e.target.index],
                index = e.target.index,
                start_time = new Date().getTime(),
                boundary = '------multipartformboundary' + (new Date()).getTime(),
                global_progress_index = global_progress.length,
                builder,
                newName = rename(file.name),
                mime = file.type;
    
            if (opts.withCredentials) {
              xhr.withCredentials = opts.withCredentials;
            }
    
    
            var data = e.target.result;
    
            if (typeof newName === "string") {
              builder = getBuilder(newName, data, mime, boundary);
            } else {
              builder = getBuilder(file.name, data, mime, boundary);
            }
    
            upload.index = index;
            upload.file = file;
            upload.downloadStartTime = start_time;
            upload.currentStart = start_time;
            upload.currentProgress = 0;
            upload.global_progress_index = global_progress_index;
            upload.startData = 0;
            upload.addEventListener("progress", progress, false);
    
            // Allow url to be a method
            if (jQuery.isFunction(opts.url)) {
                xhr.open(opts.requestType, opts.url(), true);
            } else {
                xhr.open(opts.requestType, opts.url, true);
            }
    
            xhr.setRequestHeader('content-type', 'multipart/form-data; boundary=' + boundary);
            xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
    
            // Add headers
            $.each(opts.headers, function(k, v) {
              xhr.setRequestHeader(k, v);
            });
    
    
            xhr.send(builder);
    
    
            global_progress[global_progress_index] = 0;
            globalProgress();
    
    
            opts.uploadStarted(index, file, files_count, xhr);
    
            var afterComplete = function(result) {
    
              filesDone++;
    
              // Remove from processing queue
              processingQueue.forEach(function(value, key) {
                if (value === fileIndex) {
                  processingQueue.splice(key, 1);
                }
              });
    
              // Add to donequeue
              doneQueue.push(fileIndex);
    
              // Make sure the global progress is updated
              global_progress[global_progress_index] = 100;
              globalProgress();
    
              if (filesDone === (files_count - filesRejected)) {
                afterAll();
              }
              if (result === false) {
                stop_loop = true;
              }
            };
    
            xhr.onabort = afterComplete;
    
            xhr.onload = function() {
                var serverResponse = null;
    
                if (xhr.responseText) {
                  try {
                    serverResponse = jQuery.parseJSON(xhr.responseText);
                  }
                  catch (e) {
                    serverResponse = xhr.responseText;
                  }
                }
    
                var now = new Date().getTime(),
                    timeDiff = now - start_time,
                    result = opts.uploadFinished(index, file, serverResponse, timeDiff, xhr);
    
    
                afterComplete(result);
    
    
              // Pass any errors to the error option
              if (xhr.status < 200 || xhr.status > 299) {
                opts.error(xhr.statusText, file, fileIndex, xhr.status);
              }
            };
          };
    
          // Initiate the processing loop
          process();
        }
    
        function getIndexBySize(size) {
          for (var i = 0; i < files_count; i++) {
            if (files[i].size === size) {
              return i;
            }
          }
    
          return undefined;
        }
    
        function rename(name) {
          return opts.rename(name);
        }
    
        function beforeEach(file) {
          return opts.beforeEach(file);
        }
    
        function afterAll() {
          return opts.afterAll();
        }
    
        function dragEnter(e) {
          clearTimeout(doc_leave_timer);
          e.preventDefault();
          opts.dragEnter.call(this, e);
        }
    
        function dragOver(e) {
          clearTimeout(doc_leave_timer);
          e.preventDefault();
          opts.docOver.call(this, e);
          opts.dragOver.call(this, e);
        }
    
        function dragLeave(e) {
          clearTimeout(doc_leave_timer);
          opts.dragLeave.call(this, e);
          e.stopPropagation();
        }
    
        function docDrop(e) {
          e.preventDefault();
          opts.docLeave.call(this, e);
          return false;
        }
    
        function docEnter(e) {
          clearTimeout(doc_leave_timer);
          e.preventDefault();
          opts.docEnter.call(this, e);
          return false;
        }
    
        function docOver(e) {
          clearTimeout(doc_leave_timer);
          e.preventDefault();
          opts.docOver.call(this, e);
          return false;
        }
    
        function docLeave(e) {
          doc_leave_timer = setTimeout((function(_this) {
            return function() {
              opts.docLeave.call(_this, e);
            };
          })(this), 200);
        }
    
        return this;
      };
    
      function empty() {}
    
    })(jQuery);