/** * EGroupware eTemplate2 - JS Number object * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @package etemplate * @subpackage api * @link http://www.egroupware.org * @author Nathan Gray * @copyright Nathan Gray 2011 * @version $Id$ */ "use strict"; /*egw:uses et2_core_inputWidget; phpgwapi.Resumable.resumable; */ /** * Class which implements file upload * * @augments et2_inputWidget */ var et2_file = et2_inputWidget.extend( { attributes: { "multiple": { "name": "Multiple files", "type": "boolean", "default": false, "description": "Allow the user to select more than one file to upload at a time. Subject to browser support." }, "max_file_size": { "name": "Maximum file size", "type": "integer", "default":0, "description": "Largest file accepted, in bytes. Subject to server limitations. 8MB = 8388608" }, "mime": { "name": "Allowed file types", "type": "string", "default": et2_no_init, "description": "Mime type (eg: image/png) or regex (eg: /^text\//i) for allowed file types" }, "blur": { "name": "Placeholder", "type": "string", "default": "", "description": "This text get displayed if an input-field is empty and does not have the input-focus (blur). It can be used to show a default value or a kind of help-text." }, "progress": { "name": "Progress node", "type": "string", "default": et2_no_init, "description": "The ID of an alternate node (div) to display progress and results. The Node is fetched with et2 getWidgetById so you MUST use the id assigned in XET-File (it may not be available at creation time, so we (re)check on createStatus time)" }, "onStart": { "name": "Start event handler", "type": "any", "default": et2_no_init, "description": "A (js) function called when an upload starts. Return true to continue with upload, false to cancel." }, "onFinish": { "name": "Finish event handler", "type": "any", "default": et2_no_init, "description": "A (js) function called when all files to be uploaded are finished." }, drop_target: { "name": "Optional, additional drop target for HTML5 uploads", "type": "string", "default": et2_no_init, "description": "The ID of an additional drop target for HTML5 drag-n-drop file uploads" }, label: { "name": "Label of file upload", "type": "string", "default": "Choose file...", "description": "String caption to be displayed on file upload span" }, progress_dropdownlist: { "name": "List on files in progress like dropdown", "type": "boolean", "default": false, "description": "Style list of files in uploading progress like dropdown list with a total upload progress indicator" }, onFinishOne: { "name": "Finish event handler for each one", "type": "any", "default": et2_no_init, "description": "A (js) function called when a file to be uploaded is finished." } }, asyncOptions: {}, /** * Constructor * * @memberOf et2_file */ init: function() { this._super.apply(this, arguments); this.node = null; this.input = null; this.progress = null; this.span = null; if(!this.options.id) { console.warn("File widget needs an ID. Used 'file_widget'."); this.options.id = "file_widget"; } // Legacy - id ending in [] means multiple if(this.options.id.substr(-2) == "[]") { this.options.multiple = true; } // Set up the URL to have the request ID & the widget ID var instance = this.getInstanceManager(); var self = this; this.asyncOptions = jQuery.extend({ // Callbacks onStart: function(event, file_count) { return self.onStart(event, file_count); }, onFinish: function(event, file_count) { self.onFinish.apply(self, [event, file_count]) }, onStartOne: function(event, file_name, index, file_count) { }, onFinishOne: function(event, response, name, number, total) { return self.finishUpload(event,response,name,number,total);}, onProgress: function(event, progress, name, number, total) { return self.onProgress(event,progress,name,number,total);}, onError: function(event, name, error) { return self.onError(event,name,error);}, beforeSend: function(form) { return self.beforeSend(form);}, target: egw.ajaxUrl(self.egw().getAppName()+".etemplate_widget_file.ajax_upload.etemplate"), query: function(file) {return self.beforeSend(file);}, // Disable checking for already uploaded chunks testChunks: false },this.asyncOptions); this.asyncOptions.fieldName = this.options.id; this.createInputWidget(); }, destroy: function() { this._super.apply(this, arguments); this.set_drop_target(null); this.node = null; this.input = null; this.span = null; this.progress = null; }, createInputWidget: function() { this.node = $j(document.createElement("div")).addClass("et2_file"); this.span = $j(document.createElement("span")) .addClass('et2_file_span et2_button et2_button_text') .appendTo (this.node); var span = this.span; this.input = $j(document.createElement("input")) .attr("type", "file").attr("placeholder", this.options.blur) .addClass ("et2_file_upload") .appendTo(this.node) .hover(function(e){ $j(span) .toggleClass('et2_file_spanHover'); }) .on({ mousedown:function (e){ $j(span).addClass('et2_file_spanActive'); }, mouseup:function (e){ $j(span).removeClass('et2_file_spanActive'); } }); // Check for File interface, should fall back to normal form submit if missing if(typeof File != "undefined" && typeof (new XMLHttpRequest()).upload != "undefined") { this.resumable = new Resumable(this.asyncOptions); this.resumable.assignBrowse(this.input); this.resumable.on('fileAdded', jQuery.proxy(this._fileAdded, this)); this.resumable.on('fileProgress', jQuery.proxy(this._fileProgress, this)); this.resumable.on('fileSuccess', jQuery.proxy(this.finishUpload, this)); this.resumable.on('complete', jQuery.proxy(this.onFinish, this)); } else { // This may be a problem submitting via ajax } if(this.options.progress) { var widget = this.getRoot().getWidgetById(this.options.progress); if(widget) { //may be not available at createInputWidget time this.progress = $j(widget.getDOMNode()); } } if(!this.progress) { this.progress = $j(document.createElement("div")).appendTo(this.node); } this.progress.addClass("progress"); if(this.options.multiple) { this.input.attr("multiple","multiple"); } this.setDOMNode(this.node[0]); }, /** * Set a widget or DOM node as a HTML5 file drop target * * @param String new_target widget ID or DOM node ID to be used as a new target */ set_drop_target: function(new_target) { // Cancel old drop target if(this.options.drop_target) { var widget = this.getRoot().getWidgetById(this.options.drop_target); var drop_target = widget && widget.getDOMNode() || document.getElementById(this.options.drop_target); if(drop_target) { this.resumable.unAssignDrop(drop_target); } } this.options.drop_target = new_target; if(!this.options.drop_target) return; // Set up new drop target var widget = this.getRoot().getWidgetById(this.options.drop_target); var drop_target = widget && widget.getDOMNode() || document.getElementById(this.options.drop_target); if(drop_target) { this.resumable.assignDrop([drop_target]); } else { this.egw().debug("warn", "Did not find file drop target %s", this.options.drop_target); } }, attachToDOM: function() { this._super.apply(this, arguments); // Override parent's change, file widget will fire change when finished uploading this.input.unbind("change.et2_inputWidget"); }, getValue: function() { var value = this.options.value ? this.options.value : this.input.val(); return value; }, /** * Set the value of the file widget. * * If you pass a FileList or list of files, it will trigger the async upload * * @param {FileList|File[]|false} value List of files to be uploaded, or false to reset. * @param {Event} event Most browsers require the user to initiate file transfers in some way. * Pass the event in, if you have it. */ set_value: function(value, event) { if(!value || typeof value == "undefined") { value = {}; } if(jQuery.isEmptyObject(value)) { this.options.value = {}; if (this.resumable.progress() == 1) this.progress.empty(); // Reset the HTML element this.input.wrap('