Source: Upload.js

/**
 * A component for uploading files
 * <pre><strong>options:</strong> {
 * url:'',
 * parameters:{}}
 *
 * Events:
 * uploadDidCompleteQueue - when all files are done
 * uploadDidStartQueue - when the upload starts
 * uploadDidComplete(file) - when a single file is successfull
 * uploadDidFail(file) - when a single file fails
 * </pre>
 * @constructor
 */
hui.ui.Upload = function(options) {
  this.options = hui.override({
    url : '',
    parameters : {},
    multiple : false,
    maxSize : "20480",
    types : "*.*",
    fieldName : 'file',
    chooseButton : 'Choose files...'
  },options);
  this.element = hui.get(options.element);
  this.itemContainer = hui.get.firstByClass(this.element,'hui_upload_items');
  this.status = hui.get.firstByClass(this.element,'hui_upload_status');
  this.placeholder = hui.get.firstByClass(this.element,'hui_upload_placeholder');
  this.name = options.name;
  this.items = [];
  this.busy = false;
  this._chooseImplementation();
  hui.ui.extend(this);
  this._addBehavior();
};

hui.ui.Upload.implementations = ['HTML5','Frame','Flash'];

hui.ui.Upload.nameIndex = 0;

/** Creates a new upload widget */
hui.ui.Upload.create = function(options) {
  options = options || {};
  options.element = hui.build('div',{
    'class':'hui_upload',
    html : '<div class="hui_upload_items"></div>'+
    '<div class="hui_upload_status"></div>'+
    (options.placeholder ? '<div class="hui_upload_placeholder"><span class="hui_upload_icon"></span>'+
      (options.placeholder.title ? '<h2>'+hui.string.escape(hui.ui.getTranslated(options.placeholder.title))+'</h2>' : '')+
      (options.placeholder.text ? '<p>'+hui.string.escape(hui.ui.getTranslated(options.placeholder.text))+'</p>' : '')+
    '</div>' : '')
  });
  return new hui.ui.Upload(options);
};

hui.ui.Upload.prototype = {

  /////////////// Public parts /////////////

  /**
   * Change a parameter
   */
  setParameter : function(name,value) {
    this.options.parameters[name] = value;
    if (this.impl.setParameter) {
      this.impl.setParameter(name,value);
    }
  },

  clear : function() {
    for (var i=0; i < this.items.length; i++) {
      if (this.items[i]) {
        this.items[i].remove();
      }
    }
    this.items = [];
    this.itemContainer.style.display='none';
    this.status.style.display='none';
    if (this.placeholder) {
      this.placeholder.style.display='block';
    }
  },
  addDropTarget : function(options) {
    if (options.element) {
      hui.drag.listen({
        element : options.element,
        hoverClass : options.hoverClass,
        $dropFiles : function(files) {
          if (options.$drop) {
            options.$drop();
          }
          this._transferFiles(files);
        }.bind(this)
      });
    }
  },
  uploadFiles : function(files) {
    this._transferFiles(files);
  },

  //////////////// Private parts ////////////////

  _chooseImplementation : function() {
    var impls = hui.ui.Upload.implementations;
    if (this.options.implementation) {
      impls.splice(0,0,this.options.implementation);
    }

    for (var i=0; i < impls.length; i++) {
      var impl = hui.ui.Upload[impls[i]];
      var support = impl.support();
      if (support.supported) {
        if (!this.options.multiple) {
          this.impl = new impl(this);
          hui.log('Selected impl (single): '+impls[i]);
          break;
        } else if (this.options.multiple && support.multiple) {
          this.impl = new impl(this);
          hui.log('Selected impl (multiple): '+impls[i]);
          break;
        }
      }
    }
    if (!this.impl) {
      hui.log('No implementation found, using frame');
      this.impl = new hui.ui.Upload.Frame(this);
    }
  },
  _addBehavior : function() {
    if (!this.impl.initialize) {
      alert(this.impl);
      return;
    }
    hui.ui.onReady(function() {
      this.impl.initialize();
      hui.drag.listen({
        element : this.element,
        hoverClass : 'hui_upload_drop',
        $dropFiles : this._transferFiles.bind(this)
      });
    }.bind(this));
  },

  //////////////////////////// Dropping ///////////////////////

/*  _onDrop : function(e) {
    hui.log('Drop!')
    hui.stop(e);
      hui.log(e)
    if (e.dataTransfer) {
      var files = e.dataTransfer.files;
      if (files && files.length>0) {
        this._transferFiles(files);
      } else {
        hui.log('No files...');
        hui.log(e.dataTransfer.types)
        if (hui.array.contains(e.dataTransfer.types,'image/tiff')) {
          hui.log(e.dataTransfer.getData('image/tiff'))
        }
        hui.log(e.dataTransfer.getData('text/plain'))
        hui.log(e.dataTransfer.getData('text/html'))
        hui.log(e.dataTransfer.getData('url'))
      }
    } else {
      hui.log(e)
    }
  },*/
  _transferFiles : function(files) {
    if (files.length>0) {
      if (!this.options.multiple) {
        this._transferFile(files[0]);
      } else {
        for (var i=0; i < files.length; i++) {
          var file = files[i];
          this._transferFile(file);
        }
      }
    }
  },
  _transferFile : function(file) {
    hui.log(file);
    var item = this.$_addItem({name:file.name,size:file.size});
    hui.request({
      method : 'post',
      file : file,
      url : this.options.url,
      parameters : this.options.parameters,
      $progress : function(current,total) {
        item.updateProgress(current,total);
      },
      $load : function() {
        hui.log('transferFile: load');
      },
      $abort : function() {
        this.$_itemFail(item);
        item.setError('Afbrudt');
      }.bind(this),
      $success : function(t) {
        hui.log('transferFile: success');
        item.data.request = t;
        this.$_itemSuccess(item);
      }.bind(this),
      $failure : function() {
        hui.log('transferFile: fail');
        this.$_itemFail(item);
      }.bind(this)
    });
  },

  /////////////////////// Implementation ///////////////////////////

  /** @private */
  $_addItem : function(info) {
    if (!this.busy) {
      this.fire('uploadDidStartQueue');
      this.status.style.display='block';
      this._setWidgetEnabled(false);
      this.busy = true;
    }
    return this._addItem(info);
  },
  /** @private */
  $_itemSuccess : function(item) {
    var first = hui.get.firstByClass(this.itemContainer,'hui_upload_item_success');
    item.setProgress(1);
    item.setSuccess();
    this.fire('uploadDidComplete',item.getInfo());
    this._checkQueue();
    var move = first !== null || this.items.length>1;
    move = move && !!item.element.nextSibling;

    if (move && (first === null || first != item.element.nextSibling)) {
      var parent = item.element.parentNode;
      var height = item.element.clientHeight;
      hui.animate({node:item.element,css:{height:'0px'},ease:hui.ease.slowFastSlow,duration:500,onComplete:function() {
        parent.removeChild(item.element);
        if (first) {
          parent.insertBefore(item.element,first);
        } else {
          parent.appendChild(item.element);
        }
        hui.animate({node:item.element,css:{height:height+'px'},ease:hui.ease.slowFastSlow,duration:200});
      }});
    }


  },
  /** @private */
  $_itemFail : function(item) {
    item.setError('Upload af filen fejlede!');
    this.fire('uploadDidFail',item.getInfo());
    this._checkQueue();
  },

  /*
  _updateStatus : function() {

    if (this.items.length==0) {
      this.status.style.display='none';
    } else {
      hui.dom.setText(this.status,'Status: '+Math.round(s.successful_uploads/this.items.length*100)+'%');
      this.status.style.display='block';
    }
  },*/

  /** @private */
  $_getButtonContainer : function() {
    var buttonContainer = hui.build('span',{'class':'hui_upload_button'});
    if (this.options.widget) {
      var w = hui.ui.get(this.options.widget);
      w.element.parentNode.insertBefore(buttonContainer,w.element);
      w.element.parentNode.removeChild(w.element);
      buttonContainer.appendChild(w.element);
    } else {
      buttonContainer.innerHTML='<a href="javascript:void(0);" class="hui_button"><span><span>'+hui.string.escape(hui.ui.getTranslated(this.options.chooseButton))+'</span></span></a>';
      this.element.appendChild(buttonContainer);
    }
    return buttonContainer;
  },

  _setWidgetEnabled : function(enabled) {
    if (this.options.widget) {
      var w = hui.ui.get(this.options.widget);
      if (w && w.setEnabled) {
        w.setEnabled(enabled);
      }
    }
  },

  _checkQueue : function() {
    for (var i=0; i < this.items.length; i++) {
      if (!this.items[i].isFinished()) {
        return;
      }
    }
    this.busy = false;
    this._setWidgetEnabled(true);
    this.fire('uploadDidCompleteQueue');
  },


  //////////////////// Events //////////////

  /** @private */
  _addItem : function(file) {
    var index = file.index;
    if (index===undefined) {
      index = this.items.length;
      file.index = index;
    }
    var rearrange = index>4;
    var item = new hui.ui.Upload.Item(file,rearrange);
    this.items[index] = item;
    var first = hui.get.firstByClass(this.itemContainer,'hui_upload_item_success');
    if (first) {
      this.itemContainer.insertBefore(item.element,first);
    } else {
      this.itemContainer.appendChild(item.element);
    }
    this.itemContainer.style.display='block';
    if (this.placeholder) {
      this.placeholder.style.display='none';
    }
    return item;
  }
};




/////////////////// Item ///////////////////

/**
 * @class
 * @constructor
 */
hui.ui.Upload.Item = function(info,rearrange) {
  this.data = info;
  this.rearrange = rearrange;
  this.element = hui.build('div',{className:'hui_upload_item'});
  this.element.appendChild(hui.ui.createIcon('file/generic',32));
  this.content = hui.build('div',{className:'hui_upload_item_content',parent:this.element});
  this.progress = hui.ui.ProgressBar.create({small:true});
  this.content.appendChild(this.progress.getElement());
  var text = hui.build('p',{parent:this.content});
  this.info = hui.build('strong',{parent:text});
  this.status = hui.build('em',{parent:text});
  if (info.name) {
    hui.dom.setText(this.info,info.name);
  }
  this.finished = false;
  this.error = false;
};

hui.ui.Upload.Item.prototype = {
  getInfo : function() {
    return this.data;
  },
  isFinished : function() {
    return this.finished;
  },
  setError : function(error) {
    this._setStatus(error || hui.ui.getTranslated({en:'Error',da:'Fejl'}));
    hui.cls.add(this.element,'hui_upload_item_error');
    this.progress.hide();
    this.progress.setValue(0);
    this.finished = true;
  },
  setSuccess : function(status) {
    this._setStatus(hui.ui.getTranslated({en:'Complete',da:'Færdig'}));
    this.progress.setValue(1);
    this.finished = true;
    hui.cls.add(this.element,'hui_upload_item_success');
  },
  updateProgress : function(complete,total) {
    this.setProgress(complete/total);
    return this;
  },
  setProgress : function(value) {
    this._setStatus(hui.ui.getTranslated({en:'Transfering',da:'Overfører'}));
    this.progress.setValue(Math.min(0.9999,value));
    return this;
  },
  setWaiting : function() {
    this._setStatus('Venter');
    this.progress.setWaiting();
    return this;
  },
  hide : function() {
    this.element.hide();
  },
  remove : function() {
    hui.dom.remove(this.element);
  },
  _setStatus : function(text) {
    if (this._status!==text) {
      hui.dom.setText(this.status,text);
      this._status = text;
    }
  }
};

//// Util ////

hui.ui.Upload._nameIndex = 0;

hui.ui.Upload._buildForm = function(widget) {
  var options = widget.options;

  hui.ui.Upload._nameIndex++;
  var frameName = 'hui_upload_'+hui.ui.Upload._nameIndex;
    hui.log('Frame: name='+frameName);

  var form = hui.build('form');
  form.setAttribute('action',options.url || '');
  form.setAttribute('method','post');
  form.setAttribute('enctype','multipart/form-data');
  form.setAttribute('encoding','multipart/form-data');
  form.setAttribute('target',frameName);
  if (options.parameters) {
    for (var key in options.parameters) {
      var hidden = hui.build('input',{'type':'hidden','name':key});
      hidden.value = options.parameters[key];
      form.appendChild(hidden);
    }
  }
  return form;
};











/////////////////////// Frame //////////////////////////

/**
 * @class
 * @constructor
 */
hui.ui.Upload.Frame = function(parent) {
  this.parent = parent;
};

hui.ui.Upload.Frame.support = function() {
  return {supported:true,multiple:false};
};

hui.ui.Upload.Frame.prototype = {

  initialize : function() {
    var options = this.parent.options;

    var form = this.form = hui.ui.Upload._buildForm(this.parent);
    var frameName = form.getAttribute('target');

    var iframe = this.iframe = hui.build('iframe', {
      name : frameName,
      id : frameName,
      src : hui.ui.getURL('html/blank.html'),
      style : 'display:none'
    });
    this.parent.element.appendChild(iframe);
    var self = this;
    hui.listen(iframe,'load',function() {
      self._uploadComplete();
    });

    this.fileInput = hui.build('input',{'type':'file','name':options.fieldName});
    hui.listen(this.fileInput,'change',this._onSubmit.bind(this));

    form.appendChild(this.fileInput);
    var span = hui.build('span',{'class':'hui_upload_button_input'});
    span.appendChild(form);
    var c = this.parent.$_getButtonContainer();
    c.insertBefore(span,c.firstChild);
  },
  setParameter : function(name,value) {
    var existing = this.form.getElementsByTagName('input');
    for (var i=0; i < existing.length; i++) {
      if (existing[i].name==name) {
        existing[i].value = value;
        return;
      }
    }
    hui.build('input',{'type':'hidden','name':name,'value':value,parent:this.form});
  },

  _rebuildParameters : function() {
    // IE: set value of parms again since they disappear
    if (hui.browser.msie) {
      hui.each(this.parent.options.parameters,function(key,value) {
        this.form[key].value = value;
      }.bind(this));
    }
  },
  _rebuildFileInput : function() {
    var options = this.parent.options;
    var old = this.fileInput;
    this.fileInput = hui.build('input',{'type':'file','name':options.fieldName});
    hui.listen(this.fileInput,'change',this._onSubmit.bind(this));
    hui.dom.replaceNode(old,this.fileInput);
    hui.log('Frame: input replaced');
  },
  _getFileName : function() {
    return this.fileInput.value.split('\\').pop();
  },
  _onSubmit : function() {
    this.form.style.display='none';
    this.uploading = true;
    this._rebuildParameters();
    this.form.submit();
    this.item = this.parent.$_addItem({name:this._getFileName()});
    this.item.setWaiting();
    this._rebuildFileInput();
    hui.log('Frame: Upload started:'+this.uploading);
  },

  _uploadComplete : function() {
        hui.log('complete:'+this.uploading+' / '+this.parent.name);
    if (!this.uploading) {
      return;
    }
    this.uploading = false;
    var success = this._isSuccessResponse();
    hui.log('Frame: Upload complete: success='+success);
    var item = this.item;
    if (item) {
      if (success) {
        this.parent.$_itemSuccess(item);
        hui.log('Frame: Upload succeeded');
      } else {
        this.parent.$_itemFail(item);
        hui.log('Frame: Upload failed!');
      }
    }
    this.iframe.src = hui.ui.getURL('html/blank.html');
    this.form.style.display = 'block';
    this.form.reset();
  },
  _isSuccessResponse : function() {
    var doc = hui.frame.getDocument(this.iframe);
    return doc.body.innerHTML.indexOf('SUCCESS')!==-1;
  }
};

//////////////////// HTML5 //////////////////////


/**
 * @class
 * @constructor
 */
hui.ui.Upload.HTML5 = function(parent) {
  this.parent = parent;
};

hui.ui.Upload.HTML5.support = function() {
  var supported = window.File!==undefined && (hui.browser.webkit || hui.browser.gecko || hui.browser.msie10 || hui.browser.msie11);//(window.File!==undefined && window.FileReader!==undefined && window.FileList!==undefined && window.Blob!==undefined);
  hui.log('HTML5: supported='+supported);
  //supported = !true;
  return {
    supported : supported,
    multiple : true
  };
};

hui.ui.Upload.HTML5.prototype = {
  initialize : function() {
    var options = this.parent.options;
    var span = hui.build('span',{'class':'hui_upload_button_input'});
        this.form = hui.build('form',{'style':'display: inline-block; margin:0;',parent:span});
    var ps = {'type':'file','name':options.fieldName,parent:this.form};
    if (options.multiple) {
      ps.multiple = 'multiple';
    }
    this.fileInput = hui.build('input',ps);
    var c = this.parent.$_getButtonContainer();
    c.insertBefore(span,c.firstChild);
    hui.listen(this.fileInput,'change',this._submit.bind(this));
  },
  _submit : function(e) {
    var files = this.fileInput.files;
    this.parent._transferFiles(files);
        // TODO: reset/replace input field in IE
        this._resetInput();
  },
  _resetInput : function() {
    this.form.reset();
  }
};