├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── assets ├── dropzone.css └── dropzone.js ├── dropzone.html ├── feathersUpload.md ├── index.html ├── package.json └── server.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "strict": 0, 4 | "no-console": 0 5 | }, 6 | "env": { 7 | "node": true, 8 | "mocha": true, 9 | "es6": true 10 | }, 11 | "extends": "eslint:recommended" 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/linux,node,windows 3 | 4 | ### Linux ### 5 | *~ 6 | 7 | # temporary files which can be created if a process still has a handle open of a deleted file 8 | .fuse_hidden* 9 | 10 | # KDE directory preferences 11 | .directory 12 | 13 | # Linux trash folder which might appear on any partition or disk 14 | .Trash-* 15 | 16 | 17 | ### Node ### 18 | # Logs 19 | logs 20 | *.log 21 | npm-debug.log* 22 | 23 | # Runtime data 24 | pids 25 | *.pid 26 | *.seed 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | lib-cov 30 | 31 | # Coverage directory used by tools like istanbul 32 | coverage 33 | 34 | # nyc test coverage 35 | .nyc_output 36 | 37 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 38 | .grunt 39 | 40 | # node-waf configuration 41 | .lock-wscript 42 | 43 | # Compiled binary addons (http://nodejs.org/api/addons.html) 44 | build/Release 45 | 46 | # Dependency directories 47 | node_modules 48 | jspm_packages 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional REPL history 54 | .node_repl_history 55 | 56 | 57 | ### Windows ### 58 | # Windows image file caches 59 | Thumbs.db 60 | ehthumbs.db 61 | 62 | # Folder config file 63 | Desktop.ini 64 | 65 | # Recycle Bin used on file shares 66 | $RECYCLE.BIN/ 67 | 68 | # Windows Installer files 69 | *.cab 70 | *.msi 71 | *.msm 72 | *.msp 73 | 74 | # Windows shortcuts 75 | *.lnk 76 | 77 | 78 | # uploads 79 | uploads/ 80 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 CianCoders 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # feathers-example-upload 2 | 3 | A Feathers.js file upload example, using feathers-blob. 4 | 5 | ## Features: 6 | * Simple feathers-blob example and REST client. 7 | * Example with `multipart/form-data` support and real-time client. 8 | 9 | 10 | ## How to use it: 11 | 12 | * Clone or download it. `git clone https://github.com/CianCoders/feathers-example-fileupload.git` 13 | * `$ cd feathers-example-fileupload` 14 | * `$ npm install` 15 | * `$ npm start` 16 | * Visit `http://localhost:3030/` for a simple feathers-blob example 17 | * Visit `http://localhost:3030/dropzone.html` for a dropzone example with socket.io client. 18 | * Enjoy. 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /assets/dropzone.css: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * Copyright (c) 2012 Matias Meno 4 | */ 5 | @-webkit-keyframes passing-through { 6 | 0% { 7 | opacity: 0; 8 | -webkit-transform: translateY(40px); 9 | -moz-transform: translateY(40px); 10 | -ms-transform: translateY(40px); 11 | -o-transform: translateY(40px); 12 | transform: translateY(40px); } 13 | 30%, 70% { 14 | opacity: 1; 15 | -webkit-transform: translateY(0px); 16 | -moz-transform: translateY(0px); 17 | -ms-transform: translateY(0px); 18 | -o-transform: translateY(0px); 19 | transform: translateY(0px); } 20 | 100% { 21 | opacity: 0; 22 | -webkit-transform: translateY(-40px); 23 | -moz-transform: translateY(-40px); 24 | -ms-transform: translateY(-40px); 25 | -o-transform: translateY(-40px); 26 | transform: translateY(-40px); } } 27 | @-moz-keyframes passing-through { 28 | 0% { 29 | opacity: 0; 30 | -webkit-transform: translateY(40px); 31 | -moz-transform: translateY(40px); 32 | -ms-transform: translateY(40px); 33 | -o-transform: translateY(40px); 34 | transform: translateY(40px); } 35 | 30%, 70% { 36 | opacity: 1; 37 | -webkit-transform: translateY(0px); 38 | -moz-transform: translateY(0px); 39 | -ms-transform: translateY(0px); 40 | -o-transform: translateY(0px); 41 | transform: translateY(0px); } 42 | 100% { 43 | opacity: 0; 44 | -webkit-transform: translateY(-40px); 45 | -moz-transform: translateY(-40px); 46 | -ms-transform: translateY(-40px); 47 | -o-transform: translateY(-40px); 48 | transform: translateY(-40px); } } 49 | @keyframes passing-through { 50 | 0% { 51 | opacity: 0; 52 | -webkit-transform: translateY(40px); 53 | -moz-transform: translateY(40px); 54 | -ms-transform: translateY(40px); 55 | -o-transform: translateY(40px); 56 | transform: translateY(40px); } 57 | 30%, 70% { 58 | opacity: 1; 59 | -webkit-transform: translateY(0px); 60 | -moz-transform: translateY(0px); 61 | -ms-transform: translateY(0px); 62 | -o-transform: translateY(0px); 63 | transform: translateY(0px); } 64 | 100% { 65 | opacity: 0; 66 | -webkit-transform: translateY(-40px); 67 | -moz-transform: translateY(-40px); 68 | -ms-transform: translateY(-40px); 69 | -o-transform: translateY(-40px); 70 | transform: translateY(-40px); } } 71 | @-webkit-keyframes slide-in { 72 | 0% { 73 | opacity: 0; 74 | -webkit-transform: translateY(40px); 75 | -moz-transform: translateY(40px); 76 | -ms-transform: translateY(40px); 77 | -o-transform: translateY(40px); 78 | transform: translateY(40px); } 79 | 30% { 80 | opacity: 1; 81 | -webkit-transform: translateY(0px); 82 | -moz-transform: translateY(0px); 83 | -ms-transform: translateY(0px); 84 | -o-transform: translateY(0px); 85 | transform: translateY(0px); } } 86 | @-moz-keyframes slide-in { 87 | 0% { 88 | opacity: 0; 89 | -webkit-transform: translateY(40px); 90 | -moz-transform: translateY(40px); 91 | -ms-transform: translateY(40px); 92 | -o-transform: translateY(40px); 93 | transform: translateY(40px); } 94 | 30% { 95 | opacity: 1; 96 | -webkit-transform: translateY(0px); 97 | -moz-transform: translateY(0px); 98 | -ms-transform: translateY(0px); 99 | -o-transform: translateY(0px); 100 | transform: translateY(0px); } } 101 | @keyframes slide-in { 102 | 0% { 103 | opacity: 0; 104 | -webkit-transform: translateY(40px); 105 | -moz-transform: translateY(40px); 106 | -ms-transform: translateY(40px); 107 | -o-transform: translateY(40px); 108 | transform: translateY(40px); } 109 | 30% { 110 | opacity: 1; 111 | -webkit-transform: translateY(0px); 112 | -moz-transform: translateY(0px); 113 | -ms-transform: translateY(0px); 114 | -o-transform: translateY(0px); 115 | transform: translateY(0px); } } 116 | @-webkit-keyframes pulse { 117 | 0% { 118 | -webkit-transform: scale(1); 119 | -moz-transform: scale(1); 120 | -ms-transform: scale(1); 121 | -o-transform: scale(1); 122 | transform: scale(1); } 123 | 10% { 124 | -webkit-transform: scale(1.1); 125 | -moz-transform: scale(1.1); 126 | -ms-transform: scale(1.1); 127 | -o-transform: scale(1.1); 128 | transform: scale(1.1); } 129 | 20% { 130 | -webkit-transform: scale(1); 131 | -moz-transform: scale(1); 132 | -ms-transform: scale(1); 133 | -o-transform: scale(1); 134 | transform: scale(1); } } 135 | @-moz-keyframes pulse { 136 | 0% { 137 | -webkit-transform: scale(1); 138 | -moz-transform: scale(1); 139 | -ms-transform: scale(1); 140 | -o-transform: scale(1); 141 | transform: scale(1); } 142 | 10% { 143 | -webkit-transform: scale(1.1); 144 | -moz-transform: scale(1.1); 145 | -ms-transform: scale(1.1); 146 | -o-transform: scale(1.1); 147 | transform: scale(1.1); } 148 | 20% { 149 | -webkit-transform: scale(1); 150 | -moz-transform: scale(1); 151 | -ms-transform: scale(1); 152 | -o-transform: scale(1); 153 | transform: scale(1); } } 154 | @keyframes pulse { 155 | 0% { 156 | -webkit-transform: scale(1); 157 | -moz-transform: scale(1); 158 | -ms-transform: scale(1); 159 | -o-transform: scale(1); 160 | transform: scale(1); } 161 | 10% { 162 | -webkit-transform: scale(1.1); 163 | -moz-transform: scale(1.1); 164 | -ms-transform: scale(1.1); 165 | -o-transform: scale(1.1); 166 | transform: scale(1.1); } 167 | 20% { 168 | -webkit-transform: scale(1); 169 | -moz-transform: scale(1); 170 | -ms-transform: scale(1); 171 | -o-transform: scale(1); 172 | transform: scale(1); } } 173 | .dropzone, .dropzone * { 174 | box-sizing: border-box; } 175 | 176 | .dropzone { 177 | min-height: 150px; 178 | border: 2px solid rgba(0, 0, 0, 0.3); 179 | background: white; 180 | padding: 20px 20px; } 181 | .dropzone.dz-clickable { 182 | cursor: pointer; } 183 | .dropzone.dz-clickable * { 184 | cursor: default; } 185 | .dropzone.dz-clickable .dz-message, .dropzone.dz-clickable .dz-message * { 186 | cursor: pointer; } 187 | .dropzone.dz-started .dz-message { 188 | display: none; } 189 | .dropzone.dz-drag-hover { 190 | border-style: solid; } 191 | .dropzone.dz-drag-hover .dz-message { 192 | opacity: 0.5; } 193 | .dropzone .dz-message { 194 | text-align: center; 195 | margin: 2em 0; } 196 | .dropzone .dz-preview { 197 | position: relative; 198 | display: inline-block; 199 | vertical-align: top; 200 | margin: 16px; 201 | min-height: 100px; } 202 | .dropzone .dz-preview:hover { 203 | z-index: 1000; } 204 | .dropzone .dz-preview:hover .dz-details { 205 | opacity: 1; } 206 | .dropzone .dz-preview.dz-file-preview .dz-image { 207 | border-radius: 20px; 208 | background: #999; 209 | background: linear-gradient(to bottom, #eee, #ddd); } 210 | .dropzone .dz-preview.dz-file-preview .dz-details { 211 | opacity: 1; } 212 | .dropzone .dz-preview.dz-image-preview { 213 | background: white; } 214 | .dropzone .dz-preview.dz-image-preview .dz-details { 215 | -webkit-transition: opacity 0.2s linear; 216 | -moz-transition: opacity 0.2s linear; 217 | -ms-transition: opacity 0.2s linear; 218 | -o-transition: opacity 0.2s linear; 219 | transition: opacity 0.2s linear; } 220 | .dropzone .dz-preview .dz-remove { 221 | font-size: 14px; 222 | text-align: center; 223 | display: block; 224 | cursor: pointer; 225 | border: none; } 226 | .dropzone .dz-preview .dz-remove:hover { 227 | text-decoration: underline; } 228 | .dropzone .dz-preview:hover .dz-details { 229 | opacity: 1; } 230 | .dropzone .dz-preview .dz-details { 231 | z-index: 20; 232 | position: absolute; 233 | top: 0; 234 | left: 0; 235 | opacity: 0; 236 | font-size: 13px; 237 | min-width: 100%; 238 | max-width: 100%; 239 | padding: 2em 1em; 240 | text-align: center; 241 | color: rgba(0, 0, 0, 0.9); 242 | line-height: 150%; } 243 | .dropzone .dz-preview .dz-details .dz-size { 244 | margin-bottom: 1em; 245 | font-size: 16px; } 246 | .dropzone .dz-preview .dz-details .dz-filename { 247 | white-space: nowrap; } 248 | .dropzone .dz-preview .dz-details .dz-filename:hover span { 249 | border: 1px solid rgba(200, 200, 200, 0.8); 250 | background-color: rgba(255, 255, 255, 0.8); } 251 | .dropzone .dz-preview .dz-details .dz-filename:not(:hover) { 252 | overflow: hidden; 253 | text-overflow: ellipsis; } 254 | .dropzone .dz-preview .dz-details .dz-filename:not(:hover) span { 255 | border: 1px solid transparent; } 256 | .dropzone .dz-preview .dz-details .dz-filename span, .dropzone .dz-preview .dz-details .dz-size span { 257 | background-color: rgba(255, 255, 255, 0.4); 258 | padding: 0 0.4em; 259 | border-radius: 3px; } 260 | .dropzone .dz-preview:hover .dz-image img { 261 | -webkit-transform: scale(1.05, 1.05); 262 | -moz-transform: scale(1.05, 1.05); 263 | -ms-transform: scale(1.05, 1.05); 264 | -o-transform: scale(1.05, 1.05); 265 | transform: scale(1.05, 1.05); 266 | -webkit-filter: blur(8px); 267 | filter: blur(8px); } 268 | .dropzone .dz-preview .dz-image { 269 | border-radius: 20px; 270 | overflow: hidden; 271 | width: 120px; 272 | height: 120px; 273 | position: relative; 274 | display: block; 275 | z-index: 10; } 276 | .dropzone .dz-preview .dz-image img { 277 | display: block; } 278 | .dropzone .dz-preview.dz-success .dz-success-mark { 279 | -webkit-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); 280 | -moz-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); 281 | -ms-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); 282 | -o-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); 283 | animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); } 284 | .dropzone .dz-preview.dz-error .dz-error-mark { 285 | opacity: 1; 286 | -webkit-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); 287 | -moz-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); 288 | -ms-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); 289 | -o-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); 290 | animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); } 291 | .dropzone .dz-preview .dz-success-mark, .dropzone .dz-preview .dz-error-mark { 292 | pointer-events: none; 293 | opacity: 0; 294 | z-index: 500; 295 | position: absolute; 296 | display: block; 297 | top: 50%; 298 | left: 50%; 299 | margin-left: -27px; 300 | margin-top: -27px; } 301 | .dropzone .dz-preview .dz-success-mark svg, .dropzone .dz-preview .dz-error-mark svg { 302 | display: block; 303 | width: 54px; 304 | height: 54px; } 305 | .dropzone .dz-preview.dz-processing .dz-progress { 306 | opacity: 1; 307 | -webkit-transition: all 0.2s linear; 308 | -moz-transition: all 0.2s linear; 309 | -ms-transition: all 0.2s linear; 310 | -o-transition: all 0.2s linear; 311 | transition: all 0.2s linear; } 312 | .dropzone .dz-preview.dz-complete .dz-progress { 313 | opacity: 0; 314 | -webkit-transition: opacity 0.4s ease-in; 315 | -moz-transition: opacity 0.4s ease-in; 316 | -ms-transition: opacity 0.4s ease-in; 317 | -o-transition: opacity 0.4s ease-in; 318 | transition: opacity 0.4s ease-in; } 319 | .dropzone .dz-preview:not(.dz-processing) .dz-progress { 320 | -webkit-animation: pulse 6s ease infinite; 321 | -moz-animation: pulse 6s ease infinite; 322 | -ms-animation: pulse 6s ease infinite; 323 | -o-animation: pulse 6s ease infinite; 324 | animation: pulse 6s ease infinite; } 325 | .dropzone .dz-preview .dz-progress { 326 | opacity: 1; 327 | z-index: 1000; 328 | pointer-events: none; 329 | position: absolute; 330 | height: 16px; 331 | left: 50%; 332 | top: 50%; 333 | margin-top: -8px; 334 | width: 80px; 335 | margin-left: -40px; 336 | background: rgba(255, 255, 255, 0.9); 337 | -webkit-transform: scale(1); 338 | border-radius: 8px; 339 | overflow: hidden; } 340 | .dropzone .dz-preview .dz-progress .dz-upload { 341 | background: #333; 342 | background: linear-gradient(to bottom, #666, #444); 343 | position: absolute; 344 | top: 0; 345 | left: 0; 346 | bottom: 0; 347 | width: 0; 348 | -webkit-transition: width 300ms ease-in-out; 349 | -moz-transition: width 300ms ease-in-out; 350 | -ms-transition: width 300ms ease-in-out; 351 | -o-transition: width 300ms ease-in-out; 352 | transition: width 300ms ease-in-out; } 353 | .dropzone .dz-preview.dz-error .dz-error-message { 354 | display: block; } 355 | .dropzone .dz-preview.dz-error:hover .dz-error-message { 356 | opacity: 1; 357 | pointer-events: auto; } 358 | .dropzone .dz-preview .dz-error-message { 359 | pointer-events: none; 360 | z-index: 1000; 361 | position: absolute; 362 | display: block; 363 | display: none; 364 | opacity: 0; 365 | -webkit-transition: opacity 0.3s ease; 366 | -moz-transition: opacity 0.3s ease; 367 | -ms-transition: opacity 0.3s ease; 368 | -o-transition: opacity 0.3s ease; 369 | transition: opacity 0.3s ease; 370 | border-radius: 8px; 371 | font-size: 13px; 372 | top: 130px; 373 | left: -10px; 374 | width: 140px; 375 | background: #be2626; 376 | background: linear-gradient(to bottom, #be2626, #a92222); 377 | padding: 0.5em 1.2em; 378 | color: white; } 379 | .dropzone .dz-preview .dz-error-message:after { 380 | content: ''; 381 | position: absolute; 382 | top: -6px; 383 | left: 64px; 384 | width: 0; 385 | height: 0; 386 | border-left: 6px solid transparent; 387 | border-right: 6px solid transparent; 388 | border-bottom: 6px solid #be2626; } 389 | -------------------------------------------------------------------------------- /assets/dropzone.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * 4 | * More info at [www.dropzonejs.com](http://www.dropzonejs.com) 5 | * 6 | * Copyright (c) 2012, Matias Meno 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | * 26 | */ 27 | 28 | (function() { 29 | var Dropzone, Emitter, camelize, contentLoaded, detectVerticalSquash, drawImageIOSFix, noop, without, 30 | __slice = [].slice, 31 | __hasProp = {}.hasOwnProperty, 32 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 33 | 34 | noop = function() {}; 35 | 36 | Emitter = (function() { 37 | function Emitter() {} 38 | 39 | Emitter.prototype.addEventListener = Emitter.prototype.on; 40 | 41 | Emitter.prototype.on = function(event, fn) { 42 | this._callbacks = this._callbacks || {}; 43 | if (!this._callbacks[event]) { 44 | this._callbacks[event] = []; 45 | } 46 | this._callbacks[event].push(fn); 47 | return this; 48 | }; 49 | 50 | Emitter.prototype.emit = function() { 51 | var args, callback, callbacks, event, _i, _len; 52 | event = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 53 | this._callbacks = this._callbacks || {}; 54 | callbacks = this._callbacks[event]; 55 | if (callbacks) { 56 | for (_i = 0, _len = callbacks.length; _i < _len; _i++) { 57 | callback = callbacks[_i]; 58 | callback.apply(this, args); 59 | } 60 | } 61 | return this; 62 | }; 63 | 64 | Emitter.prototype.removeListener = Emitter.prototype.off; 65 | 66 | Emitter.prototype.removeAllListeners = Emitter.prototype.off; 67 | 68 | Emitter.prototype.removeEventListener = Emitter.prototype.off; 69 | 70 | Emitter.prototype.off = function(event, fn) { 71 | var callback, callbacks, i, _i, _len; 72 | if (!this._callbacks || arguments.length === 0) { 73 | this._callbacks = {}; 74 | return this; 75 | } 76 | callbacks = this._callbacks[event]; 77 | if (!callbacks) { 78 | return this; 79 | } 80 | if (arguments.length === 1) { 81 | delete this._callbacks[event]; 82 | return this; 83 | } 84 | for (i = _i = 0, _len = callbacks.length; _i < _len; i = ++_i) { 85 | callback = callbacks[i]; 86 | if (callback === fn) { 87 | callbacks.splice(i, 1); 88 | break; 89 | } 90 | } 91 | return this; 92 | }; 93 | 94 | return Emitter; 95 | 96 | })(); 97 | 98 | Dropzone = (function(_super) { 99 | var extend, resolveOption; 100 | 101 | __extends(Dropzone, _super); 102 | 103 | Dropzone.prototype.Emitter = Emitter; 104 | 105 | 106 | /* 107 | This is a list of all available events you can register on a dropzone object. 108 | 109 | You can register an event handler like this: 110 | 111 | dropzone.on("dragEnter", function() { }); 112 | */ 113 | 114 | Dropzone.prototype.events = ["drop", "dragstart", "dragend", "dragenter", "dragover", "dragleave", "addedfile", "addedfiles", "removedfile", "thumbnail", "error", "errormultiple", "processing", "processingmultiple", "uploadprogress", "totaluploadprogress", "sending", "sendingmultiple", "success", "successmultiple", "canceled", "canceledmultiple", "complete", "completemultiple", "reset", "maxfilesexceeded", "maxfilesreached", "queuecomplete"]; 115 | 116 | Dropzone.prototype.defaultOptions = { 117 | url: null, 118 | method: "post", 119 | withCredentials: false, 120 | parallelUploads: 2, 121 | uploadMultiple: false, 122 | maxFilesize: 256, 123 | paramName: "file", 124 | createImageThumbnails: true, 125 | maxThumbnailFilesize: 10, 126 | thumbnailWidth: 120, 127 | thumbnailHeight: 120, 128 | filesizeBase: 1000, 129 | maxFiles: null, 130 | params: {}, 131 | clickable: true, 132 | ignoreHiddenFiles: true, 133 | acceptedFiles: null, 134 | acceptedMimeTypes: null, 135 | autoProcessQueue: true, 136 | autoQueue: true, 137 | addRemoveLinks: false, 138 | previewsContainer: null, 139 | hiddenInputContainer: "body", 140 | capture: null, 141 | renameFilename: null, 142 | dictDefaultMessage: "Drop files here to upload", 143 | dictFallbackMessage: "Your browser does not support drag'n'drop file uploads.", 144 | dictFallbackText: "Please use the fallback form below to upload your files like in the olden days.", 145 | dictFileTooBig: "File is too big ({{filesize}}MiB). Max filesize: {{maxFilesize}}MiB.", 146 | dictInvalidFileType: "You can't upload files of this type.", 147 | dictResponseError: "Server responded with {{statusCode}} code.", 148 | dictCancelUpload: "Cancel upload", 149 | dictCancelUploadConfirmation: "Are you sure you want to cancel this upload?", 150 | dictRemoveFile: "Remove file", 151 | dictRemoveFileConfirmation: null, 152 | dictMaxFilesExceeded: "You can not upload any more files.", 153 | accept: function(file, done) { 154 | return done(); 155 | }, 156 | init: function() { 157 | return noop; 158 | }, 159 | forceFallback: false, 160 | fallback: function() { 161 | var child, messageElement, span, _i, _len, _ref; 162 | this.element.className = "" + this.element.className + " dz-browser-not-supported"; 163 | _ref = this.element.getElementsByTagName("div"); 164 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 165 | child = _ref[_i]; 166 | if (/(^| )dz-message($| )/.test(child.className)) { 167 | messageElement = child; 168 | child.className = "dz-message"; 169 | continue; 170 | } 171 | } 172 | if (!messageElement) { 173 | messageElement = Dropzone.createElement("
"); 174 | this.element.appendChild(messageElement); 175 | } 176 | span = messageElement.getElementsByTagName("span")[0]; 177 | if (span) { 178 | if (span.textContent != null) { 179 | span.textContent = this.options.dictFallbackMessage; 180 | } else if (span.innerText != null) { 181 | span.innerText = this.options.dictFallbackMessage; 182 | } 183 | } 184 | return this.element.appendChild(this.getFallbackForm()); 185 | }, 186 | resize: function(file) { 187 | var info, srcRatio, trgRatio; 188 | info = { 189 | srcX: 0, 190 | srcY: 0, 191 | srcWidth: file.width, 192 | srcHeight: file.height 193 | }; 194 | srcRatio = file.width / file.height; 195 | info.optWidth = this.options.thumbnailWidth; 196 | info.optHeight = this.options.thumbnailHeight; 197 | if ((info.optWidth == null) && (info.optHeight == null)) { 198 | info.optWidth = info.srcWidth; 199 | info.optHeight = info.srcHeight; 200 | } else if (info.optWidth == null) { 201 | info.optWidth = srcRatio * info.optHeight; 202 | } else if (info.optHeight == null) { 203 | info.optHeight = (1 / srcRatio) * info.optWidth; 204 | } 205 | trgRatio = info.optWidth / info.optHeight; 206 | if (file.height < info.optHeight || file.width < info.optWidth) { 207 | info.trgHeight = info.srcHeight; 208 | info.trgWidth = info.srcWidth; 209 | } else { 210 | if (srcRatio > trgRatio) { 211 | info.srcHeight = file.height; 212 | info.srcWidth = info.srcHeight * trgRatio; 213 | } else { 214 | info.srcWidth = file.width; 215 | info.srcHeight = info.srcWidth / trgRatio; 216 | } 217 | } 218 | info.srcX = (file.width - info.srcWidth) / 2; 219 | info.srcY = (file.height - info.srcHeight) / 2; 220 | return info; 221 | }, 222 | 223 | /* 224 | Those functions register themselves to the events on init and handle all 225 | the user interface specific stuff. Overwriting them won't break the upload 226 | but can break the way it's displayed. 227 | You can overwrite them if you don't like the default behavior. If you just 228 | want to add an additional event handler, register it on the dropzone object 229 | and don't overwrite those options. 230 | */ 231 | drop: function(e) { 232 | return this.element.classList.remove("dz-drag-hover"); 233 | }, 234 | dragstart: noop, 235 | dragend: function(e) { 236 | return this.element.classList.remove("dz-drag-hover"); 237 | }, 238 | dragenter: function(e) { 239 | return this.element.classList.add("dz-drag-hover"); 240 | }, 241 | dragover: function(e) { 242 | return this.element.classList.add("dz-drag-hover"); 243 | }, 244 | dragleave: function(e) { 245 | return this.element.classList.remove("dz-drag-hover"); 246 | }, 247 | paste: noop, 248 | reset: function() { 249 | return this.element.classList.remove("dz-started"); 250 | }, 251 | addedfile: function(file) { 252 | var node, removeFileEvent, removeLink, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _results; 253 | if (this.element === this.previewsContainer) { 254 | this.element.classList.add("dz-started"); 255 | } 256 | if (this.previewsContainer) { 257 | file.previewElement = Dropzone.createElement(this.options.previewTemplate.trim()); 258 | file.previewTemplate = file.previewElement; 259 | this.previewsContainer.appendChild(file.previewElement); 260 | _ref = file.previewElement.querySelectorAll("[data-dz-name]"); 261 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 262 | node = _ref[_i]; 263 | node.textContent = this._renameFilename(file.name); 264 | } 265 | _ref1 = file.previewElement.querySelectorAll("[data-dz-size]"); 266 | for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { 267 | node = _ref1[_j]; 268 | node.innerHTML = this.filesize(file.size); 269 | } 270 | if (this.options.addRemoveLinks) { 271 | file._removeLink = Dropzone.createElement("" + this.options.dictRemoveFile + ""); 272 | file.previewElement.appendChild(file._removeLink); 273 | } 274 | removeFileEvent = (function(_this) { 275 | return function(e) { 276 | e.preventDefault(); 277 | e.stopPropagation(); 278 | if (file.status === Dropzone.UPLOADING) { 279 | return Dropzone.confirm(_this.options.dictCancelUploadConfirmation, function() { 280 | return _this.removeFile(file); 281 | }); 282 | } else { 283 | if (_this.options.dictRemoveFileConfirmation) { 284 | return Dropzone.confirm(_this.options.dictRemoveFileConfirmation, function() { 285 | return _this.removeFile(file); 286 | }); 287 | } else { 288 | return _this.removeFile(file); 289 | } 290 | } 291 | }; 292 | })(this); 293 | _ref2 = file.previewElement.querySelectorAll("[data-dz-remove]"); 294 | _results = []; 295 | for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { 296 | removeLink = _ref2[_k]; 297 | _results.push(removeLink.addEventListener("click", removeFileEvent)); 298 | } 299 | return _results; 300 | } 301 | }, 302 | removedfile: function(file) { 303 | var _ref; 304 | if (file.previewElement) { 305 | if ((_ref = file.previewElement) != null) { 306 | _ref.parentNode.removeChild(file.previewElement); 307 | } 308 | } 309 | return this._updateMaxFilesReachedClass(); 310 | }, 311 | thumbnail: function(file, dataUrl) { 312 | var thumbnailElement, _i, _len, _ref; 313 | if (file.previewElement) { 314 | file.previewElement.classList.remove("dz-file-preview"); 315 | _ref = file.previewElement.querySelectorAll("[data-dz-thumbnail]"); 316 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 317 | thumbnailElement = _ref[_i]; 318 | thumbnailElement.alt = file.name; 319 | thumbnailElement.src = dataUrl; 320 | } 321 | return setTimeout(((function(_this) { 322 | return function() { 323 | return file.previewElement.classList.add("dz-image-preview"); 324 | }; 325 | })(this)), 1); 326 | } 327 | }, 328 | error: function(file, message) { 329 | var node, _i, _len, _ref, _results; 330 | if (file.previewElement) { 331 | file.previewElement.classList.add("dz-error"); 332 | if (typeof message !== "String" && message.error) { 333 | message = message.error; 334 | } 335 | _ref = file.previewElement.querySelectorAll("[data-dz-errormessage]"); 336 | _results = []; 337 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 338 | node = _ref[_i]; 339 | _results.push(node.textContent = message); 340 | } 341 | return _results; 342 | } 343 | }, 344 | errormultiple: noop, 345 | processing: function(file) { 346 | if (file.previewElement) { 347 | file.previewElement.classList.add("dz-processing"); 348 | if (file._removeLink) { 349 | return file._removeLink.textContent = this.options.dictCancelUpload; 350 | } 351 | } 352 | }, 353 | processingmultiple: noop, 354 | uploadprogress: function(file, progress, bytesSent) { 355 | var node, _i, _len, _ref, _results; 356 | if (file.previewElement) { 357 | _ref = file.previewElement.querySelectorAll("[data-dz-uploadprogress]"); 358 | _results = []; 359 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 360 | node = _ref[_i]; 361 | if (node.nodeName === 'PROGRESS') { 362 | _results.push(node.value = progress); 363 | } else { 364 | _results.push(node.style.width = "" + progress + "%"); 365 | } 366 | } 367 | return _results; 368 | } 369 | }, 370 | totaluploadprogress: noop, 371 | sending: noop, 372 | sendingmultiple: noop, 373 | success: function(file) { 374 | if (file.previewElement) { 375 | return file.previewElement.classList.add("dz-success"); 376 | } 377 | }, 378 | successmultiple: noop, 379 | canceled: function(file) { 380 | return this.emit("error", file, "Upload canceled."); 381 | }, 382 | canceledmultiple: noop, 383 | complete: function(file) { 384 | if (file._removeLink) { 385 | file._removeLink.textContent = this.options.dictRemoveFile; 386 | } 387 | if (file.previewElement) { 388 | return file.previewElement.classList.add("dz-complete"); 389 | } 390 | }, 391 | completemultiple: noop, 392 | maxfilesexceeded: noop, 393 | maxfilesreached: noop, 394 | queuecomplete: noop, 395 | addedfiles: noop, 396 | previewTemplate: "
\n
\n
\n
\n
\n
\n
\n
\n
\n \n Check\n \n \n \n \n \n
\n
\n \n Error\n \n \n \n \n \n \n \n
\n
" 397 | }; 398 | 399 | extend = function() { 400 | var key, object, objects, target, val, _i, _len; 401 | target = arguments[0], objects = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 402 | for (_i = 0, _len = objects.length; _i < _len; _i++) { 403 | object = objects[_i]; 404 | for (key in object) { 405 | val = object[key]; 406 | target[key] = val; 407 | } 408 | } 409 | return target; 410 | }; 411 | 412 | function Dropzone(element, options) { 413 | var elementOptions, fallback, _ref; 414 | this.element = element; 415 | this.version = Dropzone.version; 416 | this.defaultOptions.previewTemplate = this.defaultOptions.previewTemplate.replace(/\n*/g, ""); 417 | this.clickableElements = []; 418 | this.listeners = []; 419 | this.files = []; 420 | if (typeof this.element === "string") { 421 | this.element = document.querySelector(this.element); 422 | } 423 | if (!(this.element && (this.element.nodeType != null))) { 424 | throw new Error("Invalid dropzone element."); 425 | } 426 | if (this.element.dropzone) { 427 | throw new Error("Dropzone already attached."); 428 | } 429 | Dropzone.instances.push(this); 430 | this.element.dropzone = this; 431 | elementOptions = (_ref = Dropzone.optionsForElement(this.element)) != null ? _ref : {}; 432 | this.options = extend({}, this.defaultOptions, elementOptions, options != null ? options : {}); 433 | if (this.options.forceFallback || !Dropzone.isBrowserSupported()) { 434 | return this.options.fallback.call(this); 435 | } 436 | if (this.options.url == null) { 437 | this.options.url = this.element.getAttribute("action"); 438 | } 439 | if (!this.options.url) { 440 | throw new Error("No URL provided."); 441 | } 442 | if (this.options.acceptedFiles && this.options.acceptedMimeTypes) { 443 | throw new Error("You can't provide both 'acceptedFiles' and 'acceptedMimeTypes'. 'acceptedMimeTypes' is deprecated."); 444 | } 445 | if (this.options.acceptedMimeTypes) { 446 | this.options.acceptedFiles = this.options.acceptedMimeTypes; 447 | delete this.options.acceptedMimeTypes; 448 | } 449 | this.options.method = this.options.method.toUpperCase(); 450 | if ((fallback = this.getExistingFallback()) && fallback.parentNode) { 451 | fallback.parentNode.removeChild(fallback); 452 | } 453 | if (this.options.previewsContainer !== false) { 454 | if (this.options.previewsContainer) { 455 | this.previewsContainer = Dropzone.getElement(this.options.previewsContainer, "previewsContainer"); 456 | } else { 457 | this.previewsContainer = this.element; 458 | } 459 | } 460 | if (this.options.clickable) { 461 | if (this.options.clickable === true) { 462 | this.clickableElements = [this.element]; 463 | } else { 464 | this.clickableElements = Dropzone.getElements(this.options.clickable, "clickable"); 465 | } 466 | } 467 | this.init(); 468 | } 469 | 470 | Dropzone.prototype.getAcceptedFiles = function() { 471 | var file, _i, _len, _ref, _results; 472 | _ref = this.files; 473 | _results = []; 474 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 475 | file = _ref[_i]; 476 | if (file.accepted) { 477 | _results.push(file); 478 | } 479 | } 480 | return _results; 481 | }; 482 | 483 | Dropzone.prototype.getRejectedFiles = function() { 484 | var file, _i, _len, _ref, _results; 485 | _ref = this.files; 486 | _results = []; 487 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 488 | file = _ref[_i]; 489 | if (!file.accepted) { 490 | _results.push(file); 491 | } 492 | } 493 | return _results; 494 | }; 495 | 496 | Dropzone.prototype.getFilesWithStatus = function(status) { 497 | var file, _i, _len, _ref, _results; 498 | _ref = this.files; 499 | _results = []; 500 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 501 | file = _ref[_i]; 502 | if (file.status === status) { 503 | _results.push(file); 504 | } 505 | } 506 | return _results; 507 | }; 508 | 509 | Dropzone.prototype.getQueuedFiles = function() { 510 | return this.getFilesWithStatus(Dropzone.QUEUED); 511 | }; 512 | 513 | Dropzone.prototype.getUploadingFiles = function() { 514 | return this.getFilesWithStatus(Dropzone.UPLOADING); 515 | }; 516 | 517 | Dropzone.prototype.getAddedFiles = function() { 518 | return this.getFilesWithStatus(Dropzone.ADDED); 519 | }; 520 | 521 | Dropzone.prototype.getActiveFiles = function() { 522 | var file, _i, _len, _ref, _results; 523 | _ref = this.files; 524 | _results = []; 525 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 526 | file = _ref[_i]; 527 | if (file.status === Dropzone.UPLOADING || file.status === Dropzone.QUEUED) { 528 | _results.push(file); 529 | } 530 | } 531 | return _results; 532 | }; 533 | 534 | Dropzone.prototype.init = function() { 535 | var eventName, noPropagation, setupHiddenFileInput, _i, _len, _ref, _ref1; 536 | if (this.element.tagName === "form") { 537 | this.element.setAttribute("enctype", "multipart/form-data"); 538 | } 539 | if (this.element.classList.contains("dropzone") && !this.element.querySelector(".dz-message")) { 540 | this.element.appendChild(Dropzone.createElement("
" + this.options.dictDefaultMessage + "
")); 541 | } 542 | if (this.clickableElements.length) { 543 | setupHiddenFileInput = (function(_this) { 544 | return function() { 545 | if (_this.hiddenFileInput) { 546 | _this.hiddenFileInput.parentNode.removeChild(_this.hiddenFileInput); 547 | } 548 | _this.hiddenFileInput = document.createElement("input"); 549 | _this.hiddenFileInput.setAttribute("type", "file"); 550 | if ((_this.options.maxFiles == null) || _this.options.maxFiles > 1) { 551 | _this.hiddenFileInput.setAttribute("multiple", "multiple"); 552 | } 553 | _this.hiddenFileInput.className = "dz-hidden-input"; 554 | if (_this.options.acceptedFiles != null) { 555 | _this.hiddenFileInput.setAttribute("accept", _this.options.acceptedFiles); 556 | } 557 | if (_this.options.capture != null) { 558 | _this.hiddenFileInput.setAttribute("capture", _this.options.capture); 559 | } 560 | _this.hiddenFileInput.style.visibility = "hidden"; 561 | _this.hiddenFileInput.style.position = "absolute"; 562 | _this.hiddenFileInput.style.top = "0"; 563 | _this.hiddenFileInput.style.left = "0"; 564 | _this.hiddenFileInput.style.height = "0"; 565 | _this.hiddenFileInput.style.width = "0"; 566 | document.querySelector(_this.options.hiddenInputContainer).appendChild(_this.hiddenFileInput); 567 | return _this.hiddenFileInput.addEventListener("change", function() { 568 | var file, files, _i, _len; 569 | files = _this.hiddenFileInput.files; 570 | if (files.length) { 571 | for (_i = 0, _len = files.length; _i < _len; _i++) { 572 | file = files[_i]; 573 | _this.addFile(file); 574 | } 575 | } 576 | _this.emit("addedfiles", files); 577 | return setupHiddenFileInput(); 578 | }); 579 | }; 580 | })(this); 581 | setupHiddenFileInput(); 582 | } 583 | this.URL = (_ref = window.URL) != null ? _ref : window.webkitURL; 584 | _ref1 = this.events; 585 | for (_i = 0, _len = _ref1.length; _i < _len; _i++) { 586 | eventName = _ref1[_i]; 587 | this.on(eventName, this.options[eventName]); 588 | } 589 | this.on("uploadprogress", (function(_this) { 590 | return function() { 591 | return _this.updateTotalUploadProgress(); 592 | }; 593 | })(this)); 594 | this.on("removedfile", (function(_this) { 595 | return function() { 596 | return _this.updateTotalUploadProgress(); 597 | }; 598 | })(this)); 599 | this.on("canceled", (function(_this) { 600 | return function(file) { 601 | return _this.emit("complete", file); 602 | }; 603 | })(this)); 604 | this.on("complete", (function(_this) { 605 | return function(file) { 606 | if (_this.getAddedFiles().length === 0 && _this.getUploadingFiles().length === 0 && _this.getQueuedFiles().length === 0) { 607 | return setTimeout((function() { 608 | return _this.emit("queuecomplete"); 609 | }), 0); 610 | } 611 | }; 612 | })(this)); 613 | noPropagation = function(e) { 614 | e.stopPropagation(); 615 | if (e.preventDefault) { 616 | return e.preventDefault(); 617 | } else { 618 | return e.returnValue = false; 619 | } 620 | }; 621 | this.listeners = [ 622 | { 623 | element: this.element, 624 | events: { 625 | "dragstart": (function(_this) { 626 | return function(e) { 627 | return _this.emit("dragstart", e); 628 | }; 629 | })(this), 630 | "dragenter": (function(_this) { 631 | return function(e) { 632 | noPropagation(e); 633 | return _this.emit("dragenter", e); 634 | }; 635 | })(this), 636 | "dragover": (function(_this) { 637 | return function(e) { 638 | var efct; 639 | try { 640 | efct = e.dataTransfer.effectAllowed; 641 | } catch (_error) {} 642 | e.dataTransfer.dropEffect = 'move' === efct || 'linkMove' === efct ? 'move' : 'copy'; 643 | noPropagation(e); 644 | return _this.emit("dragover", e); 645 | }; 646 | })(this), 647 | "dragleave": (function(_this) { 648 | return function(e) { 649 | return _this.emit("dragleave", e); 650 | }; 651 | })(this), 652 | "drop": (function(_this) { 653 | return function(e) { 654 | noPropagation(e); 655 | return _this.drop(e); 656 | }; 657 | })(this), 658 | "dragend": (function(_this) { 659 | return function(e) { 660 | return _this.emit("dragend", e); 661 | }; 662 | })(this) 663 | } 664 | } 665 | ]; 666 | this.clickableElements.forEach((function(_this) { 667 | return function(clickableElement) { 668 | return _this.listeners.push({ 669 | element: clickableElement, 670 | events: { 671 | "click": function(evt) { 672 | if ((clickableElement !== _this.element) || (evt.target === _this.element || Dropzone.elementInside(evt.target, _this.element.querySelector(".dz-message")))) { 673 | _this.hiddenFileInput.click(); 674 | } 675 | return true; 676 | } 677 | } 678 | }); 679 | }; 680 | })(this)); 681 | this.enable(); 682 | return this.options.init.call(this); 683 | }; 684 | 685 | Dropzone.prototype.destroy = function() { 686 | var _ref; 687 | this.disable(); 688 | this.removeAllFiles(true); 689 | if ((_ref = this.hiddenFileInput) != null ? _ref.parentNode : void 0) { 690 | this.hiddenFileInput.parentNode.removeChild(this.hiddenFileInput); 691 | this.hiddenFileInput = null; 692 | } 693 | delete this.element.dropzone; 694 | return Dropzone.instances.splice(Dropzone.instances.indexOf(this), 1); 695 | }; 696 | 697 | Dropzone.prototype.updateTotalUploadProgress = function() { 698 | var activeFiles, file, totalBytes, totalBytesSent, totalUploadProgress, _i, _len, _ref; 699 | totalBytesSent = 0; 700 | totalBytes = 0; 701 | activeFiles = this.getActiveFiles(); 702 | if (activeFiles.length) { 703 | _ref = this.getActiveFiles(); 704 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 705 | file = _ref[_i]; 706 | totalBytesSent += file.upload.bytesSent; 707 | totalBytes += file.upload.total; 708 | } 709 | totalUploadProgress = 100 * totalBytesSent / totalBytes; 710 | } else { 711 | totalUploadProgress = 100; 712 | } 713 | return this.emit("totaluploadprogress", totalUploadProgress, totalBytes, totalBytesSent); 714 | }; 715 | 716 | Dropzone.prototype._getParamName = function(n) { 717 | if (typeof this.options.paramName === "function") { 718 | return this.options.paramName(n); 719 | } else { 720 | return "" + this.options.paramName + (this.options.uploadMultiple ? "[" + n + "]" : ""); 721 | } 722 | }; 723 | 724 | Dropzone.prototype._renameFilename = function(name) { 725 | if (typeof this.options.renameFilename !== "function") { 726 | return name; 727 | } 728 | return this.options.renameFilename(name); 729 | }; 730 | 731 | Dropzone.prototype.getFallbackForm = function() { 732 | var existingFallback, fields, fieldsString, form; 733 | if (existingFallback = this.getExistingFallback()) { 734 | return existingFallback; 735 | } 736 | fieldsString = "
"; 737 | if (this.options.dictFallbackText) { 738 | fieldsString += "

" + this.options.dictFallbackText + "

"; 739 | } 740 | fieldsString += "
"; 741 | fields = Dropzone.createElement(fieldsString); 742 | if (this.element.tagName !== "FORM") { 743 | form = Dropzone.createElement("
"); 744 | form.appendChild(fields); 745 | } else { 746 | this.element.setAttribute("enctype", "multipart/form-data"); 747 | this.element.setAttribute("method", this.options.method); 748 | } 749 | return form != null ? form : fields; 750 | }; 751 | 752 | Dropzone.prototype.getExistingFallback = function() { 753 | var fallback, getFallback, tagName, _i, _len, _ref; 754 | getFallback = function(elements) { 755 | var el, _i, _len; 756 | for (_i = 0, _len = elements.length; _i < _len; _i++) { 757 | el = elements[_i]; 758 | if (/(^| )fallback($| )/.test(el.className)) { 759 | return el; 760 | } 761 | } 762 | }; 763 | _ref = ["div", "form"]; 764 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 765 | tagName = _ref[_i]; 766 | if (fallback = getFallback(this.element.getElementsByTagName(tagName))) { 767 | return fallback; 768 | } 769 | } 770 | }; 771 | 772 | Dropzone.prototype.setupEventListeners = function() { 773 | var elementListeners, event, listener, _i, _len, _ref, _results; 774 | _ref = this.listeners; 775 | _results = []; 776 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 777 | elementListeners = _ref[_i]; 778 | _results.push((function() { 779 | var _ref1, _results1; 780 | _ref1 = elementListeners.events; 781 | _results1 = []; 782 | for (event in _ref1) { 783 | listener = _ref1[event]; 784 | _results1.push(elementListeners.element.addEventListener(event, listener, false)); 785 | } 786 | return _results1; 787 | })()); 788 | } 789 | return _results; 790 | }; 791 | 792 | Dropzone.prototype.removeEventListeners = function() { 793 | var elementListeners, event, listener, _i, _len, _ref, _results; 794 | _ref = this.listeners; 795 | _results = []; 796 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 797 | elementListeners = _ref[_i]; 798 | _results.push((function() { 799 | var _ref1, _results1; 800 | _ref1 = elementListeners.events; 801 | _results1 = []; 802 | for (event in _ref1) { 803 | listener = _ref1[event]; 804 | _results1.push(elementListeners.element.removeEventListener(event, listener, false)); 805 | } 806 | return _results1; 807 | })()); 808 | } 809 | return _results; 810 | }; 811 | 812 | Dropzone.prototype.disable = function() { 813 | var file, _i, _len, _ref, _results; 814 | this.clickableElements.forEach(function(element) { 815 | return element.classList.remove("dz-clickable"); 816 | }); 817 | this.removeEventListeners(); 818 | _ref = this.files; 819 | _results = []; 820 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 821 | file = _ref[_i]; 822 | _results.push(this.cancelUpload(file)); 823 | } 824 | return _results; 825 | }; 826 | 827 | Dropzone.prototype.enable = function() { 828 | this.clickableElements.forEach(function(element) { 829 | return element.classList.add("dz-clickable"); 830 | }); 831 | return this.setupEventListeners(); 832 | }; 833 | 834 | Dropzone.prototype.filesize = function(size) { 835 | var cutoff, i, selectedSize, selectedUnit, unit, units, _i, _len; 836 | selectedSize = 0; 837 | selectedUnit = "b"; 838 | if (size > 0) { 839 | units = ['TB', 'GB', 'MB', 'KB', 'b']; 840 | for (i = _i = 0, _len = units.length; _i < _len; i = ++_i) { 841 | unit = units[i]; 842 | cutoff = Math.pow(this.options.filesizeBase, 4 - i) / 10; 843 | if (size >= cutoff) { 844 | selectedSize = size / Math.pow(this.options.filesizeBase, 4 - i); 845 | selectedUnit = unit; 846 | break; 847 | } 848 | } 849 | selectedSize = Math.round(10 * selectedSize) / 10; 850 | } 851 | return "" + selectedSize + " " + selectedUnit; 852 | }; 853 | 854 | Dropzone.prototype._updateMaxFilesReachedClass = function() { 855 | if ((this.options.maxFiles != null) && this.getAcceptedFiles().length >= this.options.maxFiles) { 856 | if (this.getAcceptedFiles().length === this.options.maxFiles) { 857 | this.emit('maxfilesreached', this.files); 858 | } 859 | return this.element.classList.add("dz-max-files-reached"); 860 | } else { 861 | return this.element.classList.remove("dz-max-files-reached"); 862 | } 863 | }; 864 | 865 | Dropzone.prototype.drop = function(e) { 866 | var files, items; 867 | if (!e.dataTransfer) { 868 | return; 869 | } 870 | this.emit("drop", e); 871 | files = e.dataTransfer.files; 872 | this.emit("addedfiles", files); 873 | if (files.length) { 874 | items = e.dataTransfer.items; 875 | if (items && items.length && (items[0].webkitGetAsEntry != null)) { 876 | this._addFilesFromItems(items); 877 | } else { 878 | this.handleFiles(files); 879 | } 880 | } 881 | }; 882 | 883 | Dropzone.prototype.paste = function(e) { 884 | var items, _ref; 885 | if ((e != null ? (_ref = e.clipboardData) != null ? _ref.items : void 0 : void 0) == null) { 886 | return; 887 | } 888 | this.emit("paste", e); 889 | items = e.clipboardData.items; 890 | if (items.length) { 891 | return this._addFilesFromItems(items); 892 | } 893 | }; 894 | 895 | Dropzone.prototype.handleFiles = function(files) { 896 | var file, _i, _len, _results; 897 | _results = []; 898 | for (_i = 0, _len = files.length; _i < _len; _i++) { 899 | file = files[_i]; 900 | _results.push(this.addFile(file)); 901 | } 902 | return _results; 903 | }; 904 | 905 | Dropzone.prototype._addFilesFromItems = function(items) { 906 | var entry, item, _i, _len, _results; 907 | _results = []; 908 | for (_i = 0, _len = items.length; _i < _len; _i++) { 909 | item = items[_i]; 910 | if ((item.webkitGetAsEntry != null) && (entry = item.webkitGetAsEntry())) { 911 | if (entry.isFile) { 912 | _results.push(this.addFile(item.getAsFile())); 913 | } else if (entry.isDirectory) { 914 | _results.push(this._addFilesFromDirectory(entry, entry.name)); 915 | } else { 916 | _results.push(void 0); 917 | } 918 | } else if (item.getAsFile != null) { 919 | if ((item.kind == null) || item.kind === "file") { 920 | _results.push(this.addFile(item.getAsFile())); 921 | } else { 922 | _results.push(void 0); 923 | } 924 | } else { 925 | _results.push(void 0); 926 | } 927 | } 928 | return _results; 929 | }; 930 | 931 | Dropzone.prototype._addFilesFromDirectory = function(directory, path) { 932 | var dirReader, errorHandler, readEntries; 933 | dirReader = directory.createReader(); 934 | errorHandler = function(error) { 935 | return typeof console !== "undefined" && console !== null ? typeof console.log === "function" ? console.log(error) : void 0 : void 0; 936 | }; 937 | readEntries = (function(_this) { 938 | return function() { 939 | return dirReader.readEntries(function(entries) { 940 | var entry, _i, _len; 941 | if (entries.length > 0) { 942 | for (_i = 0, _len = entries.length; _i < _len; _i++) { 943 | entry = entries[_i]; 944 | if (entry.isFile) { 945 | entry.file(function(file) { 946 | if (_this.options.ignoreHiddenFiles && file.name.substring(0, 1) === '.') { 947 | return; 948 | } 949 | file.fullPath = "" + path + "/" + file.name; 950 | return _this.addFile(file); 951 | }); 952 | } else if (entry.isDirectory) { 953 | _this._addFilesFromDirectory(entry, "" + path + "/" + entry.name); 954 | } 955 | } 956 | readEntries(); 957 | } 958 | return null; 959 | }, errorHandler); 960 | }; 961 | })(this); 962 | return readEntries(); 963 | }; 964 | 965 | Dropzone.prototype.accept = function(file, done) { 966 | if (file.size > this.options.maxFilesize * 1024 * 1024) { 967 | return done(this.options.dictFileTooBig.replace("{{filesize}}", Math.round(file.size / 1024 / 10.24) / 100).replace("{{maxFilesize}}", this.options.maxFilesize)); 968 | } else if (!Dropzone.isValidFile(file, this.options.acceptedFiles)) { 969 | return done(this.options.dictInvalidFileType); 970 | } else if ((this.options.maxFiles != null) && this.getAcceptedFiles().length >= this.options.maxFiles) { 971 | done(this.options.dictMaxFilesExceeded.replace("{{maxFiles}}", this.options.maxFiles)); 972 | return this.emit("maxfilesexceeded", file); 973 | } else { 974 | return this.options.accept.call(this, file, done); 975 | } 976 | }; 977 | 978 | Dropzone.prototype.addFile = function(file) { 979 | file.upload = { 980 | progress: 0, 981 | total: file.size, 982 | bytesSent: 0 983 | }; 984 | this.files.push(file); 985 | file.status = Dropzone.ADDED; 986 | this.emit("addedfile", file); 987 | this._enqueueThumbnail(file); 988 | return this.accept(file, (function(_this) { 989 | return function(error) { 990 | if (error) { 991 | file.accepted = false; 992 | _this._errorProcessing([file], error); 993 | } else { 994 | file.accepted = true; 995 | if (_this.options.autoQueue) { 996 | _this.enqueueFile(file); 997 | } 998 | } 999 | return _this._updateMaxFilesReachedClass(); 1000 | }; 1001 | })(this)); 1002 | }; 1003 | 1004 | Dropzone.prototype.enqueueFiles = function(files) { 1005 | var file, _i, _len; 1006 | for (_i = 0, _len = files.length; _i < _len; _i++) { 1007 | file = files[_i]; 1008 | this.enqueueFile(file); 1009 | } 1010 | return null; 1011 | }; 1012 | 1013 | Dropzone.prototype.enqueueFile = function(file) { 1014 | if (file.status === Dropzone.ADDED && file.accepted === true) { 1015 | file.status = Dropzone.QUEUED; 1016 | if (this.options.autoProcessQueue) { 1017 | return setTimeout(((function(_this) { 1018 | return function() { 1019 | return _this.processQueue(); 1020 | }; 1021 | })(this)), 0); 1022 | } 1023 | } else { 1024 | throw new Error("This file can't be queued because it has already been processed or was rejected."); 1025 | } 1026 | }; 1027 | 1028 | Dropzone.prototype._thumbnailQueue = []; 1029 | 1030 | Dropzone.prototype._processingThumbnail = false; 1031 | 1032 | Dropzone.prototype._enqueueThumbnail = function(file) { 1033 | if (this.options.createImageThumbnails && file.type.match(/image.*/) && file.size <= this.options.maxThumbnailFilesize * 1024 * 1024) { 1034 | this._thumbnailQueue.push(file); 1035 | return setTimeout(((function(_this) { 1036 | return function() { 1037 | return _this._processThumbnailQueue(); 1038 | }; 1039 | })(this)), 0); 1040 | } 1041 | }; 1042 | 1043 | Dropzone.prototype._processThumbnailQueue = function() { 1044 | if (this._processingThumbnail || this._thumbnailQueue.length === 0) { 1045 | return; 1046 | } 1047 | this._processingThumbnail = true; 1048 | return this.createThumbnail(this._thumbnailQueue.shift(), (function(_this) { 1049 | return function() { 1050 | _this._processingThumbnail = false; 1051 | return _this._processThumbnailQueue(); 1052 | }; 1053 | })(this)); 1054 | }; 1055 | 1056 | Dropzone.prototype.removeFile = function(file) { 1057 | if (file.status === Dropzone.UPLOADING) { 1058 | this.cancelUpload(file); 1059 | } 1060 | this.files = without(this.files, file); 1061 | this.emit("removedfile", file); 1062 | if (this.files.length === 0) { 1063 | return this.emit("reset"); 1064 | } 1065 | }; 1066 | 1067 | Dropzone.prototype.removeAllFiles = function(cancelIfNecessary) { 1068 | var file, _i, _len, _ref; 1069 | if (cancelIfNecessary == null) { 1070 | cancelIfNecessary = false; 1071 | } 1072 | _ref = this.files.slice(); 1073 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 1074 | file = _ref[_i]; 1075 | if (file.status !== Dropzone.UPLOADING || cancelIfNecessary) { 1076 | this.removeFile(file); 1077 | } 1078 | } 1079 | return null; 1080 | }; 1081 | 1082 | Dropzone.prototype.createThumbnail = function(file, callback) { 1083 | var fileReader; 1084 | fileReader = new FileReader; 1085 | fileReader.onload = (function(_this) { 1086 | return function() { 1087 | if (file.type === "image/svg+xml") { 1088 | _this.emit("thumbnail", file, fileReader.result); 1089 | if (callback != null) { 1090 | callback(); 1091 | } 1092 | return; 1093 | } 1094 | return _this.createThumbnailFromUrl(file, fileReader.result, callback); 1095 | }; 1096 | })(this); 1097 | return fileReader.readAsDataURL(file); 1098 | }; 1099 | 1100 | Dropzone.prototype.createThumbnailFromUrl = function(file, imageUrl, callback, crossOrigin) { 1101 | var img; 1102 | img = document.createElement("img"); 1103 | if (crossOrigin) { 1104 | img.crossOrigin = crossOrigin; 1105 | } 1106 | img.onload = (function(_this) { 1107 | return function() { 1108 | var canvas, ctx, resizeInfo, thumbnail, _ref, _ref1, _ref2, _ref3; 1109 | file.width = img.width; 1110 | file.height = img.height; 1111 | resizeInfo = _this.options.resize.call(_this, file); 1112 | if (resizeInfo.trgWidth == null) { 1113 | resizeInfo.trgWidth = resizeInfo.optWidth; 1114 | } 1115 | if (resizeInfo.trgHeight == null) { 1116 | resizeInfo.trgHeight = resizeInfo.optHeight; 1117 | } 1118 | canvas = document.createElement("canvas"); 1119 | ctx = canvas.getContext("2d"); 1120 | canvas.width = resizeInfo.trgWidth; 1121 | canvas.height = resizeInfo.trgHeight; 1122 | drawImageIOSFix(ctx, img, (_ref = resizeInfo.srcX) != null ? _ref : 0, (_ref1 = resizeInfo.srcY) != null ? _ref1 : 0, resizeInfo.srcWidth, resizeInfo.srcHeight, (_ref2 = resizeInfo.trgX) != null ? _ref2 : 0, (_ref3 = resizeInfo.trgY) != null ? _ref3 : 0, resizeInfo.trgWidth, resizeInfo.trgHeight); 1123 | thumbnail = canvas.toDataURL("image/png"); 1124 | _this.emit("thumbnail", file, thumbnail); 1125 | if (callback != null) { 1126 | return callback(); 1127 | } 1128 | }; 1129 | })(this); 1130 | if (callback != null) { 1131 | img.onerror = callback; 1132 | } 1133 | return img.src = imageUrl; 1134 | }; 1135 | 1136 | Dropzone.prototype.processQueue = function() { 1137 | var i, parallelUploads, processingLength, queuedFiles; 1138 | parallelUploads = this.options.parallelUploads; 1139 | processingLength = this.getUploadingFiles().length; 1140 | i = processingLength; 1141 | if (processingLength >= parallelUploads) { 1142 | return; 1143 | } 1144 | queuedFiles = this.getQueuedFiles(); 1145 | if (!(queuedFiles.length > 0)) { 1146 | return; 1147 | } 1148 | if (this.options.uploadMultiple) { 1149 | return this.processFiles(queuedFiles.slice(0, parallelUploads - processingLength)); 1150 | } else { 1151 | while (i < parallelUploads) { 1152 | if (!queuedFiles.length) { 1153 | return; 1154 | } 1155 | this.processFile(queuedFiles.shift()); 1156 | i++; 1157 | } 1158 | } 1159 | }; 1160 | 1161 | Dropzone.prototype.processFile = function(file) { 1162 | return this.processFiles([file]); 1163 | }; 1164 | 1165 | Dropzone.prototype.processFiles = function(files) { 1166 | var file, _i, _len; 1167 | for (_i = 0, _len = files.length; _i < _len; _i++) { 1168 | file = files[_i]; 1169 | file.processing = true; 1170 | file.status = Dropzone.UPLOADING; 1171 | this.emit("processing", file); 1172 | } 1173 | if (this.options.uploadMultiple) { 1174 | this.emit("processingmultiple", files); 1175 | } 1176 | return this.uploadFiles(files); 1177 | }; 1178 | 1179 | Dropzone.prototype._getFilesWithXhr = function(xhr) { 1180 | var file, files; 1181 | return files = (function() { 1182 | var _i, _len, _ref, _results; 1183 | _ref = this.files; 1184 | _results = []; 1185 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 1186 | file = _ref[_i]; 1187 | if (file.xhr === xhr) { 1188 | _results.push(file); 1189 | } 1190 | } 1191 | return _results; 1192 | }).call(this); 1193 | }; 1194 | 1195 | Dropzone.prototype.cancelUpload = function(file) { 1196 | var groupedFile, groupedFiles, _i, _j, _len, _len1, _ref; 1197 | if (file.status === Dropzone.UPLOADING) { 1198 | groupedFiles = this._getFilesWithXhr(file.xhr); 1199 | for (_i = 0, _len = groupedFiles.length; _i < _len; _i++) { 1200 | groupedFile = groupedFiles[_i]; 1201 | groupedFile.status = Dropzone.CANCELED; 1202 | } 1203 | file.xhr.abort(); 1204 | for (_j = 0, _len1 = groupedFiles.length; _j < _len1; _j++) { 1205 | groupedFile = groupedFiles[_j]; 1206 | this.emit("canceled", groupedFile); 1207 | } 1208 | if (this.options.uploadMultiple) { 1209 | this.emit("canceledmultiple", groupedFiles); 1210 | } 1211 | } else if ((_ref = file.status) === Dropzone.ADDED || _ref === Dropzone.QUEUED) { 1212 | file.status = Dropzone.CANCELED; 1213 | this.emit("canceled", file); 1214 | if (this.options.uploadMultiple) { 1215 | this.emit("canceledmultiple", [file]); 1216 | } 1217 | } 1218 | if (this.options.autoProcessQueue) { 1219 | return this.processQueue(); 1220 | } 1221 | }; 1222 | 1223 | resolveOption = function() { 1224 | var args, option; 1225 | option = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 1226 | if (typeof option === 'function') { 1227 | return option.apply(this, args); 1228 | } 1229 | return option; 1230 | }; 1231 | 1232 | Dropzone.prototype.uploadFile = function(file) { 1233 | return this.uploadFiles([file]); 1234 | }; 1235 | 1236 | Dropzone.prototype.uploadFiles = function(files) { 1237 | var file, formData, handleError, headerName, headerValue, headers, i, input, inputName, inputType, key, method, option, progressObj, response, updateProgress, url, value, xhr, _i, _j, _k, _l, _len, _len1, _len2, _len3, _m, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; 1238 | xhr = new XMLHttpRequest(); 1239 | for (_i = 0, _len = files.length; _i < _len; _i++) { 1240 | file = files[_i]; 1241 | file.xhr = xhr; 1242 | } 1243 | method = resolveOption(this.options.method, files); 1244 | url = resolveOption(this.options.url, files); 1245 | xhr.open(method, url, true); 1246 | xhr.withCredentials = !!this.options.withCredentials; 1247 | response = null; 1248 | handleError = (function(_this) { 1249 | return function() { 1250 | var _j, _len1, _results; 1251 | _results = []; 1252 | for (_j = 0, _len1 = files.length; _j < _len1; _j++) { 1253 | file = files[_j]; 1254 | _results.push(_this._errorProcessing(files, response || _this.options.dictResponseError.replace("{{statusCode}}", xhr.status), xhr)); 1255 | } 1256 | return _results; 1257 | }; 1258 | })(this); 1259 | updateProgress = (function(_this) { 1260 | return function(e) { 1261 | var allFilesFinished, progress, _j, _k, _l, _len1, _len2, _len3, _results; 1262 | if (e != null) { 1263 | progress = 100 * e.loaded / e.total; 1264 | for (_j = 0, _len1 = files.length; _j < _len1; _j++) { 1265 | file = files[_j]; 1266 | file.upload = { 1267 | progress: progress, 1268 | total: e.total, 1269 | bytesSent: e.loaded 1270 | }; 1271 | } 1272 | } else { 1273 | allFilesFinished = true; 1274 | progress = 100; 1275 | for (_k = 0, _len2 = files.length; _k < _len2; _k++) { 1276 | file = files[_k]; 1277 | if (!(file.upload.progress === 100 && file.upload.bytesSent === file.upload.total)) { 1278 | allFilesFinished = false; 1279 | } 1280 | file.upload.progress = progress; 1281 | file.upload.bytesSent = file.upload.total; 1282 | } 1283 | if (allFilesFinished) { 1284 | return; 1285 | } 1286 | } 1287 | _results = []; 1288 | for (_l = 0, _len3 = files.length; _l < _len3; _l++) { 1289 | file = files[_l]; 1290 | _results.push(_this.emit("uploadprogress", file, progress, file.upload.bytesSent)); 1291 | } 1292 | return _results; 1293 | }; 1294 | })(this); 1295 | xhr.onload = (function(_this) { 1296 | return function(e) { 1297 | var _ref; 1298 | if (files[0].status === Dropzone.CANCELED) { 1299 | return; 1300 | } 1301 | if (xhr.readyState !== 4) { 1302 | return; 1303 | } 1304 | response = xhr.responseText; 1305 | if (xhr.getResponseHeader("content-type") && ~xhr.getResponseHeader("content-type").indexOf("application/json")) { 1306 | try { 1307 | response = JSON.parse(response); 1308 | } catch (_error) { 1309 | e = _error; 1310 | response = "Invalid JSON response from server."; 1311 | } 1312 | } 1313 | updateProgress(); 1314 | if (!((200 <= (_ref = xhr.status) && _ref < 300))) { 1315 | return handleError(); 1316 | } else { 1317 | return _this._finished(files, response, e); 1318 | } 1319 | }; 1320 | })(this); 1321 | xhr.onerror = (function(_this) { 1322 | return function() { 1323 | if (files[0].status === Dropzone.CANCELED) { 1324 | return; 1325 | } 1326 | return handleError(); 1327 | }; 1328 | })(this); 1329 | progressObj = (_ref = xhr.upload) != null ? _ref : xhr; 1330 | progressObj.onprogress = updateProgress; 1331 | headers = { 1332 | "Accept": "application/json", 1333 | "Cache-Control": "no-cache", 1334 | "X-Requested-With": "XMLHttpRequest" 1335 | }; 1336 | if (this.options.headers) { 1337 | extend(headers, this.options.headers); 1338 | } 1339 | for (headerName in headers) { 1340 | headerValue = headers[headerName]; 1341 | if (headerValue) { 1342 | xhr.setRequestHeader(headerName, headerValue); 1343 | } 1344 | } 1345 | formData = new FormData(); 1346 | if (this.options.params) { 1347 | _ref1 = this.options.params; 1348 | for (key in _ref1) { 1349 | value = _ref1[key]; 1350 | formData.append(key, value); 1351 | } 1352 | } 1353 | for (_j = 0, _len1 = files.length; _j < _len1; _j++) { 1354 | file = files[_j]; 1355 | this.emit("sending", file, xhr, formData); 1356 | } 1357 | if (this.options.uploadMultiple) { 1358 | this.emit("sendingmultiple", files, xhr, formData); 1359 | } 1360 | if (this.element.tagName === "FORM") { 1361 | _ref2 = this.element.querySelectorAll("input, textarea, select, button"); 1362 | for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { 1363 | input = _ref2[_k]; 1364 | inputName = input.getAttribute("name"); 1365 | inputType = input.getAttribute("type"); 1366 | if (input.tagName === "SELECT" && input.hasAttribute("multiple")) { 1367 | _ref3 = input.options; 1368 | for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) { 1369 | option = _ref3[_l]; 1370 | if (option.selected) { 1371 | formData.append(inputName, option.value); 1372 | } 1373 | } 1374 | } else if (!inputType || ((_ref4 = inputType.toLowerCase()) !== "checkbox" && _ref4 !== "radio") || input.checked) { 1375 | formData.append(inputName, input.value); 1376 | } 1377 | } 1378 | } 1379 | for (i = _m = 0, _ref5 = files.length - 1; 0 <= _ref5 ? _m <= _ref5 : _m >= _ref5; i = 0 <= _ref5 ? ++_m : --_m) { 1380 | formData.append(this._getParamName(i), files[i], this._renameFilename(files[i].name)); 1381 | } 1382 | return this.submitRequest(xhr, formData, files); 1383 | }; 1384 | 1385 | Dropzone.prototype.submitRequest = function(xhr, formData, files) { 1386 | return xhr.send(formData); 1387 | }; 1388 | 1389 | Dropzone.prototype._finished = function(files, responseText, e) { 1390 | var file, _i, _len; 1391 | for (_i = 0, _len = files.length; _i < _len; _i++) { 1392 | file = files[_i]; 1393 | file.status = Dropzone.SUCCESS; 1394 | this.emit("success", file, responseText, e); 1395 | this.emit("complete", file); 1396 | } 1397 | if (this.options.uploadMultiple) { 1398 | this.emit("successmultiple", files, responseText, e); 1399 | this.emit("completemultiple", files); 1400 | } 1401 | if (this.options.autoProcessQueue) { 1402 | return this.processQueue(); 1403 | } 1404 | }; 1405 | 1406 | Dropzone.prototype._errorProcessing = function(files, message, xhr) { 1407 | var file, _i, _len; 1408 | for (_i = 0, _len = files.length; _i < _len; _i++) { 1409 | file = files[_i]; 1410 | file.status = Dropzone.ERROR; 1411 | this.emit("error", file, message, xhr); 1412 | this.emit("complete", file); 1413 | } 1414 | if (this.options.uploadMultiple) { 1415 | this.emit("errormultiple", files, message, xhr); 1416 | this.emit("completemultiple", files); 1417 | } 1418 | if (this.options.autoProcessQueue) { 1419 | return this.processQueue(); 1420 | } 1421 | }; 1422 | 1423 | return Dropzone; 1424 | 1425 | })(Emitter); 1426 | 1427 | Dropzone.version = "4.3.0"; 1428 | 1429 | Dropzone.options = {}; 1430 | 1431 | Dropzone.optionsForElement = function(element) { 1432 | if (element.getAttribute("id")) { 1433 | return Dropzone.options[camelize(element.getAttribute("id"))]; 1434 | } else { 1435 | return void 0; 1436 | } 1437 | }; 1438 | 1439 | Dropzone.instances = []; 1440 | 1441 | Dropzone.forElement = function(element) { 1442 | if (typeof element === "string") { 1443 | element = document.querySelector(element); 1444 | } 1445 | if ((element != null ? element.dropzone : void 0) == null) { 1446 | throw new Error("No Dropzone found for given element. This is probably because you're trying to access it before Dropzone had the time to initialize. Use the `init` option to setup any additional observers on your Dropzone."); 1447 | } 1448 | return element.dropzone; 1449 | }; 1450 | 1451 | Dropzone.autoDiscover = true; 1452 | 1453 | Dropzone.discover = function() { 1454 | var checkElements, dropzone, dropzones, _i, _len, _results; 1455 | if (document.querySelectorAll) { 1456 | dropzones = document.querySelectorAll(".dropzone"); 1457 | } else { 1458 | dropzones = []; 1459 | checkElements = function(elements) { 1460 | var el, _i, _len, _results; 1461 | _results = []; 1462 | for (_i = 0, _len = elements.length; _i < _len; _i++) { 1463 | el = elements[_i]; 1464 | if (/(^| )dropzone($| )/.test(el.className)) { 1465 | _results.push(dropzones.push(el)); 1466 | } else { 1467 | _results.push(void 0); 1468 | } 1469 | } 1470 | return _results; 1471 | }; 1472 | checkElements(document.getElementsByTagName("div")); 1473 | checkElements(document.getElementsByTagName("form")); 1474 | } 1475 | _results = []; 1476 | for (_i = 0, _len = dropzones.length; _i < _len; _i++) { 1477 | dropzone = dropzones[_i]; 1478 | if (Dropzone.optionsForElement(dropzone) !== false) { 1479 | _results.push(new Dropzone(dropzone)); 1480 | } else { 1481 | _results.push(void 0); 1482 | } 1483 | } 1484 | return _results; 1485 | }; 1486 | 1487 | Dropzone.blacklistedBrowsers = [/opera.*Macintosh.*version\/12/i]; 1488 | 1489 | Dropzone.isBrowserSupported = function() { 1490 | var capableBrowser, regex, _i, _len, _ref; 1491 | capableBrowser = true; 1492 | if (window.File && window.FileReader && window.FileList && window.Blob && window.FormData && document.querySelector) { 1493 | if (!("classList" in document.createElement("a"))) { 1494 | capableBrowser = false; 1495 | } else { 1496 | _ref = Dropzone.blacklistedBrowsers; 1497 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 1498 | regex = _ref[_i]; 1499 | if (regex.test(navigator.userAgent)) { 1500 | capableBrowser = false; 1501 | continue; 1502 | } 1503 | } 1504 | } 1505 | } else { 1506 | capableBrowser = false; 1507 | } 1508 | return capableBrowser; 1509 | }; 1510 | 1511 | without = function(list, rejectedItem) { 1512 | var item, _i, _len, _results; 1513 | _results = []; 1514 | for (_i = 0, _len = list.length; _i < _len; _i++) { 1515 | item = list[_i]; 1516 | if (item !== rejectedItem) { 1517 | _results.push(item); 1518 | } 1519 | } 1520 | return _results; 1521 | }; 1522 | 1523 | camelize = function(str) { 1524 | return str.replace(/[\-_](\w)/g, function(match) { 1525 | return match.charAt(1).toUpperCase(); 1526 | }); 1527 | }; 1528 | 1529 | Dropzone.createElement = function(string) { 1530 | var div; 1531 | div = document.createElement("div"); 1532 | div.innerHTML = string; 1533 | return div.childNodes[0]; 1534 | }; 1535 | 1536 | Dropzone.elementInside = function(element, container) { 1537 | if (element === container) { 1538 | return true; 1539 | } 1540 | while (element = element.parentNode) { 1541 | if (element === container) { 1542 | return true; 1543 | } 1544 | } 1545 | return false; 1546 | }; 1547 | 1548 | Dropzone.getElement = function(el, name) { 1549 | var element; 1550 | if (typeof el === "string") { 1551 | element = document.querySelector(el); 1552 | } else if (el.nodeType != null) { 1553 | element = el; 1554 | } 1555 | if (element == null) { 1556 | throw new Error("Invalid `" + name + "` option provided. Please provide a CSS selector or a plain HTML element."); 1557 | } 1558 | return element; 1559 | }; 1560 | 1561 | Dropzone.getElements = function(els, name) { 1562 | var e, el, elements, _i, _j, _len, _len1, _ref; 1563 | if (els instanceof Array) { 1564 | elements = []; 1565 | try { 1566 | for (_i = 0, _len = els.length; _i < _len; _i++) { 1567 | el = els[_i]; 1568 | elements.push(this.getElement(el, name)); 1569 | } 1570 | } catch (_error) { 1571 | e = _error; 1572 | elements = null; 1573 | } 1574 | } else if (typeof els === "string") { 1575 | elements = []; 1576 | _ref = document.querySelectorAll(els); 1577 | for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { 1578 | el = _ref[_j]; 1579 | elements.push(el); 1580 | } 1581 | } else if (els.nodeType != null) { 1582 | elements = [els]; 1583 | } 1584 | if (!((elements != null) && elements.length)) { 1585 | throw new Error("Invalid `" + name + "` option provided. Please provide a CSS selector, a plain HTML element or a list of those."); 1586 | } 1587 | return elements; 1588 | }; 1589 | 1590 | Dropzone.confirm = function(question, accepted, rejected) { 1591 | if (window.confirm(question)) { 1592 | return accepted(); 1593 | } else if (rejected != null) { 1594 | return rejected(); 1595 | } 1596 | }; 1597 | 1598 | Dropzone.isValidFile = function(file, acceptedFiles) { 1599 | var baseMimeType, mimeType, validType, _i, _len; 1600 | if (!acceptedFiles) { 1601 | return true; 1602 | } 1603 | acceptedFiles = acceptedFiles.split(","); 1604 | mimeType = file.type; 1605 | baseMimeType = mimeType.replace(/\/.*$/, ""); 1606 | for (_i = 0, _len = acceptedFiles.length; _i < _len; _i++) { 1607 | validType = acceptedFiles[_i]; 1608 | validType = validType.trim(); 1609 | if (validType.charAt(0) === ".") { 1610 | if (file.name.toLowerCase().indexOf(validType.toLowerCase(), file.name.length - validType.length) !== -1) { 1611 | return true; 1612 | } 1613 | } else if (/\/\*$/.test(validType)) { 1614 | if (baseMimeType === validType.replace(/\/.*$/, "")) { 1615 | return true; 1616 | } 1617 | } else { 1618 | if (mimeType === validType) { 1619 | return true; 1620 | } 1621 | } 1622 | } 1623 | return false; 1624 | }; 1625 | 1626 | if (typeof jQuery !== "undefined" && jQuery !== null) { 1627 | jQuery.fn.dropzone = function(options) { 1628 | return this.each(function() { 1629 | return new Dropzone(this, options); 1630 | }); 1631 | }; 1632 | } 1633 | 1634 | if (typeof module !== "undefined" && module !== null) { 1635 | module.exports = Dropzone; 1636 | } else { 1637 | window.Dropzone = Dropzone; 1638 | } 1639 | 1640 | Dropzone.ADDED = "added"; 1641 | 1642 | Dropzone.QUEUED = "queued"; 1643 | 1644 | Dropzone.ACCEPTED = Dropzone.QUEUED; 1645 | 1646 | Dropzone.UPLOADING = "uploading"; 1647 | 1648 | Dropzone.PROCESSING = Dropzone.UPLOADING; 1649 | 1650 | Dropzone.CANCELED = "canceled"; 1651 | 1652 | Dropzone.ERROR = "error"; 1653 | 1654 | Dropzone.SUCCESS = "success"; 1655 | 1656 | 1657 | /* 1658 | 1659 | Bugfix for iOS 6 and 7 1660 | Source: http://stackoverflow.com/questions/11929099/html5-canvas-drawimage-ratio-bug-ios 1661 | based on the work of https://github.com/stomita/ios-imagefile-megapixel 1662 | */ 1663 | 1664 | detectVerticalSquash = function(img) { 1665 | var alpha, canvas, ctx, data, ey, ih, iw, py, ratio, sy; 1666 | iw = img.naturalWidth; 1667 | ih = img.naturalHeight; 1668 | canvas = document.createElement("canvas"); 1669 | canvas.width = 1; 1670 | canvas.height = ih; 1671 | ctx = canvas.getContext("2d"); 1672 | ctx.drawImage(img, 0, 0); 1673 | data = ctx.getImageData(0, 0, 1, ih).data; 1674 | sy = 0; 1675 | ey = ih; 1676 | py = ih; 1677 | while (py > sy) { 1678 | alpha = data[(py - 1) * 4 + 3]; 1679 | if (alpha === 0) { 1680 | ey = py; 1681 | } else { 1682 | sy = py; 1683 | } 1684 | py = (ey + sy) >> 1; 1685 | } 1686 | ratio = py / ih; 1687 | if (ratio === 0) { 1688 | return 1; 1689 | } else { 1690 | return ratio; 1691 | } 1692 | }; 1693 | 1694 | drawImageIOSFix = function(ctx, img, sx, sy, sw, sh, dx, dy, dw, dh) { 1695 | var vertSquashRatio; 1696 | vertSquashRatio = detectVerticalSquash(img); 1697 | return ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh / vertSquashRatio); 1698 | }; 1699 | 1700 | 1701 | /* 1702 | * contentloaded.js 1703 | * 1704 | * Author: Diego Perini (diego.perini at gmail.com) 1705 | * Summary: cross-browser wrapper for DOMContentLoaded 1706 | * Updated: 20101020 1707 | * License: MIT 1708 | * Version: 1.2 1709 | * 1710 | * URL: 1711 | * http://javascript.nwbox.com/ContentLoaded/ 1712 | * http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE 1713 | */ 1714 | 1715 | contentLoaded = function(win, fn) { 1716 | var add, doc, done, init, poll, pre, rem, root, top; 1717 | done = false; 1718 | top = true; 1719 | doc = win.document; 1720 | root = doc.documentElement; 1721 | add = (doc.addEventListener ? "addEventListener" : "attachEvent"); 1722 | rem = (doc.addEventListener ? "removeEventListener" : "detachEvent"); 1723 | pre = (doc.addEventListener ? "" : "on"); 1724 | init = function(e) { 1725 | if (e.type === "readystatechange" && doc.readyState !== "complete") { 1726 | return; 1727 | } 1728 | (e.type === "load" ? win : doc)[rem](pre + e.type, init, false); 1729 | if (!done && (done = true)) { 1730 | return fn.call(win, e.type || e); 1731 | } 1732 | }; 1733 | poll = function() { 1734 | var e; 1735 | try { 1736 | root.doScroll("left"); 1737 | } catch (_error) { 1738 | e = _error; 1739 | setTimeout(poll, 50); 1740 | return; 1741 | } 1742 | return init("poll"); 1743 | }; 1744 | if (doc.readyState !== "complete") { 1745 | if (doc.createEventObject && root.doScroll) { 1746 | try { 1747 | top = !win.frameElement; 1748 | } catch (_error) {} 1749 | if (top) { 1750 | poll(); 1751 | } 1752 | } 1753 | doc[add](pre + "DOMContentLoaded", init, false); 1754 | doc[add](pre + "readystatechange", init, false); 1755 | return win[add](pre + "load", init, false); 1756 | } 1757 | }; 1758 | 1759 | Dropzone._autoDiscoverFunction = function() { 1760 | if (Dropzone.autoDiscover) { 1761 | return Dropzone.discover(); 1762 | } 1763 | }; 1764 | 1765 | contentLoaded(window, Dropzone._autoDiscoverFunction); 1766 | 1767 | }).call(this); 1768 | -------------------------------------------------------------------------------- /dropzone.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Feathersjs File Upload 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 37 | 38 | 39 |

Let's upload some files!

40 |
43 | 44 | 45 | -------------------------------------------------------------------------------- /feathersUpload.md: -------------------------------------------------------------------------------- 1 | # File uploads in Feathersjs 2 | 3 | Over the last months we at [ciancoders.com](https://ciancoders.com/) have been working in a new SPA project using Feathers and React, the combination of those two turns out to be **just amazing**. 4 | 5 | Recently we where struggling to find a way to upload files whitout having to write a separate Express middleware or having to (re)write a complex Feathers service. 6 | 7 | # Our Goals 8 | We want to implement an upload service in a way that it acomplishes these few important things: 9 | 10 | 1. It has to handle large files (+10MB). 11 | 2. Needs to work with the app's authetication and authorization. 12 | 3. The files has to be validated. 13 | 4. By the moment there is no third party storage service involved, but this will change in the near future. 14 | 5. Has to show the upload progress. 15 | 16 | The plan is to upload the files to a feathers service, so we can take advantage of the hooks for authentication, authorization and validation, not to mention the service events. 17 | 18 | Fortunately, there is an already developed file storage service: [feathers-blob](https://github.com/feathersjs/feathers-blob). With it we can easily archieve our goals, but (spoiler alert) it isn't the ideal solution, as it has some problems we will discuss below. 19 | 20 | 21 | ## Basic upload with feathers-blob and feathers-client 22 | 23 | For the sake of simplicity, we will be working over a very basic feathers server, with just the upload service. 24 | 25 | Lets look at the server code: 26 | 27 | ```javascript 28 | /* --- server.js --- */ 29 | 30 | const feathers = require('feathers'); 31 | const rest = require('feathers-rest'); 32 | const socketio = require('feathers-socketio'); 33 | const hooks = require('feathers-hooks'); 34 | const bodyParser = require('body-parser'); 35 | const handler = require('feathers-errors/handler'); 36 | 37 | 38 | // feathers-blob service 39 | const blobService = require('feathers-blob'); 40 | // Here we initialize a FileSystem storage, 41 | // but you can use feathers-blob with any other 42 | // storage service like AWS or Google Drive. 43 | const fs = require('fs-blob-store'); 44 | const blobStorage = fs(__dirname + '/uploads'); 45 | 46 | 47 | // Feathers app 48 | const app = feathers(); 49 | 50 | // Parse HTTP JSON bodies 51 | app.use(bodyParser.json()); 52 | // Parse URL-encoded params 53 | app.use(bodyParser.urlencoded({ extended: true })); 54 | // Register hooks module 55 | app.configure(hooks()); 56 | // Add REST API support 57 | app.configure(rest()); 58 | // Configure Socket.io real-time APIs 59 | app.configure(socketio()); 60 | 61 | 62 | // Upload Service 63 | app.use('/uploads', blobService({Model: blobStorage})); 64 | 65 | 66 | // Register a nicer error handler than the default Express one 67 | app.use(handler()); 68 | 69 | // Start the server 70 | app.listen(3030, function(){ 71 | console.log('Feathers app started at localhost:3030') 72 | }); 73 | ``` 74 | 75 | Now go ahead and POST something to localhost:3030/uploads`, for example with postman: 76 | 77 | ```json 78 | { 79 | "uri": "data:image/gif;base64,R0lGODlhEwATAPcAAP/+//7/////+////fvzYvryYvvzZ/fxg/zxWfvxW/zwXPrtW/vxXvfrXv3xYvrvYvntYvnvY/ruZPrwZPfsZPjsZfjtZvfsZvHmY/zxavftaPrvavjuafzxbfnua/jta/ftbP3yb/zzcPvwb/zzcfvxcfzxc/3zdf3zdv70efvwd/rwd/vwefftd/3yfPvxfP70f/zzfvnwffvzf/rxf/rxgPjvgPjvgfnwhPvzhvjvhv71jfz0kPrykvz0mv72nvblTPnnUPjoUPrpUvnnUfnpUvXlUfnpU/npVPnqVPfnU/3uVvvsWPfpVvnqWfrrXPLiW/nrX/vtYv7xavrta/Hlcvnuf/Pphvbsif3zk/zzlPzylfjuk/z0o/LqnvbhSPbhSfjiS/jlS/jjTPfhTfjlTubUU+/iiPPokfrvl/Dll/ftovLWPfHXPvHZP/PbQ/bcRuDJP/PaRvjgSffdSe3ddu7fge7fi+zkuO7NMvPTOt2/Nu7SO+3OO/PWQdnGbOneqeneqvDqyu3JMuvJMu7KNfHNON7GZdnEbejanObXnOW8JOa9KOvCLOnBK9+4Ku3FL9ayKuzEMcenK9e+XODOiePSkODOkOW3ItisI9yxL+a9NtGiHr+VH5h5JsSfNM2bGN6rMJt4JMOYL5h4JZl5Jph3Jpl4J5h5J5h3KJl4KZp5Ks+sUN7Gi96lLL+PKMmbMZt2Jpp3Jpt3KZl4K7qFFdyiKdufKsedRdm7feOpQN2QKMKENrpvJbFfIrNjJL1mLMBpLr9oLrFhK69bJFkpE1kpFYNeTqFEIlsoFbmlnlsmFFwpGFkoF/////7+/v///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAANAALAAAAAATABMAAAj/AKEJHCgokKJKlhThGciQYSIva7r8SHPFzqGGAwPd4bKlh5YsPKy0qFLnT0NAaHTcsIHDho0aKkaAwGCGEkM1NmSkIjWLBosVJT6cOjUrzsBKPl54KmYsACoTMmk1WwaA1CRoeM7siJEqmTIAsjp40ICK2bEApfZcsoQlxwxRzgI8W8XhgoVYA+Kq6sMK0QEYKVCUkoVqQwQJFTwFEAAAFZ9PlFy4OEEiRIYJD55EodDA1ClTbPp0okRFxBQDBRgskAKhiRMlc+Sw4SNpFCIoBBwkUMBkCBIiY8qAgcPG0KBHrBTFQbCEV5EjQYQACfNFjp5CgxpxagVtUhIjwzaJYSHzhQ4cP3ryQHLEqJbASnu+6EIW6o2b2X0ISXK0CFSugazs0YYmwQhziyuE2PLLIv3h0hArkRhiCCzAENOLL7tgAoqDGLXSSSaPMLIIJpmAUst/GA3UCiuv1PIKLtw1FBAAOw==" 80 | } 81 | ``` 82 | 83 | The service will respond with something like this: 84 | 85 | ```json 86 | { 87 | "id": "6454364d8facd7a88e627e4c4b11b032d2f83af8f7f9329ffc2b7a5c879dc838.gif", 88 | "uri": "the-same-uri-we-uploaded", 89 | "size": 1156 90 | } 91 | ``` 92 | 93 | Or we can implement a very basic frontend with `feathers-client` and `jQuery`: 94 | 95 | ```html 96 | 97 | 98 | 99 | Feathersjs File Upload 100 | 101 | 102 | 103 | 147 | 148 | 149 |

Let's upload some files!

150 | 151 | 152 | 153 | 154 | ``` 155 | 156 | Simply as that, the file has been uploaded and saved to the `./uploads` directory. 157 | 158 | We'd just implemented all what we needed! Awesome! 159 | 160 | Let's call it a day shall we?... But hey, there is somethiing that doesn't feels quite right ...right? 161 | 162 | ### DataURI upload problems 163 | 164 | It doesn't feels right because it is not. Let's imagine what would happen if we try to upload a large file, say 25MB or more: The entire file (plus some extra MB due to the encoding) has to be kept in memory for the entire upload proccess, this could look like nothing for a normal computer but for mobile devices it's a big deal. 165 | 166 | We have a big RAM consumption problem. Not to mention we have to encode the file before sending it... 167 | 168 | The solution is to split the dataURI and upload one small chunk at a time and then reasemble everything on the server. But hey, is not that the same thing the browsers has been able to do since maybe the very early days of the web? maybe in Netscape Navigator? 169 | 170 | Well, actually doing a `multipart/form-data` post is still the easiest way to upload a file, plus there are various libraries out there wich can make it even easier. 171 | 172 | 173 | ## Second and final attempt - feathers-blob with multipart support. 174 | 175 | Back with the backend, in order to accept multipart uploads, we need a way to decode all the data received by the web server. Given that Feathers behaves like Express, let's just use `multer` to handle that. 176 | 177 | ``` javascript 178 | /* --- server.js --- */ 179 | const multer = require('multer'); 180 | const multipartMiddleware = multer(); 181 | 182 | // Upload Service with multipart support 183 | app.use('/uploads', 184 | 185 | // multer parses the file named 'uri'. 186 | // Without extra params the data is 187 | // temporarely kept in memory 188 | multipartMiddleware.single('uri'), 189 | 190 | // another middleware, this time to 191 | // transfer the received file to feathers 192 | function(req,res,next){ 193 | req.feathers.file = req.file; 194 | next(); 195 | }, 196 | blobService({Model: blobStorage}) 197 | ); 198 | 199 | 200 | ``` 201 | 202 | Notice we kept the file field name as *uri* just to keep it uniform, as the service will always work with that name anyways. But you can change it if you preffer. 203 | 204 | As fheathers-blob only understands files encoded as dataURI, we need to convert them first, let's make a Hook for that: 205 | 206 | ```javascript 207 | /* --- server.js --- */ 208 | const dauria = require('dauria'); 209 | 210 | // before-create Hook to get the file (if there is any) 211 | // and turn it into a datauri, 212 | // transparently getting feathers-blob to work 213 | // with multipart file uploads 214 | app.service('/uploads').before({ 215 | create: [ 216 | function(hook) { 217 | if (!hook.data.uri && hook.params.file){ 218 | const file = hook.params.file; 219 | const uri = dauria.getBase64DataURI(file.buffer, file.mimetype); 220 | hook.data = {uri: uri}; 221 | } 222 | } 223 | ] 224 | }); 225 | ``` 226 | 227 | *Et voila!*. Now we have a Feahtersjs file storage service working, with support for traditional multipart uploads, and a variety of storage options to choose. 228 | 229 | **Simply awesome.** 230 | 231 | 232 | # Furhter improvements 233 | 234 | The service always return the dataURI back to us, wich may not be neccesary as we'd just uploaded the file, also we need to validate the file and check for authorization. 235 | 236 | All those things can be easily done with more Hooks, and that's the benefit of keeping all inside feathersjs services. I left that to you. 237 | 238 | For the frontend, there is a problem with the client: in order to show the upload progress it's stuck with only REST functionality and not real-time with socket.io. 239 | 240 | The solution is to switch `feathers-client` from REST to `socket.io`, and just use wherever you like for uploading the files, thats an easy task now that we are able to do a traditional `form-multipart` upload. 241 | 242 | Here is an example using dropzone: 243 | 244 | ```html 245 | 246 | 247 | 248 | Feathersjs File Upload 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 281 | 282 | 283 |

Let's upload some files!

284 |
287 | 288 | 289 | ``` 290 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Feathersjs File Upload 5 | 6 | 7 | 8 | 52 | 53 | 54 |

Let's upload some files!

55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "feathers-example-upload", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "dependencies": { 7 | "babel-eslint": "^6.0.4", 8 | "body-parser": "^1.15.1", 9 | "dauria": "^1.1.4", 10 | "eslint": "^2.10.2", 11 | "feathers": "^2.0.1", 12 | "feathers-authentication": "^0.7.7", 13 | "feathers-blob": "^1.0.5", 14 | "feathers-errors": "^2.1.0", 15 | "feathers-hooks": "^1.5.3", 16 | "feathers-memory": "^0.7.1", 17 | "feathers-rest": "^1.3.0", 18 | "feathers-socketio": "^1.4.0", 19 | "fs-blob-store": "^5.2.1", 20 | "multer": "^1.1.0" 21 | }, 22 | "devDependencies": {}, 23 | "scripts": { 24 | "test": "echo \"Error: no test specified\" && exit 1", 25 | "start": "node server.js" 26 | }, 27 | "author": "Dennis Xiloj (https://ciancoders.com)", 28 | "license": "MIT" 29 | } 30 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | // --- server.js --- 2 | const feathers = require('feathers'); 3 | const serveStatic = require('feathers').static; 4 | const rest = require('feathers-rest'); 5 | const socketio = require('feathers-socketio'); 6 | const hooks = require('feathers-hooks'); 7 | const bodyParser = require('body-parser'); 8 | const handler = require('feathers-errors/handler'); 9 | const multer = require('multer'); 10 | const multipartMiddleware = multer(); 11 | const dauria = require('dauria'); 12 | 13 | // feathers-blob service 14 | const blobService = require('feathers-blob'); 15 | // Here we initialize a FileSystem storage, 16 | // but you can use feathers-blob with any other 17 | // storage service like AWS or Google Drive. 18 | const fs = require('fs-blob-store'); 19 | const blobStorage = fs(__dirname + '/uploads'); 20 | 21 | 22 | // Feathers app 23 | const app = feathers(); 24 | 25 | // Serve our index page 26 | app.use('/', serveStatic(__dirname)) 27 | // Parse HTTP JSON bodies 28 | app.use(bodyParser.json({limit: '10mb'})); 29 | // Parse URL-encoded params 30 | app.use(bodyParser.urlencoded({limit: '10mb', extended: true })); 31 | // Register hooks module 32 | app.configure(hooks()); 33 | // Add REST API support 34 | app.configure(rest()); 35 | // Configure Socket.io real-time APIs 36 | app.configure(socketio()); 37 | 38 | 39 | // Upload Service with multipart support 40 | app.use('/uploads', 41 | 42 | // multer parses the file named 'uri'. 43 | // Without extra params the data is 44 | // temporarely kept in memory 45 | multipartMiddleware.single('uri'), 46 | 47 | // another middleware, this time to 48 | // transfer the received file to feathers 49 | function(req,res,next){ 50 | req.feathers.file = req.file; 51 | next(); 52 | }, 53 | blobService({Model: blobStorage}) 54 | ); 55 | 56 | // before-create Hook to get the file (if there is any) 57 | // and turn it into a datauri, 58 | // transparently getting feathers-blob 59 | // to work with multipart file uploads 60 | app.service('/uploads').before({ 61 | create: [ 62 | function(hook) { 63 | if (!hook.data.uri && hook.params.file){ 64 | const file = hook.params.file; 65 | const uri = dauria.getBase64DataURI(file.buffer, file.mimetype); 66 | hook.data = {uri: uri}; 67 | } 68 | } 69 | ] 70 | }); 71 | 72 | // Register a nicer error handler than the default Express one 73 | app.use(handler()); 74 | 75 | // Start the server 76 | app.listen(3030, function(){ 77 | console.log('Feathers app started at localhost:3030') 78 | }); 79 | --------------------------------------------------------------------------------