/** * 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(); } };