1/** 2 * This file is part of Filepicker. 3 * 4 * Copyright (c) 2016 Cretu Eusebiu <[email protected]> 5 * 6 * For the full copyright and license information, please visit: 7 * http://codecanyon.net/licenses/standard 8 */ 9 10 11class Filepicker { 12 constructor(options) { 13 this._init(options); 14 } 15 16 17 static plugins = {}; 18 static namespace = 'filepicker'; 19 static pluginName = 'filePicker'; 20 21 /** 22 * Register a plugin. 23 * 24 * @param {String} name 25 * @param {Function} callback 26 */ 27 static plugin(name, callback) { 28 Filepicker.plugins[name] = callback; 29 } 30 31 _init(options) { 32 33 this.events = {}; 34 this.plugins = {}; 35 this.element = options.el || null; 36 this.options = $.extend(true, $.extend(true, {}, defaults), options); 37 38 this._queue = []; 39 this._sending = 0; 40 41 this._registerPlugins(); 42 43 $.extend(true, this.options, options); 44 45 if (this.options.fileInput instanceof $) { 46 console.log(this.options.fileInput); 47 this.on(this.options.fileInput, 'change', this.onChange); 48 } else { 49 console.log(this.options.fileInput); 50 this.on(this.element, 'change', this.options.fileInput, this.onChange); 51 } 52 53 this.trigger('init'); 54 55 this._initPlugins(); 56 } 57 58 _registerPlugins() { 59 Object.entries(this.options.plugins).forEach(([_, name]) => { 60 if (Filepicker.plugins[name]) { 61 this.plugins[name] = new Filepicker.plugins[name](this); 62 } 63 }); 64 } 65 66 _initPlugins() { 67 try { 68 Object.values(this.plugins).forEach(plugin => { 69 this._initPlugin(plugin); 70 }); 71 } 72 catch (e) { 73 console.error(e); 74 } 75 76 } 77 78 _initPlugin(plugin) { 79 plugin.$f = this; 80 var _this = this; 81 82 /** 83 * Remove an event handler from element and instance. 84 * 85 * See Filepicker.prototype.trigger (events.js). 86 */ 87 plugin.trigger = function (type, event) { 88 var data = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; 89 var context = arguments[3]; 90 91 return _this.trigger(type, event, data, context); 92 }; 93 /** 94 * Add event listener on element or instance. 95 * 96 * See Filepicker.prototype.on (events.js). 97 */ 98 plugin.on = function () { 99 var args = arguments; 100 101 if ($.isFunction(args[2])) { 102 args[4] = args[3] || this; 103 args[3] = args[2]; 104 args[2] = null; 105 } else { 106 args[4] = args[4] || this; 107 } 108 109 return plugin.$f.on(args[0], args[1], args[2], args[3], args[4]); 110 }; 111 112 plugin.init(); 113 } 114 115 /** 116 * Fetch files from the server. 117 * 118 * @param {Object} options 119 * @return {Object} jQuery Promise Object 120 */ 121 fetch(options) { 122 return this._ajax(this._route('fetch'), options); 123 } 124 125 /** 126 * Update file by name. 127 * 128 * @param {String} file 129 * @param {Object} options 130 * @return {Object} jQuery Promise Object 131 */ 132 update(file, options) { 133 const route = this._route('patch', 'PATCH'); 134 return this._ajax(route, { file, ...options }); 135 } 136 137 /** 138 * Delete files by name. 139 * 140 * @param {String|Array} files 141 * @return {Object} jQuery Promise Object 142 */ 143 delete(files) { 144 const filesArray = Array.isArray(files) ? files : [files]; 145 return this._ajax(this._route('delete', 'DELETE'), { files: filesArray }); 146 } 147 148 /** 149 * Get the translation for a given key. 150 * 151 * @param {String} id 152 * @param {Object} params 153 * @return {String} 154 */ 155 trans(id, params = {}) { 156 let message = this.options.messages[id] || id.toString(); 157 158 if (typeof message === 'function') { 159 message = message.call(this, params); 160 } else { 161 for (const key in params) { 162 message = message.replace(`:${key}`, params[key]); 163 } 164 } 165 166 return message; 167 } 168 169 /** 170 * Add extra props to the file. 171 * 172 * @param {Object} file 173 */ 174 addProps(file) { 175 if (!file.extension) { 176 file.extension = this._getExtension(file.name); 177 } 178 179 file.sizeFormatted = this._formatSize(file.size); 180 file.imageFile = this.options.imageFileTypes.test(file.name); 181 file.pdfFile = this.options.pdfFileTypes.test(file.name); 182 file.timeFormatted = file.time ? this._formatTime(file.time) : ''; 183 184 file.timeISOString = () => 185 Date.prototype.toISOString ? new Date(file.time * 1000).toISOString() : ''; 186 } 187 188 /** 189 * Get custom data. 190 * 191 * @param {Object} merge 192 * @return {Object} 193 */ 194 data(merge = {}) { 195 let data = typeof this.options.data === 'function' ? this.options.data.call(this) : this.options.data; 196 return { ...data, ...merge }; 197 } 198 199 /** 200 * Destroy events. 201 */ 202 destroy() { 203 this.events = []; 204 205 if (this.element) { 206 this.element.off(`.${Filepicker.pluginName}`); 207 } 208 209 if (this.options.fileInput) { 210 if (this.options.fileInput instanceof $) { 211 this.options.fileInput.off('change'); 212 } else { 213 this.element.off('change', this.options.fileInput); 214 } 215 } 216 217 if (this.options.dropZone) { 218 this.options.dropZone.off('dragover dragenter dragleave drop'); 219 } 220 } 221 222 /** 223 * Extend plugin. 224 * 225 * @param {Object} plugin 226 * @param {Object} options 227 */ 228 extend(plugin, options) { 229 plugin.$f = this; 230 this.options = { ...this.options, ...options }; 231 } 232 233 _addMethods(data) { 234 var _this = this; 235 var dfd = $.Deferred(); 236
237 data.state = 'pending'; 238 data.progress = { total: 0, loaded: 0 }; 239 data.promise = function () { 240 return dfd.promise(); 241 }; 242 243 /** 244 * Send data. 245 * 246 * @param {Object} options 247 * @return {Object|Boolean} jQuery Promise Object 248 */ 249 data.send = function (options) { 250 if (data.state === 'sending') { 251 return dfd; 252 } 253 254 data.state = 'sending'; 255 256 var xhr = data.xhr = $.ajaxSettings.xhr(); 257 var route = _this._route('upload', 'POST'); 258 259 xhr.open(route.method, route.uri, true); 260 261 xhr.upload.onprogress = function (e) { 262 if (e.lengthComputable) { 263 data.progress.total = e.total; 264 data.progress.loaded = e.loaded; 265 266 dfd.notify(e.loaded, e.total, xhr); 267 } 268 }; 269 270 xhr.onload = function () { 271 xhr.responseJSON = null; 272 273 try { 274 xhr.responseJSON = $.parseJSON(xhr.responseText); 275 } catch (e) { 276 var max = _this._getPostMaxSize(xhr.responseText); 277 278 if (max > -1) { 279 dfd.reject(_this.trans('postMaxSize', { max: max }), xhr); 280 } else { 281 dfd.reject(_this.trans('error'), xhr); 282 } 283 } 284 285 if (xhr.responseJSON != null) { 286 if (xhr.status === 200 || xhr.status === 201) { 287 dfd.resolve(xhr.responseJSON, xhr); 288 } else { 289 dfd.reject(_this.trans('error', { response: xhr.responseJSON }), xhr); 290 } 291 } 292 }; 293 294 var formData = new FormData(); 295 296 $.each(_this.data(options), function (key, value) { 297 formData.append(key, value); 298 if ($("#token_recaptcha").length > 0) 299 formData.append("recaptcha", $("#token_recaptcha").val()) 300 }); 301 302 $.each(data.files, function (_, file) { 303 if (!file.error) { 304 formData.append(_this.options.paramName, file, file.name); 305 } 306 }); 307 xhr.send(formData); 308 309 return data.promise(); 310 }; 311 312 /** 313 * Abort upload. 314 */ 315 data.abort = function () { 316 if (data.xhr) { 317 data.xhr.abort(); 318 } 319 320 _this._processQueue(); 321 }; 322 323 return data; 324 } 325 326 _processQueue() { 327 this._sending--; 328 329 const { parallelUploads: parallel } = this.options; 330 331 if (!parallel || parallel > this._sending) { 332 for (let i = 0; i < this._queue.length; i++) { 333 if (this._queue[i].state !== 'sending') { 334 this._queue[i].send(); 335 this._queue.splice(i, 1); 336 this._sending++; 337 break; 338 } 339 } 340 } 341 } 342 343 _ajax(route, data) { 344 if (route.method != 'GET' && route.method != 'POST') { 345 data._method = route.method; 346 route.method = 'POST'; 347 } 348 349 return $.ajax({ 350 url: route.uri, 351 type: route.method, 352 dataType: 'json', 353 data: this.data(data) 354 }); 355 } 356 357 _route(name, method) { 358 const route = this.options.routes[name]; 359 return { 360 uri: route ? route.uri : this.options.url, 361 method: route ? route.method : method || 'GET', 362 }; 363 } 364 365 _getExtension(filename) { 366 return filename.substring(filename.lastIndexOf('.') + 1); 367 } 368 369 _formatTime(timestamp) { 370 var d = new Date(timestamp * 1000), 371 yyyy = d.getFullYear(), 372 mm = ('0' + (d.getMonth() + 1)).slice(-2), 373 dd = ('0' + d.getDate()).slice(-2), 374 hh = d.getHours(), 375 h = hh, 376 min = ('0' + d.getMinutes()).slice(-2), 377 ampm = 'AM'; 378 379 if (hh > 12) { 380 h = hh - 12; 381 ampm = 'PM'; 382 } else if (hh === 12) { 383 h = 12; 384 ampm = 'PM'; 385 } else if (hh == 0) { 386 h = 12; 387 } 388 389 return yyyy + '-' + mm + '-' + dd + ', ' + h + ':' + min + ' ' + ampm; 390 } 391 392 _formatSize(bytes) { 393 if (!$.isNumeric(bytes)) { 394 return ''; 395 } 396 397 var quant = { 398 GB: 1073741824, 399 MB: 1048576, 400 KB: 1024, 401 B: 1 402 }; 403 404 for (var unit in quant) { 405 if (bytes >= quant[unit]) { 406 var size = Math.round(bytes / quant[unit] * 10) / 10; 407 return size + ' ' + unit; 408 } 409 } 410 } 411 412 _getInputFiles(input) { 413 var files = $.makeArray(input.prop('files')); 414 415 this._replaceInput(input); 416 if (files.length > 5) { 417 alert("Solo puede cargar 5 documentos a la vez") 418 return [] 419 } 420 return files; 421 } 422 423 _replaceInput(input) { 424 var inputClone = input.clone(true); 425 426 $('<form/>').append(inputClone)[0].reset(); 427 428 input.after(inputClone).detach(); 429 430 $.cleanData(input.off('remove')); 431 432 var inputs = []; 433 434 if (this.options.fileInput instanceof $) { 435 inputs = this.options.fileInput; 436 } else if (this.element) { 437 inputs = this.element.find(this.options.fileInput); 438 } 439 440 inputs.map(function (i, el) { 441 if (el === input[0]) { 442 return inputClone[0]; 443 } 444 445 return el; 446 }); 447 } 448 449 _getPostMaxSize(text) { 450 if (!text.includes('POST Content-Length')) { 451 return -1; 452 } 453 454 const matches = text.match(/\d+/g) || []; 455 return matches[1] ? Math.round(parseInt(matches[1], 10) / 1048576) : 0; 456 } 457 458 on(element, type, selector, handler, context) { 459 if (!arguments[0]) { 460 return false;
461 } 462 463 if (arguments[0] instanceof $) { 464 // Add event handler on the element. 465 466 var element = arguments[0]; 467 var type = arguments[1]; 468 var selector = arguments[2]; 469 var handler = arguments[3]; 470 var context = arguments[4]; 471 472 // Shift arguments if selector is missing. 473 if ($.isFunction(selector)) { 474 context = handler; 475 handler = selector; 476 selector = null; 477 } 478 479 element.on(type, selector, $.proxy(handler, context || this)); 480 } else { 481 // Add event handler the instance. 482 483 var _type = arguments[0]; 484 var _handler = arguments[1]; 485 486 (this.events[_type] = this.events[_type] || []).push(_handler); 487 } 488 } 489 490 off(type, handler) { 491 if (this.element) { 492 this.element.off(type, handler); 493 } 494 495 type || (this.events = {}); 496 497 var list = this.events[type] || []; 498 var i = list.length = handler ? list.length : 0; 499 500 while (i--) { 501 handler == list[i] && list.splice(i, 1); 502 } 503 } 504 505 trigger(type, event) { 506 var data = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; 507 var context = arguments[3]; 508 509 event = $.Event(event); 510 event.type = type + '.' + Filepicker.namespace; 511 512 context = context || this; 513 514 if (this.element) { 515 event.target = this.element[0]; 516 } 517 518 this._copyProps(event); 519 520 // Trigger event on the element. 521 if (this.element) { 522 this.element.trigger(event, data); 523 } 524 525 // Trigger event on the instance. 526 this._emit.apply(this, [type, context, event].concat(data)); 527 528 // Trigger event on default handler. 529 var response = $.isFunction(this.options[type]) && this.options[type].apply(context, [event].concat(data)) === false; 530 531 return !(response || event.isDefaultPrevented()); 532 } 533 534 _emit(type, context, ...args) { 535 const list = this.events[type] || []; 536 list.forEach(handler => { 537 handler.call(context, ...args); 538 }); 539 } 540 541 _copyProps(event) { 542 const orig = event.originalEvent; 543 if (orig) { 544 for (const prop in orig) { 545 if (!(prop in event)) { 546 event[prop] = orig[prop]; 547 } 548 } 549 } 550 } 551 552 553 onChange(e) { 554 if (!(window.File && window.FileList && window.FormData)) { 555 return this.trigger('uploadfallback', e, this.trans('uploadFallback')); 556 } 557 558 this.onAdd(e, { files: this._getInputFiles($(e.target)) }); 559 } 560 561 onAdd(e, data) { 562 var _this = this; 563 564 var i = 0; 565 var batchSize = 0; 566 var fileSet = []; 567 var limit = this.options.uploadMultipleLimit; 568 var limitSize = this.options.uploadMultipleSize; 569 var overhead = this.options.uploadMultipleSizeOverhead; 570 var validate = function validate(file) { 571 return _this.trigger('validate', e, file); 572 }; 573 574 $.each(data.files, function (_, file) { 575 return _this.addProps(file); 576 }); 577 578 data.originalFiles = data.files; 579 580 if (!this.options.uploadMultiple) { 581 $.each(data.files, function (_, file) { 582 validate(file); 583 fileSet.push([file]); 584 }); 585 } else if (limit) { 586 for (i = 0; i < data.files.length; i += limit) { 587 var files = data.files.slice(i, i + limit); 588 589 $.each(files, function (_, file) { 590 validate(file); 591 }); 592 593 fileSet.push(files); 594 } 595 } else { 596 $.each(data.files, function (_, file) { 597 if (!validate(file)) { 598 batchSize += file.size + overhead; 599 600 if (limitSize && batchSize > limitSize) { 601 i++; 602 batchSize = file.size + overhead; 603 } 604 } 605
606 if (!fileSet[i]) { 607 fileSet[i] = []; 608 } 609 610 fileSet[i].push(file); 611 }); 612 } 613 614 $.each(fileSet, function (_, files) { 615 var newData = $.extend({}, data, { files: files }); 616 617 _this._addMethods(newData); 618 619 _this.trigger('add', e, newData); 620 }); 621 } 622 623 onSend(e, data) { 624 var _this2 = this; 625 626 var filtered = data.files.filter(function (file) { 627 return !file.error; 628 }); 629 630 if (filtered.length == 0) { 631 return false; 632 } 633 634 if (this.trigger('send', e, data) === false) { 635 return this.onFail(this.trans(data.errorThrown || 'abort'), data); 636 } 637 638 data.promise().progress(function (loaded, total) { 639 return _this2.onProgress(loaded, total, data); 640 }).done(function (result, xhr) { 641 return _this2.onDone(xhr.responseJSON, data); 642 }).fail(function (error) { 643 return _this2.onFail(error, data); 644 }).always(function (resultOrError) { 645 return _this2.onAlways(resultOrError, data); 646 }); 647 648 var parallel = this.options.parallelUploads; 649 650 if (!parallel || parallel > this._sending) { 651 this._sending++; 652 data.send(); 653 } else { 654 this._queue.push(data); 655 } 656 } 657 658 onProgress(loaded, total, data) { 659 data.progress.percentage = Math.floor(loaded / total * 100); 660 this.trigger('progress', null, data); 661 } 662 663 onDone(result, data) { 664 data.result = result; 665 data.state = 'sent'; 666 667 for (var i = 0; i < data.files.length; i++) { 668 data.files[i] = $.extend({ original: data.files[i] }, result[i]); 669 this.addProps(data.files[i]); 670 } 671 672 this.trigger('done', null, data); 673 } 674 675 onFail(errorThrown, data) { 676 data.errorThrown = errorThrown; 677 data.state = 'failed'; 678 this.trigger('fail', null, data); 679 } 680 681 onAlways(resultOrError, data) { 682 this._processQueue(); 683 data.resultOrError = resultOrError; 684 this.trigger('always', null, data); 685 } 686} 687 688$.fn[Filepicker.pluginName] = function (options = {}) { 689 return this.each(function () { 690 const el = $(this); 691 let instance = el.data(Filepicker.pluginName); 692 693 if (!instance) { 694 options.el = el; 695 instance = new Filepicker(options); 696 el.data(Filepicker.pluginName, instance); 697 } 698 }); 699}; 700 701const defaults = { 702 // url: null, 703 routes: { 704 // upload: {uri : '/upload', method: 'POST'}, 705 }, 706 plugins: [], 707 708 uploadMultiple: false, 709 uploadMultipleLimit: undefined, 710 uploadMultipleSize: undefined, 711 uploadMultipleSizeOverhead: 512, 712 parallelUploads: undefined, 713 714 // acceptFileTypes: '', 715 imageFileTypes: /(\.|\/)(gif|jpe?g|png)$/i, 716 pdfFileTypes: /(\.|\/)(pdf)$/i, 717 minFileSize: 1, 718 // maxFileSize: undefined, 719 720 data: {}, 721 paramName: 'files[]', 722 723 fileInput: 'input[type="file"]', 724 725 messages: { 726 uploadFallback: 'The browser does not support file uploads.', 727 minFileSize: 'The file must be at least :min KB.', 728 maxFileSize: 'The file may not be greater than :max KB.', 729 postMaxSize: 'The file exceeds the post max size limit of :max MB.', 730 invalidFileType: 'The file type is not allowed.', 731 error: 'Oops! Something went wrong.', 732 abort: 'The operation was aborted.' 733 }, 734 735 /** 736 * Add handler. 737 * 738 * @param {Object} e 739 * @param {Object} data 740 */ 741 add(e, data) { 742 if (!e.defaultPrevented) { 743 this.onSend(e, data); 744 } 745 }, 746 747 /** 748 * Progress handler. 749 * 750 * @param {Object} e 751 * @param {Object} data 752 */ 753 // progress: function (e, data) {}, 754 755 /** 756 * Upload done handler. 757 * 758 * @param {Object} e 759 * @param {Object} data 760 */ 761 // done: function (e, data) {}, 762 763 /** 764 * Upload fail handler. 765 * 766 * @param {Object} e 767 * @param {Object} data 768 */ 769 // fail: function (e, data) {}, 770 771 /** 772 * Upload always handler. 773 * 774 * @param {Object} e 775 * @param {Object} data 776 */ 777 // always: function (e, data) {}, 778 779 /** 780 * Validate file handler. 781 * 782 * @param {Object} e 783 * @param {Object} file 784 * @return {Boolean} 785 */ 786 validate(e, file) { 787 var o = this.options; 788 789 if (o.acceptFileTypes && !o.acceptFileTypes.test(file.name)) { 790 file.error = this.trans('invalidFileType'); 791 } else if (file.size !== undefined && o.maxFileSize && file.size > o.maxFileSize) { 792 file.error = this.trans('maxFileSize', { max: o.maxFileSize / 1000 }); 793 } else if (file.size !== undefined && o.minFileSize && file.size < o.minFileSize) { 794 file.error = this.trans('minFileSize', { min: o.minFileSize / 1000 }); 795 } 796 797 return file.error === undefined; 798 }, 799 800 /** 801 * Upload fallback handler. 802 * 803 * @param {Object} e 804 * @param {String} message 805 */ 806 uploadfallback(e, message) { 807 alert(message); 808 } 809}; 810 811export default Filepicker;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.