├── screenshot.png ├── package.yml ├── Source ├── Languages │ ├── Locale.en-US.uploadManager.js │ ├── Locale.de-DE.uploadManager.js │ ├── Locale.es-CO.uploadManager.js │ ├── Locale.fr-FR.uploadManager.js │ └── Locale.js ├── progressbar.js └── upload.js ├── php ├── download.php ├── upload.php ├── upload.html5.php ├── upload.html.php └── uploadhelper.php ├── LICENSE ├── number.js ├── Docs └── HOWTO.md ├── Demo └── index.html └── README.md /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tbela99/uploadManager/HEAD/screenshot.png -------------------------------------------------------------------------------- /package.yml: -------------------------------------------------------------------------------- 1 | name: uploadManager 2 | author: tbela99 3 | category: Utilities 4 | tags: [file, upload, html5, ajax] 5 | demo: http://tbela.fragged.org/demos/upload/Demo/ 6 | current: 1.5.4 7 | sources: 8 | - "number.js" 9 | - "Source/progressbar.js" 10 | - "Source/upload.js" 11 | - "Source/Languages/Locale.en-US.uploadManager.js" -------------------------------------------------------------------------------- /Source/Languages/Locale.en-US.uploadManager.js: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | name: Locale.en-US.uploadManager 4 | description: English Language File for uploadManager 5 | authors: Thierry Bela 6 | requires: [More/Locale] 7 | provides: Locale.en-US.uploadManager 8 | ... 9 | */ 10 | 11 | Locale.define('en-US', 'uploadManager', { 12 | BROWSE: 'Browse ...', 13 | CANCEL: 'Cancel', 14 | DROP_FILE_HERE: 'Drop files here', 15 | EMPTY_FILE: 'The selected file is empty', 16 | FILE_CORRUPTED: 'Uploaded file has been corrupted', 17 | MAX_FILE_SIZE_EXCEEDED: function (size) { 18 | 19 | return 'File too big (size must not exceed ' + size + ')' 20 | }, 21 | PAUSE: 'Pause', 22 | PREFETCH_FAILED: 'Failed to prefetch file infos', 23 | RESUME: 'Resume', 24 | RETRY: 'Retry', 25 | TOTAL_FILES_SIZE_EXCEEDED: function (size) { 26 | 27 | return 'File too big (total size must not exceed ' + size + ')' 28 | }, 29 | UPLOADING: 'uploading, please wait ...', 30 | UNAUTHORIZED_FILE_TYPE: 'Unauthorized file type' 31 | }); 32 | -------------------------------------------------------------------------------- /Source/Languages/Locale.de-DE.uploadManager.js: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | name: Locale.de-DE.uploadManager 4 | description: German Language File for uploadManager 5 | authors: Matthias Jobst 6 | requires: [More/Locale] 7 | provides: Locale.de-DE.uploadManager 8 | ... 9 | */ 10 | 11 | Locale.define('de-DE', 'uploadManager', { 12 | BROWSE: 'Datei wählen ...', 13 | CANCEL: 'Abbrechen', 14 | DROP_FILE_HERE: 'Dateien hier ablegen', 15 | EMPTY_FILE: 'Die gewählte Datei ist leer', 16 | FILE_CORRUPTED: 'Fehler beim Hochladen der Datei', 17 | MAX_FILE_SIZE_EXCEEDED: function (size) { 18 | 19 | return 'Datei zu groß (maximale Dateigröße: ' + size + ')' 20 | }, 21 | PAUSE: 'Pausieren', 22 | PREFETCH_FAILED: 'Datei-Infos konnten nicht hochgeladen werden', 23 | RESUME: 'Fortsetzen', 24 | RETRY: 'Erneut versuchen', 25 | TOTAL_FILES_SIZE_EXCEEDED: function (size) { 26 | 27 | return 'Dateien zu groß (maximale Gesamtgröße ' + size + ')' 28 | }, 29 | UPLOADING: 'Upload läuft, bitte warten ...', 30 | UNAUTHORIZED_FILE_TYPE: 'Dateityp nicht erlaubt' 31 | }); 32 | 33 | -------------------------------------------------------------------------------- /php/download.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Source/Languages/Locale.es-CO.uploadManager.js: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | name: Locale.en-CO.uploadManager 4 | description: Espanish-Colombia Language File for uploadManager 5 | authors: David Avellaneda 6 | requires: [More/Locale] 7 | provides: Locale.es-CO.uploadManager 8 | ... 9 | */ 10 | 11 | Locale.define('es-CO', 'uploadManager', { 12 | BROWSE: 'Buscar...', 13 | CANCEL: 'Cancelar', 14 | DROP_FILE_HERE: 'Suelta archivos aquí', 15 | EMPTY_FILE: 'El archivo seleccionado está vacío', 16 | FILE_CORRUPTED: 'El archivo subido ha sido corrompido', 17 | MAX_FILE_SIZE_EXCEEDED: function (size) { 18 | return 'Archivo muy grande (el tamaño no debe exceder ' + size + ')' 19 | }, 20 | PAUSE: 'Pausar', 21 | PREFETCH_FAILED: 'No se pudo cargar la información del archivo', 22 | RESUME: 'Resumir', 23 | RETRY: 'Reintentar', 24 | TOTAL_FILES_SIZE_EXCEEDED: function (size) { 25 | 26 | return 'Archivo muy grande (el tamaño no debe exceder ' + size + ')' 27 | }, 28 | UPLOADING: 'subiendo, por favor espere ...', 29 | UNAUTHORIZED_FILE_TYPE: 'Tipo de archivo no autorizado' 30 | }); 31 | -------------------------------------------------------------------------------- /Source/Languages/Locale.fr-FR.uploadManager.js: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | name: Locale.fr-FR.uploadManager 4 | description: French Language File for uploadManager 5 | authors: Thierry Bela 6 | requires: [More/Locale] 7 | provides: Locale.fr-FR.uploadManager 8 | ... 9 | */ 10 | 11 | Locale.define('fr-FR', 'uploadManager', { 12 | BROWSE: 'Parcourir ...', 13 | CANCEL: 'Annuler', 14 | DROP_FILE_HERE: 'Déposer les fichiers ici', 15 | EMPTY_FILE: 'Le fichier sélectionné est vide', 16 | FILE_CORRUPTED: 'Le fichier téléchargé est endommagé', 17 | MAX_FILE_SIZE_EXCEEDED: function (size) { 18 | 19 | return 'Fichier trop grand (la taille ne peut excéder ' + size + ')' 20 | }, 21 | PAUSE: 'Pause', 22 | PREFETCH_FAILED: 'Impossible de récuperer les infos du fichier', 23 | RESUME: 'Resume', 24 | RETRY: 'Réessayer', 25 | TOTAL_FILES_SIZE_EXCEEDED: function (size) { 26 | 27 | return 'Fichier trop grand (le total ne peut excéder ' + size + ')' 28 | }, 29 | UPLOADING: 'Transfert en cours, veuillez patienter ...', 30 | UNAUTHORIZED_FILE_TYPE: 'Type de fichier non autorisé' 31 | }); 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2006 Thierry Bela 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /number.js: -------------------------------------------------------------------------------- 1 | 2 | Number.implement({ 3 | 4 | format: function(kSep, floatsep, decimals, fill) { 5 | 6 | decimals = decimals == undefined ? 2 : decimals; 7 | floatsep = floatsep == undefined ? '.' : floatsep; 8 | kSep = kSep == undefined ? ' ' : kSep; 9 | fill = fill == undefined ? '' : fill; 10 | 11 | var parts = this.round(decimals).toString().split('.'), 12 | integer = parts[0], 13 | string = '' + integer, 14 | str = '', 15 | i, j; 16 | 17 | for(i = 0, j = string.length; j > 0; j-- && i++) str = (j > 1 && i % 3 == 2 ? kSep : '') + string.charAt(j-1) + str; 18 | 19 | if (decimals === 0) return str; 20 | 21 | var dec = parts[1] ? parts[1].substr(0, decimals) : ''; 22 | 23 | if(fill) while(dec.length < decimals) dec += '0'; 24 | 25 | return str + (dec ? floatsep + dec : '') 26 | }, 27 | 28 | toFileSize: function(units) { 29 | 30 | if(this == 0) return 0; 31 | 32 | var s = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'], e = Math.floor(Math.log(this) / Math.log(1024)); 33 | 34 | return (this / Math.pow(1024, Math.floor(e))).toFixed(2) + " " + (units && units[e] ? units[e] : s[e]); 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /php/upload.php: -------------------------------------------------------------------------------- 1 | $value) 38 | if (substr($name, 0, 5) == 'HTTP_') 39 | $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; 40 | 41 | return $headers; 42 | } 43 | } 44 | 45 | ob_start(); 46 | 47 | if(uploadhelper::getVar('dl') == 1) require BASE_PATH.DS.'download.php'; 48 | 49 | else { 50 | 51 | $headers = apache_request_headers(); 52 | 53 | require !empty($headers['Sender']) ? BASE_PATH.DS.'upload.html5.php' : BASE_PATH.DS.'upload.html.php'; 54 | } 55 | 56 | ob_flush(); 57 | 58 | //garbage colletor, remove old files 59 | 60 | error_reporting(0); 61 | 62 | $t = time(); 63 | 64 | //file are not supposed to stay here for a long period, they should be moved after being uploaded 65 | $max_age = 3600 * 24; 66 | 67 | if($handle = opendir(TEMP_PATH)) { 68 | 69 | while (false !== ($file = readdir($handle))) { 70 | 71 | if($file == '.' || $file == '..'|| $file == 'index.php') 72 | continue; 73 | 74 | if($t - filemtime(TEMP_PATH.DS.$file) > $max_age) 75 | unlink(TEMP_PATH.DS.$file); 76 | } 77 | 78 | closedir($handle); 79 | } 80 | 81 | exit(); 82 | ?> -------------------------------------------------------------------------------- /Source/Languages/Locale.js: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | 4 | script: Object.Extras.js 5 | 6 | name: Object.Extras 7 | 8 | description: Extra Object generics, like getFromPath which allows a path notation to child elements. 9 | 10 | license: MIT-style license 11 | 12 | authors: 13 | - Aaron Newton 14 | 15 | requires: 16 | - Core/Object 17 | - /MooTools.More 18 | 19 | provides: [Object.Extras] 20 | 21 | ... 22 | */ 23 | 24 | (function(){ 25 | 26 | var defined = function(value){ 27 | return value != null; 28 | }; 29 | 30 | var hasOwnProperty = Object.prototype.hasOwnProperty; 31 | 32 | Object.extend({ 33 | 34 | getFromPath: function(source, parts){ 35 | if (typeof parts == 'string') parts = parts.split('.'); 36 | for (var i = 0, l = parts.length; i < l; i++){ 37 | if (hasOwnProperty.call(source, parts[i])) source = source[parts[i]]; 38 | else return null; 39 | } 40 | return source; 41 | }, 42 | 43 | cleanValues: function(object, method){ 44 | method = method || defined; 45 | for (var key in object) if (!method(object[key])){ 46 | delete object[key]; 47 | } 48 | return object; 49 | }, 50 | 51 | erase: function(object, key){ 52 | if (hasOwnProperty.call(object, key)) delete object[key]; 53 | return object; 54 | }, 55 | 56 | run: function(object){ 57 | var args = Array.slice(arguments, 1); 58 | for (var key in object) if (object[key].apply){ 59 | object[key].apply(object, args); 60 | } 61 | return object; 62 | } 63 | 64 | }); 65 | 66 | })(); 67 | 68 | 69 | /* 70 | --- 71 | 72 | script: Locale.js 73 | 74 | name: Locale 75 | 76 | description: Provides methods for localization. 77 | 78 | license: MIT-style license 79 | 80 | authors: 81 | - Aaron Newton 82 | - Arian Stolwijk 83 | 84 | requires: 85 | - Core/Events 86 | - /Object.Extras 87 | - /MooTools.More 88 | 89 | provides: [Locale, Lang] 90 | 91 | ... 92 | */ 93 | 94 | (function(){ 95 | 96 | var current = null, 97 | locales = {}, 98 | inherits = {}; 99 | 100 | var getSet = function(set){ 101 | if (instanceOf(set, Locale.Set)) return set; 102 | else return locales[set]; 103 | }; 104 | 105 | var Locale = this.Locale = { 106 | 107 | define: function(locale, set, key, value){ 108 | var name; 109 | if (instanceOf(locale, Locale.Set)){ 110 | name = locale.name; 111 | if (name) locales[name] = locale; 112 | } else { 113 | name = locale; 114 | if (!locales[name]) locales[name] = new Locale.Set(name); 115 | locale = locales[name]; 116 | } 117 | 118 | if (set) locale.define(set, key, value); 119 | 120 | 121 | 122 | if (!current) current = locale; 123 | 124 | return locale; 125 | }, 126 | 127 | use: function(locale){ 128 | locale = getSet(locale); 129 | 130 | if (locale){ 131 | current = locale; 132 | 133 | this.fireEvent('change', locale); 134 | 135 | 136 | } 137 | 138 | return this; 139 | }, 140 | 141 | getCurrent: function(){ 142 | return current; 143 | }, 144 | 145 | get: function(key, args){ 146 | return (current) ? current.get(key, args) : ''; 147 | }, 148 | 149 | inherit: function(locale, inherits, set){ 150 | locale = getSet(locale); 151 | 152 | if (locale) locale.inherit(inherits, set); 153 | return this; 154 | }, 155 | 156 | list: function(){ 157 | return Object.keys(locales); 158 | } 159 | 160 | }; 161 | 162 | Object.append(Locale, new Events); 163 | 164 | Locale.Set = new Class({ 165 | 166 | sets: {}, 167 | 168 | inherits: { 169 | locales: [], 170 | sets: {} 171 | }, 172 | 173 | initialize: function(name){ 174 | this.name = name || ''; 175 | }, 176 | 177 | define: function(set, key, value){ 178 | var defineData = this.sets[set]; 179 | if (!defineData) defineData = {}; 180 | 181 | if (key){ 182 | if (typeOf(key) == 'object') defineData = Object.merge(defineData, key); 183 | else defineData[key] = value; 184 | } 185 | this.sets[set] = defineData; 186 | 187 | return this; 188 | }, 189 | 190 | get: function(key, args, _base){ 191 | var value = Object.getFromPath(this.sets, key); 192 | if (value != null){ 193 | var type = typeOf(value); 194 | if (type == 'function') value = value.apply(null, Array.from(args)); 195 | else if (type == 'object') value = Object.clone(value); 196 | return value; 197 | } 198 | 199 | // get value of inherited locales 200 | var index = key.indexOf('.'), 201 | set = index < 0 ? key : key.substr(0, index), 202 | names = (this.inherits.sets[set] || []).combine(this.inherits.locales).include('en-US'); 203 | if (!_base) _base = []; 204 | 205 | for (var i = 0, l = names.length; i < l; i++){ 206 | if (_base.contains(names[i])) continue; 207 | _base.include(names[i]); 208 | 209 | var locale = locales[names[i]]; 210 | if (!locale) continue; 211 | 212 | value = locale.get(key, args, _base); 213 | if (value != null) return value; 214 | } 215 | 216 | return ''; 217 | }, 218 | 219 | inherit: function(names, set){ 220 | names = Array.from(names); 221 | 222 | if (set && !this.inherits.sets[set]) this.inherits.sets[set] = []; 223 | 224 | var l = names.length; 225 | while (l--) (set ? this.inherits.sets[set] : this.inherits.locales).unshift(names[l]); 226 | 227 | return this; 228 | } 229 | 230 | }); 231 | 232 | 233 | 234 | })(); 235 | 236 | -------------------------------------------------------------------------------- /Docs/HOWTO.md: -------------------------------------------------------------------------------- 1 | uploadManager 2 | ============ 3 | 4 | mootools 1.3 ajax file upload with: 5 | 6 | [Demo](http://tbela.fragged.org/demos/upload/Demo/) 7 |  8 | 9 | How to use 10 | ---------- 11 | 12 | uploadManager uploads files in a temporary folder of your webserser and pull back the uploaded file name and its path in the form, so you can send them along with the rest of the form. 13 | you will need a webserver with php installed to run the demo. 14 | for a detailed usage see [HOWTO.md](https://github.com/tbela99/uploadManager/blob/master/Docs/HOWTO.md) in the Docs folder. 15 | 16 | # uploadManager 17 | 18 | creates and manage uploads with the following features: 19 | 20 | - resume upload on error/pause (Google Chrome, Firefox 3.6+) 21 | - file drag drop (currently supported by chrome 5+, firefox 3.6+ and safari 5.1+) 22 | - progress bar for browsers supporting HTML5 File API (chrome5+, safari4+, Firefox 3.6+) 23 | - no input file for Firefox 4 24 | - iframe for the others 25 | - customizable by css (fully customizable in firefox 4 and later) 26 | - easy to use 27 | 28 | How does it work 29 | --------------------- 30 | 31 | uploadManager uploads files in a temporary folder of your webserser and pull back the uploaded file name and its path in the form, so you can send them along with the rest of the form. 32 | you will need a webserver with php installed to run the demo. 33 | 34 | # Setup server side upload 35 | 36 | you must indicate the temp upload folder by editing the constant *TEMP_PATH* in *upload.php*. you will need to create the folder and allow the script to create file in it. 37 | you should also change the methods *uploadHelper::encrypt()* and *uploadHelper::decrypt()* in the file uploadhelper.php to provide a better encryption method. 38 | 39 | # the client side upload 40 | 41 | ### CSS: 42 | 43 | #upload { 44 | 45 | border: 1px solid #ccc; 46 | max-width: 300px; 47 | min-height: 100px; 48 | } 49 | 50 | /* 51 | 52 | An element is created as first child of #upload (or the element el called by attachDragEvents(el). It is hidden on creation with style "display:none". 53 | style of this el, and style for morph when drag enter is fired 54 | */ 55 | .drop-upload { 56 | 57 | left: 0; top: 0; 58 | height: 24px; 59 | z-index: 10; 60 | background-color: #000; 61 | vertical-align: middle; 62 | text-align: center; 63 | color:#fff; 64 | } 65 | /* 66 | 67 | applied on dragenter 68 | */ 69 | .drop-upload-active { 70 | 71 | background-color: #5BA2D5; 72 | } 73 | 74 | /* 75 | 76 | cancel upload button 77 | */ 78 | .cancel-upload { 79 | 80 | background:#ccc; 81 | border: 1px solid #999999; 82 | color: #000000; 83 | display: inline-block; 84 | margin: 5px 5px 0 3px; 85 | padding: 2px 5px; 86 | } 87 | .cancel-upload:hover { 88 | 89 | background: #aaa; 90 | color: #fff; 91 | } 92 | 93 | /* 94 | 95 | browse button style: currently supported only by Firexox 4+ 96 | */ 97 | 98 | .browse-upload { 99 | 100 | background:#ccc; 101 | border: 1px solid #999999; 102 | color: #000000; 103 | display: inline-block; 104 | margin: 5px 5px 0 0; 105 | padding: 2px 5px; 106 | } 107 | .browse-upload:hover { 108 | 109 | background: #aaa; 110 | color: #fff; 111 | } 112 | 113 | ### HTML: 114 | 115 |
117 | 118 | ### Javascript: 119 | 120 | var options = { 121 | 122 | //id of the upload container 123 | container: 'upload', 124 | 125 | //where to send the upload request 126 | base: '../php/upload.php', 127 | 128 | //filter file types 129 | //filetype: 'html,rar,zip', 130 | 131 | //form field name 132 | name: 'names[]', 133 | 134 | multiple: true, //enable multiple selection in file dialog 135 | progressbar: { 136 | 137 | width: 140, //fix the progressbar width, optional 138 | color: '#000', 139 | fillColor: '#fff', 140 | text: 'Pending...', 141 | onChange: function (value, progressbar) { 142 | 143 | //console.log(arguments) 144 | progressbar.setText('completed: ' + (100 * value).format() + '%') 145 | } 146 | } 147 | }; 148 | 149 | //enable file drag drop on $('upload') 150 | uploadManager.attachDragEvents('upload', options); 151 | 152 | //click to add a new file upload 153 | document.getElement('a').addEvent('click', function(e) { 154 | 155 | e.stop(); 156 | 157 | uploadManager.upload(options) 158 | }) 159 | 160 | when the upload succeed, two fields are pushed into the form: 161 | - one field which contains the file name. 162 | - one field which contains the encrypted file path on the server. 163 | 164 | # Handling the form submission 165 | 166 | in your php form submission handler, getting the file name is a trivial task. you should move files from the temp directory to the final location 167 | 168 | $file) { 177 | 178 | //get the file path 179 | echo uploadHelper::decrypt($file); 180 | 181 | //file name 182 | echo $names[$k]; 183 | } 184 | ?> 185 | 186 | 187 | -------------------------------------------------------------------------------- /Source/progressbar.js: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | script: progressbar.js 4 | license: MIT-style license. 5 | description: Javascript progressbar. 6 | copyright: Copyright (c) Thierry Bela 7 | authors: [Thierry Bela] 8 | 9 | requires: 10 | core:1.2.4: 11 | - Element.Event 12 | - Fx.Elements 13 | provides: [ProgressBar] 14 | ... 15 | */ 16 | 17 | !function (windows) { 18 | 19 | var ProgressBar = new Class({ 20 | 21 | options: { 22 | 23 | /* 24 | 25 | backgroundImage: '', 26 | */ 27 | 28 | value: 0, 29 | text: '', 30 | gradient: false, 31 | fillColor: '#aaa', 32 | color: '#fff' 33 | }, 34 | previous: 0, 35 | Implements: [Options, Events], 36 | initialize: function (options) { 37 | 38 | options = this.setOptions(options).options; 39 | 40 | if(typeof options.gradient == 'boolean') options.gradient = [options.color, options.fillColor]; 41 | 42 | options.gradient = options.gradient ? this.getBackground(options.gradient) : options.fillColor; 43 | 44 | var container = document.id(this.options.container), 45 | width = this.width = options.width || container.getStyle('width').toInt() || 1, 46 | height, 47 | style = 'position:absolute;display:inline-block;margin:0 auto;left:0;top:0', 48 | self = this, 49 | timer, 50 | value = 0, 51 | change = function () { 52 | 53 | self.fireEvent('change', [value, self]) 54 | }, 55 | clear = function () { 56 | 57 | clearTimeout(timer); 58 | change() 59 | }, 60 | last, 61 | set; 62 | 63 | this.value = 0; 64 | this.element = new Element('span', {style: 'width:' + width + 'px;position:relative;border:1px solid ' + options.fillColor + ';background:' + options.color + ';display:inline-block;'}). 65 | inject(container). 66 | grab(new Element('span', {style: 'z-index:1;width:' + width + 'px;text-indent:5px;margin:0 auto;color:' + options.fillColor + ';' + style, text: options.text})). 67 | grab(new Element('span', {style: 'z-index:2;overflow:hidden;width:' + options.value + 'px;' + style}). 68 | grab(new Element('span', {style: 'width:' + width + 'px;text-indent:5px;margin:0 auto;color:' + options.color + (options.backgroundImage ? ';background: url(' + options.backgroundImage + ') repeat-x' : '') + ';display:inline-block;' + style, text: options.text})) 69 | ). 70 | grab(new Element('span', {html: ' ', style: 'width:' + options.value + 'px;' + (options.gradient.indexOf(':') == -1 ? 'background' : 'filter') + ':' + options.gradient + ';' + style})); 71 | 72 | last = this.element.getLast(); 73 | height = last.getStyle('height'); 74 | this.element.setStyle('height', height).getElement('span+span').setStyle('height', height); 75 | this.elements = $$(this.element.getFirst(), this.element.getElement('span span')); 76 | this.progress = new Fx.Elements([last, last.getPrevious()], { 77 | link: 'cancel', 78 | onStart: function () { 79 | 80 | timer = setTimeout(change, 10) 81 | }, 82 | onCancel: clear, 83 | onComplete: clear 84 | }); 85 | 86 | //little hack to improve the precision of the value 87 | set = this.progress.set.bind(this.progress); 88 | 89 | this.progress.set = function (now) { 90 | 91 | this.value = value = now[0].width[0].value / this.width; 92 | return set(now) 93 | 94 | }.bind(this); 95 | 96 | 97 | if(options.text === '') this.elements[1].set('html', ' '); 98 | this.setValue(options.value) 99 | }, 100 | toElement: function () { return this.element }, 101 | setText: function (text) { 102 | 103 | this.elements.set('text', text); 104 | return this 105 | }, 106 | setValue: function(value) { 107 | 108 | if(value < 0) value = 0; 109 | else if(value > 1) value = 1; 110 | 111 | if(this.value != value) { 112 | 113 | var tween = {width: value * this.width}, 114 | self = this; 115 | 116 | this.previous = this.value; 117 | this.progress.start({0: tween, 1: tween}).chain(function () { if(value == 1) self.fireEvent('complete', self) }) 118 | } 119 | 120 | return this 121 | }, 122 | getValue: function () { return this.value }, 123 | getBackground: function (background) { 124 | 125 | if(typeof background == 'string') return background; 126 | 127 | background = Object.map(background, function (val) { return val }); 128 | var bg; 129 | 130 | switch(Browser.name) { 131 | 132 | case 'firefox': 133 | 134 | if(Browser.version >= '3.6') bg = '-moz-linear-gradient(top,{0} 0%,{1} 59%)'; /* FF3.6+ */ 135 | 136 | break; 137 | case 'chrome': 138 | case 'safari': 139 | 140 | if((Browser.name == 'chrome' && Browser.version >= '10') || (Browser.name == 'safari' && Browser.version >= '5.1')) bg = '-webkit-linear-gradient(top,{0} 0%,{1} 59%)'; /* Chrome10+,Safari5.1+ */ 141 | else if(Browser.chrome || Browser.version >= '4') bg = '-webkit-gradient(linear, left top,left bottom,color-stop(0%,{0}),color-stop(59%,{1}))'; /* Chrome,Safari4+ */ 142 | 143 | break; 144 | case 'opera': 145 | 146 | if(Browser.version >= '11.10') bg = '-o-linear-gradient(top,{0} 0%,{1} 59%)'; /* Opera11.10+ */ 147 | 148 | break; 149 | case 'ie': 150 | 151 | if(Browser.version >= '10') bg = '-ms-linear-gradient(top,{0} 0%,{1} 59%)'; /* IE10+ */ 152 | else if(Browser.version >= 6) bg = "progid:DXImageTransform.Microsoft.gradient(startColorstr='{0}', endColorstr='{1}',GradientType=0 )"; /* IE6-9 */ 153 | break; 154 | } 155 | 156 | if(bg == '') bg = '{0}'; 157 | 158 | return bg.substitute(background) 159 | } 160 | }); 161 | 162 | if(typeof define == 'function' && define.amd) define(function () { return ProgressBar }); 163 | else window.ProgressBar = ProgressBar 164 | 165 | }(this); 166 | ; 167 | -------------------------------------------------------------------------------- /php/upload.html5.php: -------------------------------------------------------------------------------- 1 | false, 'message' => 'File size is required')); 27 | exit(); 28 | } 29 | 30 | //request file transfert infos 31 | if(!empty($headers['Prefetch'])) { 32 | 33 | $filename = ''; 34 | $success = true; 35 | 36 | //create transfert log 37 | if(empty($headers['Guid'])) { 38 | 39 | $guid = uploadHelper::uuid(); 40 | 41 | //really needed ? 42 | while(is_file(TEMP_PATH.DS.$guid)) 43 | $guid = uploadHelper::uuid(); 44 | 45 | //store file path & size 46 | $filename = uploadHelper::create_filename(basename($headers['Filename']).'.tmp', TEMP_PATH); 47 | //$chunk = $filename.$headers['Current']; 48 | 49 | //force empty file creation 50 | fclose(fopen($filename, 'w')); 51 | //fclose(fopen($chunk, 'w')); 52 | file_put_contents(TEMP_PATH.DS.$guid, basename($filename)."\n".$headers['Parts']); 53 | 54 | echo json_encode(array('guid' => $guid, 'success' => $success, 'remove' => $url.'r='.($guid ? '&guid='.$guid : ''))); 55 | exit(); 56 | } 57 | 58 | else { 59 | 60 | if(empty($headers['Chunk-Size']) || 61 | !isset($headers['Current']) || 62 | !isset($headers['Offset']) || 63 | !is_numeric($headers['Chunk-Size']) || 64 | !is_numeric($headers['Current']) || 65 | !is_numeric($headers['Offset'])) { 66 | 67 | echo json_encode(array('success' => false, 'message' => 'Invalid headers sent')); 68 | exit(); 69 | } 70 | 71 | $guid = $headers['Guid']; 72 | $filename = TEMP_PATH.DS.$guid; 73 | 74 | $infos = is_file($filename) ? explode("\n", file_get_contents($filename)) : array(); 75 | 76 | //guid validation 77 | if(!preg_match('/[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}/', $guid) || !is_file($filename) || empty($infos[0]) || !is_file(TEMP_PATH.DS.$infos[0])) { 78 | 79 | echo json_encode(array('success' => false, 'message' => 'Invalid guid')); 80 | exit(); 81 | } 82 | 83 | $chunk = TEMP_PATH.DS.$infos[0].$headers['Current']; 84 | 85 | if(!is_file($chunk)) 86 | fclose(fopen($chunk, 'w')); 87 | 88 | echo json_encode(array('guid' => $guid, 'size' => filesize($chunk), 'success' => $success, 'remove' => $url.'r='.($guid ? '&guid='.$guid : ''))); 89 | exit(); 90 | } 91 | } 92 | 93 | $success = true; 94 | $filename = ''; 95 | $path = ''; 96 | $guid = ''; 97 | 98 | //resume upload 99 | if(!empty($headers['Guid'])) { 100 | 101 | $guid = $headers['Guid']; 102 | $file = TEMP_PATH.DS.$guid; 103 | 104 | $infos = is_file($file) ? explode("\n", file_get_contents($file)) : array(); 105 | 106 | //guid validation 107 | if(!preg_match('/[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}/', $guid)) { 108 | 109 | echo json_encode(array('success' => false, 'Invalid guid')); 110 | exit(); 111 | } 112 | 113 | if(!is_file($file) || empty($infos[0]) || !is_file(TEMP_PATH.DS.$infos[0])) { 114 | 115 | echo json_encode(array('success' => false, 'File not found')); 116 | exit(); 117 | } 118 | 119 | //here we store the partial upload 120 | //read the segment info 121 | $path = uploadHelper::encrypt(TEMP_PATH.DS.$infos[0]); 122 | $filename = TEMP_PATH.DS.$infos[0].$headers['Current']; 123 | 124 | ignore_user_abort(true); 125 | $handle = fopen($filename, 'ab'); 126 | fwrite($handle, file_get_contents('php://input')); 127 | fclose($handle); 128 | 129 | //ugh! :) 130 | clearstatcache(/* true, $filename */); 131 | 132 | if(connection_aborted()) 133 | exit(); 134 | } 135 | 136 | else { 137 | 138 | $filename = uploadHelper::create_filename(basename($headers['Filename']).'.tmp', TEMP_PATH); 139 | file_put_contents($filename, file_get_contents('php://input')); 140 | $path = uploadHelper::encrypt($filename); 141 | } 142 | 143 | $filesize = isset($headers['Chunk-Size']) ? $headers['Chunk-Size'] : $headers['Size']; 144 | $size = filesize($filename); 145 | 146 | if($size == 0 || (empty($headers['Partial']) && $size != $filesize)) 147 | unlink($filename); 148 | else 149 | if(isset($headers['Guid']) && $filesize == $size) { 150 | 151 | //merge 152 | $handle = fopen(TEMP_PATH.DS.$infos[0], 'r+b'); 153 | fseek($handle, $headers['Offset']); 154 | fwrite($handle, file_get_contents($filename)); 155 | fclose($handle); 156 | } 157 | 158 | $return = array( 159 | 160 | 'file' => basename($headers['Filename']), 161 | 'path' => $path, 162 | 'success' => !empty($headers['Partial']) || $size == $filesize, 163 | 'size' => $size, 164 | 'remove' => $url.'r='.urlencode($path).($guid ? '&guid='.$guid : '') 165 | ); 166 | 167 | if($guid) 168 | $return['clean'] = $url.'c='.$guid; 169 | 170 | echo json_encode($return); 171 | 172 | } 173 | 174 | else 175 | //remove chunks 176 | if($guid = uploadHelper::getVar('c')) { 177 | 178 | if(preg_match('/[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}/', $guid) && is_file(TEMP_PATH.DS.$guid)) { 179 | 180 | //remove chunks 181 | $infos = file(TEMP_PATH.DS.$guid); 182 | 183 | $infos[0] = trim($infos[0]); 184 | 185 | for($i = 0; $i < $infos[1]; $i++) 186 | if(is_file(TEMP_PATH.DS.$infos[0].$i)) 187 | unlink(TEMP_PATH.DS.$infos[0].$i); 188 | 189 | unlink(TEMP_PATH.DS.$guid); 190 | } 191 | } 192 | 193 | else 194 | //remove file 195 | if($file = uploadHelper::getVar('r')) { 196 | 197 | if(is_file($file = uploadHelper::decrypt($file))) { 198 | 199 | //$file = realpath($file); 200 | if(is_file(TEMP_PATH.DS.basename($file))) 201 | unlink($file); 202 | 203 | //remove file 204 | if($guid = uploadHelper::getVar('guid')) { 205 | 206 | if(preg_match('/[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}/', $guid) && is_file(TEMP_PATH.DS.$guid)) { 207 | 208 | //remove chunks 209 | $infos = file(TEMP_PATH.DS.$guid); 210 | 211 | $infos[0] = trim($infos[0]); 212 | 213 | for($i = 0; $i < $infos[1]; $i++) 214 | if(is_file(TEMP_PATH.DS.$infos[0].$i)) 215 | unlink(TEMP_PATH.DS.$infos[0].$i); 216 | 217 | unlink(TEMP_PATH.DS.$guid); 218 | } 219 | } 220 | } 221 | } 222 | 223 | ?> -------------------------------------------------------------------------------- /php/upload.html.php: -------------------------------------------------------------------------------- 1 | 27 | 28 | 29 |201 | * $overwrite : 202 | * - _COPY_SKIP_ALWAYS : skip existing 203 | * - _COPY_SKIP_IF_NEWER : skip existing if it is newer 204 | * - _COPY_SKIP_NEVER : always overwrite 205 | * - any other value considered as _COPY_SKIP_ALWAYS 206 | * if $dest is a file, it might be replaced depending on the value of $overwrite 207 | * if $dest is a directory, $source will be copied into this directory 208 | *209 | * @param string $dest 210 | * @param string $source 211 | * @param boolean $overwrite 212 | * @return boolean true on succes 213 | * @see file_move() 214 | */ 215 | public static function file_copy($dest, $source, $overwrite = _COPY_SKIP_ALWAYS) { 216 | 217 | if($dest == $source) 218 | return false; 219 | 220 | if(basename($source) == '') 221 | return false; 222 | 223 | if(is_dir($dest)) 224 | $dest = self::add_path_delimiter($dest).basename($source); 225 | 226 | if(!is_file($source) || !self::mkdirs(dirname($dest))) 227 | return false; 228 | //source == dest ? 229 | if(is_file($dest)) { 230 | //existing but older or always overwrite 231 | if($overwrite == _COPY_CREATE_NEW) 232 | $dest = self::create_filename($dest); 233 | 234 | if(($overwrite == _COPY_SKIP_IF_NEWER && filemtime($source) > filemtime($dest)) || $overwrite == _COPY_SKIP_NEVER || $overwrite == _COPY_CREATE_NEW) { 235 | 236 | if(!copy($source, $dest)) 237 | return false; 238 | 239 | touch($dest, filemtime($source)); 240 | } 241 | //else don't touch 242 | return true; 243 | } 244 | 245 | //file_exists return true if the file is a directory, but is_file return false and the copy will fail 246 | //not a file, already tested with is_file 247 | if(file_exists($dest)) 248 | return false; 249 | 250 | if(copy($source, $dest)) { 251 | touch($dest, filemtime($source)); 252 | return true; 253 | } 254 | return false; 255 | } 256 | 257 | /** 258 | * 259 | * 260 | * @param string $path 261 | * @return string 262 | */ 263 | public static function add_path_delimiter($path) { 264 | 265 | if($path == '') 266 | return $path; 267 | $path = str_replace('/', DS, $path); 268 | 269 | if($path[strlen($path) - 1] == DS) 270 | return $path; 271 | return $path.DS; 272 | } 273 | 274 | /** 275 | * generate smart file name like file.php, file(1).php if file.php exists, and so on... but DOES NOT create the file 276 | * 277 | * @param string $name 278 | * @param string $dir directory where file will be created, may not exist. if not specified, the current directory is used. the directory is not created 279 | * @return string 280 | * @internal now better handling of files name like 'file.tar.gz' 281 | */ 282 | public static function create_filename($name, $dir = '', $lowercaseext = false) { 283 | 284 | $name = self::clean($name); 285 | 286 | $dir = self::add_path_delimiter($dir); 287 | $path = $dir.$name; 288 | 289 | if(!file_exists($path)) 290 | return $path; 291 | 292 | $ext = self::file_ext($lowercaseext ? strtolower($name) : $name); 293 | 294 | if(!$dir) 295 | $dir = self::add_path_delimiter($dir.dirname($name)); 296 | 297 | $base = self::file_name($name); 298 | 299 | $i = 1; 300 | while(file_exists($dir. $base. '('.$i.').'.$ext)) $i++; 301 | return $dir. $base. '('.$i.').'.$ext; 302 | } 303 | 304 | public static function truncate_str($str, $length = 80, $append = '...', $smart = true) { 305 | 306 | if(strlen($str) <= $length || $length == 0) 307 | return $str; 308 | if(!$smart) 309 | return substr($str, 0, max($length - strlen($append), 0)).$append; 310 | return preg_replace('/(((<[^\/][^>]*>)|(<[^>]*)|(\w|-|\(|\[|<))*)\z/m', '', substr($str, 0, max($length - strlen($append), 0))).$append; 311 | } 312 | 313 | public static function str_search($str, $quote_style = ENT_QUOTES, $charset = 'utf-8') { 314 | 315 | return iconv($charset, $charset.'//IGNORE', html_entity_decode(preg_replace('/&(.)(acute|circ|grave|uml|cedil|tilde);/si', '$1', htmlentities($str, $quote_style, $charset)), $quote_style, $charset)); 316 | } 317 | 318 | public static function safe_name($name) { 319 | 320 | return strtolower(preg_replace('/[^a-z0-9,._]+/i', '-', self::str_search($name))); 321 | } 322 | 323 | /** 324 | * 325 | * @param string $path 326 | * @return string 327 | */ 328 | public static function remove_path_delimiter($path) { 329 | 330 | if($path == '') 331 | return $path; 332 | $path = str_replace(array('\\', '/'), DS, $path); 333 | 334 | if($path[strlen($path) - 1] == DS) 335 | return substr($path, 0, strlen($path) - 1); 336 | return $path; 337 | } 338 | 339 | public static function mkdirs($directory) { 340 | 341 | if(is_dir($directory)) 342 | return $directory; 343 | 344 | //existing but not a directory! 345 | if(file_exists($directory)) 346 | return false; 347 | 348 | $directory = str_replace("\\", "/", self::remove_path_delimiter($directory)); 349 | $directory = str_replace("/", DS, self::remove_path_delimiter($directory)); 350 | 351 | if(!($dirs = explode(DS, $directory))) 352 | return false; 353 | 354 | $dir = $dirs[0]; 355 | 356 | //linux/unix system, fix bug ? 357 | if($directory[0] == '/') 358 | $dir = '/'.$dir; 359 | 360 | if(!is_dir($dir.DS) && !file_exists($dir)) 361 | 362 | if(!mkdir($dir)) { 363 | // echo ' '.$dir; 364 | return false; 365 | } 366 | 367 | // chmod($dir, $chmod); 368 | 369 | for($j = 1; $j < count($dirs); $j++) { 370 | 371 | $dir .= "/".$dirs[$j]; 372 | if(!is_dir($dir.'/') && !file_exists($dir)) 373 | if(!mkdir($dir)) { 374 | // echo ' '.$dir; 375 | return false; 376 | } 377 | 378 | // chmod($dir, $chmod); 379 | } 380 | 381 | // chmod($dir, $chmod); 382 | return $dir; 383 | } 384 | 385 | /** 386 | * file upload helper class 387 | * 388 | * @param string $sub_dir where to put uploaded files 389 | * @param string $key_field upload form file name 390 | * @return uploadHelper 391 | */ 392 | public function __construct($sub_dir = TEMP_PATH, $key_field = 'file') { 393 | 394 | if(!($this->dir_path = $this->mkdirs($sub_dir))) 395 | die("failed to build $sub_dir"); 396 | 397 | if(!is_file($this->dir_path.DS.'index.html')) 398 | file_put_contents($this->dir_path.DS.'index.html', ''); 399 | 400 | $this->key_field = $key_field; 401 | } 402 | 403 | /** 404 | * upload a single file, return false on error 405 | * 406 | * @return array 407 | */ 408 | function upload_file() { 409 | 410 | $res = array(); 411 | 412 | if($this->get_param($this->get_param($_FILES, $this->key_field), 'error') != UPLOAD_ERR_OK) 413 | return false; 414 | 415 | $path = $this->create_filename($this->get_param($this->get_param($_FILES, $this->key_field), 'name'), $this->dir_path); 416 | 417 | if(!$this->file_copy ($path, $this->get_param($this->get_param($_FILES, $this->key_field), 'tmp_name'))) 418 | return false; 419 | 420 | /* file name */ 421 | $res[0]['name'] = $_FILES[$this->key_field]['name']; 422 | 423 | /* uploaded file name */ 424 | $res[0]['path'] = $path; 425 | 426 | /* uploaded file mimetype */ 427 | $res[0]['type'] = $_FILES[$this->key_field]['type']; 428 | 429 | return $res; 430 | } 431 | 432 | /** 433 | * upload multiple files, return false on error 434 | * 435 | * @return array 436 | */ 437 | function upload_files() { 438 | 439 | $res = array(); 440 | 441 | if(is_array($this->get_param($this->get_param($_FILES, $this->key_field), 'name'))) { 442 | 443 | for($i = 0; $i < count($_FILES[$this->key_field]['name']); $i++) { 444 | 445 | if($_FILES[$this->key_field]['error'][$i] != UPLOAD_ERR_OK) 446 | return false; 447 | 448 | $path = $this->create_filename ($_FILES[$this->key_field]['name'][$i], $this->dir_path); 449 | 450 | if(!$this->file_copy ($path, $_FILES[$this->key_field]['tmp_name'][$i])) 451 | return false; 452 | 453 | $res[$i]['name'] = $_FILES[$this->key_field]['name'][$i]; 454 | $res[$i]['path'] = $path; 455 | $res[$i]['type'] = $_FILES[$this->key_field]['type'][$i]; 456 | } 457 | 458 | } else return $this->upload_file(); 459 | 460 | return $res; 461 | } 462 | } 463 | //} 464 | ?> -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | uploadManager 2 | ============ 3 | 4 | Mootools html5/ajax multipart file upload 5 | 6 | [Demo](http://tbela.fragged.org/demos/upload/Demo/) 7 |  8 | 9 | How to use 10 | ---------- 11 | 12 | uploadManager uploads files in a temporary folder of your webserser and pull back the uploaded file name and its path in the form, so you can send them along with the rest of the form. 13 | you will need a webserver with php installed to run the demo. 14 | for a detailed usage see [HOWTO.md](https://github.com/tbela99/uploadManager/blob/master/Docs/HOWTO.md) in the Docs folder. 15 | 16 | # uploadManager 17 | 18 | creates and manage uploads with the following features: 19 | 20 | - easy to use 21 | - Select files and upload folders from the file browser dialog (tested in chrome 20+) 22 | - folder drag drop (currently supported by chrome 21+) 23 | - file drag drop (currently supported by chrome 5+, firefox 3.6+ and safari 5.1+, IE10, Opera 12+) 24 | - faster upload: each file has multiple chunks uploaded in parallel (Google Chrome, Firefox 3.6+, Opera 12.5+, IE10) 25 | - resume upload on error/pause (Google Chrome, Firefox 4.0+, Opera 12.5+, IE10) 26 | - optional progressbar for browsers supporting HTML5 File API (chrome5+, safari4+, Firefox 3.6+, IE10, Opera 12 (Next)) 27 | - no input file for Firefox 4+ 28 | - iframe for older browsers 29 | - customizable by css (fully customizable in firefox 4 and later) 30 | - supports localization 31 | 32 | ### uploadManager Property: resume 33 | 34 | (*boolean*) indicates if the browser can resume upload after error or pause. 35 | 36 | ### uploadManager Property: xmlhttpupload 37 | 38 | (*boolean*) indicates if the browser handle file upload via XMLHTTPRequest. 39 | 40 | ### uploadManager Property: enqueue 41 | 42 | (*boolean*) queue upload. by default upload are not queued. 43 | 44 | ### uploadManager Property: concurrent 45 | 46 | (*int*) limit the number of active uploads if enqueue is *true*. default to 1 47 | 48 | ### uploadManager Property: multiple 49 | 50 | (*boolean*) indicates if the browser can handle multiple files selection. 51 | 52 | ### uploadManager Method: upload {#uploadManager:upload} 53 | ------------ 54 | 55 | create a new upload field in a given container. 56 | 57 | ### Returns: 58 | 59 | * (*object*) - file upload instance. if the the maximum number of files that can be uploaded is reached null is returned instead. 60 | 61 | #### Arguments: 62 | 63 | 1. options - (*object*) see file upload instance options. 64 | 65 | ##### Options: 66 | 67 | - container - (*string*) upload container id. 68 | - folder - (*boolean*) Select folders instead of files in the file selection dialog. (Chrome 22+) 69 | - pause - (*boolean*) allow user to pause/resume upload (if the browser can resume broken upload) otherwise the resume button will only appear when an error occur. default to false. 70 | - chunks - (*int*) number of chunks uploaded simultaneously for a file. default to 3. 71 | - chunckSize - (*int*) chunk file size. default to 1Mb. if the browser can resume broken file upload, file will be split in pieces of a maximum length of chunckSize. 72 | - base - (*string*, optional) url of the page that will handle server side file upload. default to *upload.php*. 73 | - limit - (*int*, optional) maximum number of files the user can upload. 0 means no restriction. default to 0. 74 | - filesize - (*int*, optional) maximum size of a file the user can upload. 0 means no restriction. default to 0. 75 | - maxsize - (*int*, optional) maximum size of files uploaded by a user. 0 means no restriction. default to 0. 76 | - iframe - (*boolean*, optional) force iframe upload. 77 | - multiple - (*boolean*, optional) enable multiple file selection if the browser can handle it. 78 | - filetype - (*string*, optional) authorized file type. 79 | - name - (*string*) name of the upload form field. it contains the original name of the file sent by the user. if the upload succeed a hidden field named *'file_' + name* and containing the encrypted file path on the server will be pushed into the form. if multiple file upload is enabled then this field will be sent as an array 80 | for example if our form field is named *name[]*, then *name[]* will contains the original file name and *file_name[]* will contains the encrypted file path on the server. 81 | - progressbar - (*mixed*, optional) indicates whether to display a progressbar or not. if *false* then the progressbar is disabled. if *true* the progressbar will use default options. if it is an *object*, it will be passed as progressbar options. see [Progressbar](http://github.com/tbela99/progressbar/) 82 | - hideDialog - (*boolean*, optional) Firefox 4+ only: if true the file selection dialog will not be shown after the upload instance is created. 83 | - autostart - (*boolean*, optional) automatically start upload. default to true. 84 | 85 | ##### Progressbar: 86 | 87 | - container - (*mixed*) progressbar container. 88 | - width - (*int*, optional) progressbar width. 89 | - value - (*number*, optional) initial value of the progressbar. value is always between 0 and 1 (100%). default to 0. 90 | - text - (*string*, optional) progressbar text. 91 | - color - (*string*, optional) progressbar color. 92 | - fillColor - (*string*, optional) progressbar fill color. 93 | - backgroundImage - (*string*, optional) background image used to fill the progressbar. this parameter will shadow the fillColor parameter. 94 | 95 | ##### Progressbar events: 96 | 97 | ##### onChange 98 | 99 | ##### Arguments: 100 | 101 | - value - (*number*) progressbar value. it is a number between 0 and 1 102 | - progressbar - (*object*) progressbar. 103 | 104 | ##### onComplete 105 | 106 | ##### Arguments: 107 | 108 | - progressbar - (*object*) progressbar. 109 | 110 | 111 | ##### Upload events: 112 | 113 | ##### onCreate 114 | 115 | Fired after the upload instance has been created. 116 | 117 | ##### Arguments: 118 | 119 | - transfer - (*object*) file upload instance 120 | 121 | ##### onStart 122 | 123 | Fired after transfer is started. 124 | 125 | ##### Arguments: 126 | 127 | - transfer - (*object*) file upload instance 128 | 129 | ##### onLoad 130 | 131 | Fired before the file is uploaded. 132 | 133 | ##### Arguments: 134 | 135 | - options - (*object*) 136 | 137 | ##### Options: 138 | 139 | - element - (*element*) the file upload instance container. 140 | - file - (*string*) the file name. 141 | - size - (*int*) file size. if the browser supports XMLHTTPRequest file upload, this will be the actual file size, otherwise it will be 0. 142 | - transfer - (*object*) file upload instance. 143 | 144 | ##### onProgress 145 | 146 | Fired while the file is uploaded. 147 | 148 | ##### Arguments: 149 | 150 | - value - (*number*) - the progress value is between 0 and 1 151 | 152 | ##### onAbort 153 | 154 | Fired when the transfer is aborted before the file is uploaded. 155 | 156 | ##### Arguments: 157 | 158 | - options - (*object*) 159 | 160 | ##### Options: 161 | 162 | - file - (*string*) file name 163 | - message - (*string*) error message 164 | - transfer - (*object*) file upload instance. 165 | 166 | ##### onCancel 167 | 168 | ##### Options: 169 | 170 | - message - (*string*, optional) error message 171 | 172 | Fired when the transfer is cancelled. 173 | 174 | ##### Arguments: 175 | 176 | - transfer - (*object*) file upload instance. 177 | 178 | 179 | ##### onFailure 180 | 181 | Fired when the transfer fails. 182 | 183 | ##### Arguments: 184 | 185 | - transfer - (*object*) file upload instance. 186 | 187 | ##### onSuccess 188 | 189 | Fired when the transfer succeed. 190 | 191 | ##### Arguments: 192 | 193 | 1. infos - (*object*) uploaded file infos 194 | 195 | ##### Infos: 196 | 197 | - file - (*string*) the original file name. 198 | - path - (*string*) the encrypted file path on the server. 199 | - size - (*int*) uploaded file size. 200 | - transfer - (*object*) file upload instance 201 | 202 | ##### onPause 203 | 204 | Fired when the transfer is paused. 205 | 206 | ##### Arguments: 207 | 208 | - transfer - (*object*) file upload instance 209 | 210 | ##### onResume 211 | 212 | Fired when the transfer is resumed. 213 | 214 | ##### Arguments: 215 | 216 | - transfer - (*object*) file upload instance 217 | 218 | ##### onComplete 219 | 220 | Fired when the transfer is complete. 221 | 222 | ##### Arguments: 223 | 224 | - transfer - (*object*) file upload instance 225 | 226 | ##### onAllComplete 227 | 228 | Fired when all transfer are completed. 229 | 230 | ##### Arguments: 231 | 232 | - container - (*string*) container id 233 | 234 | ### uploadManager Method: attachDragEvents 235 | ------------ 236 | 237 | enable files to be uploaded when they are dropped on an element. this happens only if the browser supports file drag drop. 238 | 239 | ### Returns: 240 | 241 | * (*object*) - uploadManager. 242 | 243 | #### Arguments: 244 | 245 | 1. el - (*mixed*) element 246 | 1. options - (*object*) see [uploadManager#upload](#uploadManager:upload) . 247 | 248 | ### uploadManager Method: detachDragEvents 249 | ------------ 250 | 251 | remove upload events from an element. 252 | 253 | ### Returns: 254 | 255 | * (*object*) - uploadManager. 256 | 257 | #### Arguments: 258 | 259 | 1. el - (*mixed*) element 260 | 261 | ### uploadManager Method: get 262 | ------------ 263 | 264 | return the [file upload instance](#uploadManager:instance) with the given id. 265 | 266 | ### Returns: 267 | 268 | * (*object*) - file upload instance. 269 | 270 | #### Arguments: 271 | 272 | 1. id - (*string*) file upload instance id. 273 | 274 | ### uploadManager Method: getSize 275 | ------------ 276 | 277 | return uploaded file size for a given container. 278 | 279 | ### Returns: 280 | 281 | * (*mixed*) 282 | 283 | #### Arguments: 284 | 285 | 1. container - (*string*) container id 286 | 287 | return all the file upload instance of a given container. 288 | 289 | 290 | ### uploadManager Method: getTransfers 291 | ------------ 292 | 293 | return the transfer instances for a given container. 294 | 295 | ### Returns: 296 | 297 | * (*array*) 298 | 299 | #### Arguments: 300 | 301 | 1. container - (*string*) container id 302 | 303 | 304 | ## File upload instance {#uploadManager:instance} 305 | ---------------------- 306 | 307 | object wrapping a file upload instance. 308 | 309 | ### Implements 310 | 311 | Options, Events. see [uploadManager#upload](#uploadManager:upload) for implemented options and events. 312 | 313 | ### File upload instance Properties: 314 | ------------ 315 | - completed - (*boolean*) true if the file has been succesfully uploaded 316 | - filesize - (*int*) the uploaded file size in byte. 317 | - state - (*int*) state of the transfer of this instance. value are: 0 (not started), 1 (loading), 2 (aborted), 3 (cancelled), 4 (completed) 318 | - progressbar - (*object*) progressbar. the progressbar is created only after the file is loaded. 319 | 320 | Example 321 | ------- 322 | 323 | //CSS 324 | 325 | #dropfiles { 326 | 327 | border: #000; 328 | width: 100%; 329 | height: 250px; 330 | } 331 | 332 | //HTML 333 | 334 | 335 | 336 | 337 | 338 | 339 | Select a picture 340 | 341 | 342 | //Javacript 343 | 344 | window.addEvent('domready', function () { 345 | 346 | //upload options 347 | var options = { 348 | //upload container 349 | container: 'dropfiles', 350 | 351 | //only one file can be uploaded 352 | limit: 1, 353 | 354 | // uncomment the following line to enable folder upload in google chrome 22+ 355 | // folder: true, 356 | 357 | //upload field name 358 | name: 'picture', 359 | 360 | //filter by file type 361 | filetype: 'jpg,gif,png', 362 | 363 | //where to send uploaded file 364 | base: '/files/upload', 365 | 366 | // dynamically add UI elements to the upload field 367 | onCreate: function (transfer) { 368 | 369 | //add ui elements dynamically like a textarea ? 370 | transfer.toElement().grab(new Element('span[text=UI element]')) 371 | }, 372 | onComplete: function () { 373 | 374 | alert('I am complete') 375 | }, 376 | allComplete: function () { 377 | 378 | alert('Yay! we are all complete') 379 | } 380 | } 381 | 382 | //enable drap & drop 383 | uploadManager.attachDragEvents('dropfiles', options); 384 | 385 | //add a new upload on click on the link right before #dropfiles 386 | document.getElement("#dropfiles!+a").addEvent("click", function (e) { 387 | 388 | e.stop(); 389 | 390 | uploadManager.upload(options); 391 | }) 392 | }) 393 | 394 | 395 | Example 396 | -------- 397 | 398 | a simple form to upload a text file. 399 | 400 | ### HTML: 401 | 402 | 407 | 408 | ### Javascript: 409 | 410 | var options = { 411 | 412 | //upload only one file 413 | limit: 1, 414 | //upload in this element 415 | container: 'upload', 416 | //allowed file type 417 | filetype: 'jpg,gif,png', 418 | //transfer aborted 419 | onAbort: function () { 420 | 421 | alert('Unauthorized file type, allowed file type are "' + this.options.filetype + '"') 422 | }, 423 | onSuccess: function () { alert('Transfer completed succesfully!') } 424 | }; 425 | 426 | // check files are uploaded before the form can be submitted 427 | window.addEvent('domready', function () { 428 | 429 | //add click listener on the link 430 | document.id('upload').addEvent('click', function (e) { 431 | 432 | e.stop(); 433 | 434 | //create upload field 435 | uploadManager.upload(options) 436 | }). 437 | //check submit form 438 | getParent('form').addEvent('submit', function (e) { 439 | 440 | e.stop(); 441 | 442 | //transfer instances 443 | var transfers = uploadManager.getTransfers('upload'); 444 | 445 | //user have not uploaded a file 446 | if(transfers.length == 0) { 447 | 448 | alert('Please select a file to upload'); 449 | return 450 | } 451 | 452 | //check we have ungoing transfers 453 | if(transfers.some(function (instance) { return instance.state == 1 })) { 454 | 455 | alert('there are some pending queries, please wait until it completes, the try again'); 456 | return 457 | } 458 | 459 | //check all transfers have completed 460 | if(!transfers.every(function (instance) { return instance.complete})) { 461 | 462 | alert('some tranfers may have failed. please try again'); 463 | return 464 | } 465 | 466 | //all transfers have completed succesfully, submit the form 467 | alert('Yay!'); 468 | 469 | //this.submit() 470 | }) 471 | }) 472 | -------------------------------------------------------------------------------- /Source/upload.js: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | script: upload.js 4 | license: MIT-style license. 5 | description: Javascript crossbrowser file upload, with some html5 sugar. 6 | copyright: Copyright (c) 2006 - 2012 Thierry Bela 7 | authors: [Thierry Bela] 8 | 9 | requires: 10 | core:1.3: 11 | - Element.Event 12 | - Element.Style 13 | - Fx.Tween 14 | - Fx.Elements 15 | - Array 16 | provides: [uploadManager] 17 | ... 18 | */ 19 | 20 | String.implement({shorten: function (max, end, fill) { 21 | 22 | max = max || 20; 23 | end = end || 12; 24 | fill = fill || '... '; 25 | 26 | if(this.length > max) return this.substring(0, max - end - fill.length + 1) + fill + this.substring(this.length - end + 1); 27 | 28 | return this 29 | } 30 | }); 31 | 32 | (function ($, window, undef) { 33 | 34 | "use strict"; 35 | 36 | function wrapper(ProgressBar, Locale) { 37 | 38 | var store = 'umo', 39 | transport = 'upl:tr', 40 | Browser = window.Browser, 41 | Element = window.Element, 42 | Locale = window.Locale, 43 | Object = window.Object, 44 | XMLHttpRequest = window.XMLHttpRequest, 45 | addEvent = 'addEvent', 46 | addEvents = 'addEvents', 47 | append = 'append', 48 | decode = 'decode', 49 | createElement = 'createElement', 50 | fireEvent = 'fireEvent', 51 | get = 'get', 52 | getElement = 'getElement', 53 | getXHR = 'getXHR', 54 | responseText = 'responseText', 55 | setStyle = 'setStyle', 56 | toFileSize = 'toFileSize', 57 | hasFileReader = 'FileReader' in window, 58 | setRequestHeader = 'setRequestHeader', 59 | getAsEntry = undef, 60 | input = (function () { var input = document[createElement]('input'); input.type = 'file'; return input })(), 61 | fileproto = window.File ? File.prototype : {}, 62 | method = 'slice' in fileproto ? 'slice' : ('mozSlice' in fileproto ? 'mozSlice' : ('webkitSlice' in fileproto ? 'webkitSlice' : false)), 63 | //browser version 64 | brokenSlice = (Browser.chrome && Browser.version < 11) || (Browser.firefox && Browser.version <= 4), 65 | 66 | uploadManager = { 67 | 68 | /* xmlhttp can be used */ 69 | xmlhttpupload: 'files' in input && XMLHttpRequest && 'upload' in new XMLHttpRequest(), 70 | 71 | /* can handle multiple files upload */ 72 | multiple: 'multiple' in input, 73 | //FF 4.01, chrome 11: resume upload seems to be broken 74 | resume: !!method, 75 | 76 | //upload hash 77 | uploads: {}, 78 | 79 | active: false, 80 | //active transfers 81 | actives: {}, 82 | 83 | //queue uploads per container 84 | enqueue: false, 85 | 86 | //maximun concurrent uploads, if queue uploads is true 87 | concurrent: 1, 88 | 89 | //transfer queue, callback functions 90 | queue: {}, 91 | 92 | /* 93 | 94 | attach file dragdrop events onto el 95 | */ 96 | 97 | attachDragEvents: function (el, options) { 98 | 99 | $(el)[addEvents](dragdrop).store(store, options).grab(new Element('div[style=display:none][class=drop-upload]', {text: Locale[get]('uploadManager.DROP_FILE_HERE') }), 'top'); 100 | return this 101 | }, 102 | 103 | /* 104 | 105 | detach file dragdrop events from el 106 | */ 107 | 108 | detachDragEvents: function (el) { 109 | 110 | el = $(el); 111 | if(el.retrieve(store)) $(el).removeEvents(dragdrop).eliminate(store).getFirst().destroy(); 112 | return this 113 | }, 114 | 115 | upload: function(options) { 116 | 117 | var opt = Object[append]({limit: 0, filesize: 0, maxsize: 0, progressbar: true, autostart: true/*, resume: false, iframe: false */}, options), 118 | container = opt.container, 119 | transfer; 120 | 121 | if(!this.uploads[container]) { 122 | 123 | this.actives[container] = []; 124 | this.uploads[container] = [] 125 | } 126 | 127 | //restrict number of uploaded files 128 | if(opt.limit > 0 && this.uploads[container].length >= opt.limit) return undef; 129 | 130 | if(opt.limit != 1 && !opt.name.test(/\[\]$/)) opt.name += '[]'; 131 | 132 | //where to send the uploaded file 133 | opt.base = opt.base || 'upload.php'; 134 | opt.id = opt.name.replace(/[^a-z0-9]/gi, '') + Date.now(); 135 | 136 | if(opt.iframe || !this.xmlhttpupload) transfer = new Transfert(opt); 137 | 138 | //opera 12 has 'slice' method but can't resume uploads 139 | else if(this.resume && (!Browser.opera || Browser.version > 12)) transfer = new HTML5MultipartTransfert(opt); 140 | else transfer = new HTML5Transfert(opt); 141 | 142 | this.uploads[container].push(transfer); 143 | 144 | return transfer 145 | }, 146 | 147 | push: function(container, callback) { 148 | 149 | this.queue[container] = this.queue[container] || []; 150 | this.queue[container].push(callback); 151 | 152 | return this.load(container) 153 | }, 154 | 155 | load: function (container) { 156 | 157 | var active = 0, prop; 158 | 159 | if(this.enqueue && this.active) { 160 | 161 | for(prop in this.actives[container]) { 162 | 163 | if(this.actives[container].hasOwnProperty(prop) && this.actives[container][prop].state == 1) active++; 164 | if(active >= this.concurrent) return this 165 | } 166 | } 167 | 168 | var callback = this.queue[container].shift(); 169 | if(callback) { 170 | 171 | this.active = true; 172 | callback() 173 | } 174 | 175 | return this 176 | }, 177 | 178 | //return Transfert instance associated to a given id 179 | get: function (id) { return $(id).retrieve(transport) }, 180 | 181 | getSize: function(container) { 182 | 183 | var size = 0; 184 | (this.uploads[container] || []).each(function (transfer) { size += transfer.filesize }); 185 | 186 | return size 187 | }, 188 | 189 | //return a copy of the internal list 190 | getTransfers: function (container) { return (this.uploads[container] || []).concat() } 191 | }, 192 | dragdrop = { 193 | 194 | dragenter: function(e) { 195 | 196 | e.stop(); 197 | 198 | if(e.target == this) this.getFirst().setStyle('display', 'block').morph('.drop-upload-active'); 199 | }, 200 | dragleave: function(e) { 201 | 202 | e.stop(); 203 | 204 | if(e.target == this) { 205 | 206 | var element = this.getFirst(); 207 | element.morph('.drop-upload').get('morph').chain(function () { 208 | 209 | element.style.display = 'none' 210 | }) 211 | } 212 | }, 213 | dragover: function(e) { 214 | 215 | e.stop(); 216 | this.getFirst(); 217 | }, 218 | drop: function (e) { 219 | 220 | e.stop(); 221 | 222 | var dataTransfer = e.event.dataTransfer, 223 | options = Object[append]({}, this.retrieve(store), {hideDialog: true}), 224 | upload = function (f) { 225 | 226 | // webkit upload folder 227 | // http://wiki.whatwg.org/wiki/DragAndDropEntries 228 | if(getAsEntry == undef && ![ 229 | 230 | 'getAsEntry', 'webkitGetAsEntry', 'mozGetAsEntry', 'oGetAsEntry', 'msGetAsEntry', 231 | 'getAsFile' // chrome 20 need this 232 | 233 | ].some(function (method) { 234 | 235 | if(f[method]) getAsEntry = method; 236 | 237 | return getAsEntry 238 | })) getAsEntry = ''; 239 | 240 | if(getAsEntry && f[getAsEntry]) f = f[getAsEntry](); 241 | 242 | if(f.isDirectory) f.createReader().readEntries(function(entries) { Array.each(entries, upload) }); 243 | else if(f.isFile) f.file(upload, function () { /* console.log('Error! ', arguments) */ }); 244 | else { 245 | 246 | transfer = uploadManager.upload(Object[append]({}, options)); 247 | if(transfer) transfer.load(f) 248 | } 249 | }, 250 | transfer; 251 | 252 | this.getFirst().removeClass('drop-upload-active').style.display = 'none'; 253 | if(dataTransfer) Array.each(dataTransfer.items || dataTransfer.files, upload) 254 | } 255 | }, 256 | 257 | Transfert = new Class({ 258 | 259 | state: 0, 260 | filesize: 0, 261 | complete: false, 262 | /* progressbar: null, */ 263 | initialize: function(options) { 264 | 265 | //file type filter 266 | if(options.filetype) this[addEvent]('load', function (object) { 267 | 268 | var matches = options.filetype.split(/[^a-z0-9]/i); 269 | 270 | if(!this.aborted && matches.length > 0) { 271 | 272 | this.aborted = !new RegExp('(\.' + matches.join(')$|(\.') + '$)', 'i').test(object.file); 273 | if(this.aborted) this.message = Locale[get]('uploadManager.UNAUTHORIZED_FILE_TYPE') 274 | } 275 | 276 | }); 277 | 278 | var element, container = options.container; 279 | 280 | // keep a copy 281 | this._options = Object.append({}, options); 282 | this[addEvents]({ 283 | 284 | abort: function () { this.state = 2 }, 285 | load: function () { uploadManager.actives[container].push(this) }, 286 | success: function (json) { 287 | 288 | this.state = 4; 289 | this.filesize = json.size; 290 | this.complete = true; 291 | uploadManager.actives[container].erase(this); 292 | 293 | //ultimate file size limit check 294 | if(options.maxsize > 0) { 295 | 296 | var size = 0; 297 | 298 | uploadManager.getTransfers(options.container).each(function (transfer) { if(transfer.state == 4) size += transfer.size}); 299 | 300 | if(size > options.maxsize) { 301 | 302 | this.message = Locale[get]('uploadManager.TOTAL_FILES_SIZE_EXCEEDED', options.maxsize[toFileSize]()); 303 | this.cancel() 304 | } 305 | } 306 | 307 | var id = options.id; 308 | 309 | $(id + '_lfile').set({checked: true, value: json.path, disabled: false}); 310 | $(id).set({value: json.file, disabled: false }) 311 | }, 312 | cancel: function () { 313 | 314 | this.state = 3; 315 | uploadManager.uploads[container].erase(this); 316 | uploadManager.actives[container].erase(this) 317 | }, 318 | complete: function () { 319 | 320 | if(uploadManager.actives[container].length == 0 && uploadManager.queue[container].length == 0) { 321 | 322 | uploadManager.active = false; 323 | this[fireEvent]('allComplete', container) 324 | } 325 | else if(uploadManager.enqueue) { 326 | 327 | uploadManager.active = Object.every(uploadManager.actives[container], function (upload) { return upload.state == 1 }); 328 | uploadManager.load(container) 329 | } 330 | } 331 | }).setOptions(options); 332 | 333 | element = this[createElement](options); 334 | 335 | this.checkbox = element[getElement]('#' + options.id).store(transport, this); 336 | element[getElement]('a.cancel-upload')[addEvent]("click", function(e) { 337 | 338 | e.stop(); 339 | this.cancel() 340 | 341 | }.bind(this)); 342 | this[fireEvent]('create', this) 343 | }, 344 | 345 | createElement: function (options) { 346 | 347 | this.element = new Element('div', {'class': 'upload-container', 348 | html: '' 349 | + '' 350 | + '' 351 | + '' + Locale[get]('uploadManager.CANCEL') + '' 352 | }).inject(options.container); 353 | 354 | return this.element 355 | }, 356 | 357 | toElement: function () { return this.element }, 358 | 359 | load: function (file) { 360 | 361 | this.state = 1; 362 | this.aborted = false; 363 | this[fireEvent]('load', {element: this.element, file: file, size: 0, transfer: this}); 364 | 365 | if(this.aborted) { 366 | 367 | this.state = 2; 368 | this[fireEvent]('abort', {file: file, message: this.message || '', transfer: this}) 369 | } 370 | 371 | delete this.message; 372 | return this; 373 | }, 374 | 375 | cancel: function (message) { 376 | 377 | var complete = this.running; 378 | 379 | if(message) this.message = message; 380 | 381 | this[fireEvent]('cancel', this); 382 | if(complete) this[fireEvent]('complete', this); 383 | delete this.message; 384 | this.element.destroy() 385 | }, 386 | 387 | // start upload 388 | upload: function () { 389 | 390 | }, 391 | 392 | Implements: [Options, Events] 393 | }), 394 | 395 | HTML5 = { 396 | 397 | events: { 398 | 399 | load: function (obj) { 400 | 401 | var options = this.options; 402 | 403 | //file size limit check 404 | if(obj.size == 0) { 405 | 406 | this.message = Locale[get]('uploadManager.EMPTY_FILE') 407 | this.aborted = true 408 | } 409 | 410 | //file size limit check 411 | else if(options.filesize > 0 && obj.size > options.filesize) { 412 | 413 | this.aborted = true; 414 | this.message = Locale[get]('uploadManager.MAX_FILE_SIZE_EXCEEDED', options.filesize[toFileSize]()) 415 | } 416 | 417 | else if(options.maxsize > 0 && uploadManager.getSize(options.container) + obj.size > options.maxsize) { 418 | 419 | this.aborted = true; 420 | this.message = Locale[get]('uploadManager.TOTAL_FILES_SIZE_EXCEEDED', options.maxsize[toFileSize]()) 421 | } 422 | } 423 | }, 424 | 425 | add: function (obj, event, fn) { 426 | 427 | fn = fn.bind(this); 428 | if(obj.addEventListener) obj.addEventListener(event, fn, false); 429 | 430 | //IE 7 - 8 431 | else obj['on' + event] = fn; 432 | return this 433 | }, 434 | 435 | load: function (file) { 436 | 437 | this.aborted = false; 438 | if(this.aborted) { 439 | 440 | this[fireEvent]('abort', {file: file, message: this.message || '', transfer: this}); 441 | delete this.message; 442 | this.cancel(); 443 | return this 444 | } 445 | 446 | this.file = file; 447 | this.size = file.size; 448 | this.filename = file.name; 449 | 450 | var first = this.element.getFirst('.upload-progress'), 451 | span = first[getElement]('span')[setStyle]('display', 'none'), 452 | field = span.getNext()[setStyle]('display', 'none'), 453 | progress; 454 | 455 | this[addEvent]('progress', function (value) { 456 | 457 | if(progress && progress.setValue) progress.setValue(value); 458 | 459 | if(value == 1) { 460 | 461 | field[getElement]('label').set({text: this.filename.shorten() + ' (' + this.size[toFileSize]() + ')', title: this.filename}); 462 | field.style.display = '' 463 | } 464 | }); 465 | 466 | if(this.options.progressbar) progress = this.progressbar = new ProgressBar(Object[append]({ 467 | 468 | container: first.set('title', file.name), 469 | text: file.name.shorten() 470 | 471 | }, typeof this.options.progressbar == 'object' ? this.options.progressbar : {}))[addEvents]({ 472 | change: function () { 473 | 474 | first.set('title', file.name + ' (' + (this.value * 100).format() + '%)') 475 | }, 476 | onComplete: function () { 477 | 478 | progress = progress.toElement(); 479 | progress.style.display = 'none'; 480 | 481 | (function () { progress.destroy() }).delay(50) 482 | } 483 | }); 484 | 485 | this[fireEvent]('load', {element: this.element, file: file.name, size: file.size, transfer: this}); 486 | 487 | field.getFirst().style.display = 'none'; 488 | 489 | this.element[getElement]('input[type=file]').destroy(); 490 | 491 | span.destroy(); 492 | uploadManager.push(this.options.container, function () { 493 | 494 | this.state = 1; 495 | if(this.options.autostart) this.upload(); 496 | 497 | }.bind(this)); 498 | 499 | if(this.reader) this.reader.readAsBinaryString(file); 500 | return this 501 | }, 502 | 503 | createElement: function (options) { 504 | 505 | var self = this, input = self.createHTML(options)[getElement]('input[type=file]')[addEvent]('change', function (e) { 506 | 507 | var loaded = false, 508 | options = Object[append]({}, self._options, {hideDialog: true}), 509 | transfer; 510 | 511 | Array.each(input.files, function (f) { 512 | 513 | if(f.name == '.' || f.name == '..') return; 514 | else if(!loaded) { 515 | 516 | self.load(f); 517 | loaded = true 518 | } 519 | else { 520 | 521 | transfer = uploadManager.upload(Object[append]({}, options)); 522 | if(transfer) transfer.load(f) 523 | } 524 | }) 525 | }); 526 | 527 | // directory chooser 528 | if(options.folder) input.set({directory: '', webkitdirectory: '', mozdirectory: '', msdirectory: '', odirectory: '' }); 529 | 530 | if(Browser.name == 'firefox' && Browser.version >= 4) { 531 | 532 | new Element('a', {text: Locale[get]('uploadManager.BROWSE'), 'class': 'browse-upload btn', href: '#', events: {click: function (e) { e.stop(); input.click() }}}).inject(input[setStyle]('display', 'none'), 'before'); 533 | if(!self.options.hideDialog) input.click.delay(10, input) 534 | } 535 | 536 | return self[addEvent]('abort', function () { input.value = '' }).element 537 | } 538 | }, 539 | 540 | HTML5Transfert = new Class(Object[append]({ 541 | 542 | Extends: Transfert, 543 | running: false, 544 | ready: false, 545 | initialize: function(options) { 546 | 547 | this[addEvents](this.events)[addEvents]({ 548 | 549 | success: function (json) { 550 | 551 | var remove = json.remove; 552 | delete json.remove; 553 | 554 | this[addEvent]('cancel', function () { 555 | 556 | var xhr = new XMLHttpRequest(); 557 | 558 | xhr.open('GET', remove, true); 559 | xhr[setRequestHeader]('Sender', 'XMLHttpRequest'); 560 | xhr.send() 561 | }) 562 | }, 563 | 564 | cancel: function () { 565 | 566 | if(this.running) { 567 | 568 | this.xhr.abort(); 569 | this[getXHR]().running = false 570 | } 571 | } 572 | 573 | }).parent(options); 574 | 575 | this[getXHR]().binary = !!this.xhr.sendAsBinary; 576 | 577 | if(hasFileReader) { 578 | 579 | var reader = this.reader = new FileReader(); 580 | 581 | this.add(reader, 'load', function(e) { 582 | 583 | this.bin = e.target.result; 584 | this.ready = true 585 | }) 586 | } 587 | }, 588 | getXHR: function () { 589 | 590 | var xhr = this.xhr = new XMLHttpRequest(), 591 | options = this.options; 592 | 593 | this.add(xhr.upload, 'progress', function(e) { if (e.lengthComputable) this[fireEvent]('progress', e.loaded / e.total) }). 594 | add(xhr, 'load', function() { 595 | 596 | if(xhr.status == 0) { 597 | 598 | this.element[getElement]('.resume-upload').style.display = ''; 599 | return 600 | } 601 | 602 | var self = this[fireEvent]('progress', 1), 603 | options = this.options; 604 | 605 | var status, json, event = 'success'; 606 | this.running = false; 607 | 608 | try { status = xhr.status } catch(e) {} 609 | 610 | //success 611 | if (status >= 200 && status < 300) { 612 | 613 | try { 614 | 615 | json = JSON[decode](xhr[responseText]); 616 | json.transfer = this; 617 | json.element = this.element; 618 | if(json.size != this.size) event = 'failure' 619 | } 620 | catch(e) { event = 'failure' } 621 | 622 | } else event = 'failure'; 623 | 624 | this[fireEvent](event, event == 'failure' ? this : json)[fireEvent]('complete', this); 625 | 626 | if(json.size == 0) this.cancel(Locale[get]('uploadManager.EMPTY_FILE')); 627 | else if(options.filesize > 0 && json.size > options.filesize) this.cancel(Locale[get]('uploadManager.MAX_FILE_SIZE_EXCEEDED', options.filesize[toFileSize]())); 628 | else if(options.maxsize > 0 && uploadManager.getSize(options.container) > options.maxsize) this.cancel(Locale[get]('uploadManager.TOTAL_FILES_SIZE_EXCEEDED', options.maxsize[toFileSize]())) 629 | }). 630 | add(xhr, 'abort', this.resume). 631 | add(xhr, 'error', this.resume); 632 | 633 | return this 634 | }, 635 | resume: function () { 636 | 637 | this.element[getElement]('.resume-upload').style.display = '' 638 | }, 639 | createHTML: function (options) { 640 | 641 | this.element = new Element('div', { 642 | 'class': 'upload-container', 643 | html: '