fileupload.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. /*!
  2. * =============================================================
  3. * dropify v0.2.2 - Override your input files with style.
  4. * https://github.com/JeremyFagis/dropify
  5. *
  6. * (c) 2017 - Jeremy FAGIS <jeremy@fagis.fr> (http://fagis.fr)
  7. * =============================================================
  8. */
  9. ;(function(root, factory) {
  10. if (typeof define === 'function' && define.amd) {
  11. define(['jquery'], factory);
  12. } else if (typeof exports === 'object') {
  13. module.exports = factory(require('jquery'));
  14. } else {
  15. root.Dropify = factory(root.jQuery);
  16. }
  17. }(this, function($) {
  18. var pluginName = "dropify";
  19. /**
  20. * Dropify plugin
  21. *
  22. * @param {Object} element
  23. * @param {Array} options
  24. */
  25. function Dropify(element, options) {
  26. if (!(window.File && window.FileReader && window.FileList && window.Blob)) {
  27. return;
  28. }
  29. var defaults = {
  30. defaultFile: '',
  31. maxFileSize: 0,
  32. minWidth: 0,
  33. maxWidth: 0,
  34. minHeight: 0,
  35. maxHeight: 0,
  36. showRemove: true,
  37. showLoader: true,
  38. showErrors: true,
  39. errorTimeout: 3000,
  40. errorsPosition: 'overlay',
  41. imgFileExtensions: ['png', 'jpg', 'jpeg', 'gif', 'bmp'],
  42. maxFileSizePreview: "5M",
  43. allowedFormats: ['portrait', 'square', 'landscape'],
  44. allowedFileExtensions: ['*'],
  45. messages: {
  46. 'default': 'Drag and drop a file here or click',
  47. 'replace': 'Drag and drop or click to replace',
  48. 'remove': 'Remove',
  49. 'error': 'Ooops, something wrong happended.'
  50. },
  51. error: {
  52. 'fileSize': 'The file size is too big ({{ value }} max).',
  53. 'minWidth': 'The image width is too small ({{ value }}}px min).',
  54. 'maxWidth': 'The image width is too big ({{ value }}}px max).',
  55. 'minHeight': 'The image height is too small ({{ value }}}px min).',
  56. 'maxHeight': 'The image height is too big ({{ value }}px max).',
  57. 'imageFormat': 'The image format is not allowed ({{ value }} only).',
  58. 'fileExtension': 'The file is not allowed ({{ value }} only).'
  59. },
  60. tpl: {
  61. wrap: '<div class="dropify-wrapper"></div>',
  62. loader: '<div class="dropify-loader"></div>',
  63. message: '<div class="dropify-message"><span class="file-icon" /> <p>{{ default }}</p></div>',
  64. preview: '<div class="dropify-preview"><span class="dropify-render"></span><div class="dropify-infos"><div class="dropify-infos-inner"><p class="dropify-infos-message">{{ replace }}</p></div></div></div>',
  65. filename: '<p class="dropify-filename"><span class="dropify-filename-inner"></span></p>',
  66. clearButton: '<button type="button" class="dropify-clear">{{ remove }}</button>',
  67. errorLine: '<p class="dropify-error">{{ error }}</p>',
  68. errorsContainer: '<div class="dropify-errors-container"><ul></ul></div>'
  69. }
  70. };
  71. this.element = element;
  72. this.input = $(this.element);
  73. this.wrapper = null;
  74. this.preview = null;
  75. this.filenameWrapper = null;
  76. this.settings = $.extend(true, defaults, options, this.input.data());
  77. this.errorsEvent = $.Event('dropify.errors');
  78. this.isDisabled = false;
  79. this.isInit = false;
  80. this.file = {
  81. object: null,
  82. name: null,
  83. size: null,
  84. width: null,
  85. height: null,
  86. type: null
  87. };
  88. if (!Array.isArray(this.settings.allowedFormats)) {
  89. this.settings.allowedFormats = this.settings.allowedFormats.split(' ');
  90. }
  91. if (!Array.isArray(this.settings.allowedFileExtensions)) {
  92. this.settings.allowedFileExtensions = this.settings.allowedFileExtensions.split(' ');
  93. }
  94. this.onChange = this.onChange.bind(this);
  95. this.clearElement = this.clearElement.bind(this);
  96. this.onFileReady = this.onFileReady.bind(this);
  97. this.translateMessages();
  98. this.createElements();
  99. this.setContainerSize();
  100. this.errorsEvent.errors = [];
  101. this.input.on('change', this.onChange);
  102. }
  103. /**
  104. * On change event
  105. */
  106. Dropify.prototype.onChange = function()
  107. {
  108. this.resetPreview();
  109. this.readFile(this.element);
  110. };
  111. /**
  112. * Create dom elements
  113. */
  114. Dropify.prototype.createElements = function()
  115. {
  116. this.isInit = true;
  117. this.input.wrap($(this.settings.tpl.wrap));
  118. this.wrapper = this.input.parent();
  119. var messageWrapper = $(this.settings.tpl.message).insertBefore(this.input);
  120. $(this.settings.tpl.errorLine).appendTo(messageWrapper);
  121. if (this.isTouchDevice() === true) {
  122. this.wrapper.addClass('touch-fallback');
  123. }
  124. if (this.input.attr('disabled')) {
  125. this.isDisabled = true;
  126. this.wrapper.addClass('disabled');
  127. }
  128. if (this.settings.showLoader === true) {
  129. this.loader = $(this.settings.tpl.loader);
  130. this.loader.insertBefore(this.input);
  131. }
  132. this.preview = $(this.settings.tpl.preview);
  133. this.preview.insertAfter(this.input);
  134. if (this.isDisabled === false && this.settings.showRemove === true) {
  135. this.clearButton = $(this.settings.tpl.clearButton);
  136. this.clearButton.insertAfter(this.input);
  137. this.clearButton.on('click', this.clearElement);
  138. }
  139. this.filenameWrapper = $(this.settings.tpl.filename);
  140. this.filenameWrapper.prependTo(this.preview.find('.dropify-infos-inner'));
  141. if (this.settings.showErrors === true) {
  142. this.errorsContainer = $(this.settings.tpl.errorsContainer);
  143. if (this.settings.errorsPosition === 'outside') {
  144. this.errorsContainer.insertAfter(this.wrapper);
  145. } else {
  146. this.errorsContainer.insertBefore(this.input);
  147. }
  148. }
  149. var defaultFile = this.settings.defaultFile || '';
  150. if (defaultFile.trim() !== '') {
  151. this.file.name = this.cleanFilename(defaultFile);
  152. this.setPreview(this.isImage(), defaultFile);
  153. }
  154. };
  155. /**
  156. * Read the file using FileReader
  157. *
  158. * @param {Object} input
  159. */
  160. Dropify.prototype.readFile = function(input)
  161. {
  162. if (input.files && input.files[0]) {
  163. var reader = new FileReader();
  164. var image = new Image();
  165. var file = input.files[0];
  166. var srcBase64 = null;
  167. var _this = this;
  168. var eventFileReady = $.Event("dropify.fileReady");
  169. this.clearErrors();
  170. this.showLoader();
  171. this.setFileInformations(file);
  172. this.errorsEvent.errors = [];
  173. this.checkFileSize();
  174. this.isFileExtensionAllowed();
  175. if (this.isImage() && this.file.size < this.sizeToByte(this.settings.maxFileSizePreview)) {
  176. this.input.on('dropify.fileReady', this.onFileReady);
  177. reader.readAsDataURL(file);
  178. reader.onload = function(_file) {
  179. srcBase64 = _file.target.result;
  180. image.src = _file.target.result;
  181. image.onload = function() {
  182. _this.setFileDimensions(this.width, this.height);
  183. _this.validateImage();
  184. _this.input.trigger(eventFileReady, [true, srcBase64]);
  185. };
  186. }.bind(this);
  187. } else {
  188. this.onFileReady(false);
  189. }
  190. }
  191. };
  192. /**
  193. * On file ready to show
  194. *
  195. * @param {Event} event
  196. * @param {Bool} previewable
  197. * @param {String} src
  198. */
  199. Dropify.prototype.onFileReady = function(event, previewable, src)
  200. {
  201. this.input.off('dropify.fileReady', this.onFileReady);
  202. if (this.errorsEvent.errors.length === 0) {
  203. this.setPreview(previewable, src);
  204. } else {
  205. this.input.trigger(this.errorsEvent, [this]);
  206. for (var i = this.errorsEvent.errors.length - 1; i >= 0; i--) {
  207. var errorNamespace = this.errorsEvent.errors[i].namespace;
  208. var errorKey = errorNamespace.split('.').pop();
  209. this.showError(errorKey);
  210. }
  211. if (typeof this.errorsContainer !== "undefined") {
  212. this.errorsContainer.addClass('visible');
  213. var errorsContainer = this.errorsContainer;
  214. setTimeout(function(){ errorsContainer.removeClass('visible'); }, this.settings.errorTimeout);
  215. }
  216. this.wrapper.addClass('has-error');
  217. this.resetPreview();
  218. this.clearElement();
  219. }
  220. };
  221. /**
  222. * Set file informations
  223. *
  224. * @param {File} file
  225. */
  226. Dropify.prototype.setFileInformations = function(file)
  227. {
  228. this.file.object = file;
  229. this.file.name = file.name;
  230. this.file.size = file.size;
  231. this.file.type = file.type;
  232. this.file.width = null;
  233. this.file.height = null;
  234. };
  235. /**
  236. * Set file dimensions
  237. *
  238. * @param {Int} width
  239. * @param {Int} height
  240. */
  241. Dropify.prototype.setFileDimensions = function(width, height)
  242. {
  243. this.file.width = width;
  244. this.file.height = height;
  245. };
  246. /**
  247. * Set the preview and animate it
  248. *
  249. * @param {String} src
  250. */
  251. Dropify.prototype.setPreview = function(previewable, src)
  252. {
  253. this.wrapper.removeClass('has-error').addClass('has-preview');
  254. this.filenameWrapper.children('.dropify-filename-inner').html(this.file.name);
  255. var render = this.preview.children('.dropify-render');
  256. this.hideLoader();
  257. if (previewable === true) {
  258. var imgTag = $('<img />').attr('src', src);
  259. if (this.settings.height) {
  260. imgTag.css("max-height", this.settings.height);
  261. }
  262. imgTag.appendTo(render);
  263. } else {
  264. $('<i />').attr('class', 'dropify-font-file').appendTo(render);
  265. $('<span class="dropify-extension" />').html(this.getFileType()).appendTo(render);
  266. }
  267. this.preview.fadeIn();
  268. };
  269. /**
  270. * Reset the preview
  271. */
  272. Dropify.prototype.resetPreview = function()
  273. {
  274. this.wrapper.removeClass('has-preview');
  275. var render = this.preview.children('.dropify-render');
  276. render.find('.dropify-extension').remove();
  277. render.find('i').remove();
  278. render.find('img').remove();
  279. this.preview.hide();
  280. this.hideLoader();
  281. };
  282. /**
  283. * Clean the src and get the filename
  284. *
  285. * @param {String} src
  286. *
  287. * @return {String} filename
  288. */
  289. Dropify.prototype.cleanFilename = function(src)
  290. {
  291. var filename = src.split('\\').pop();
  292. if (filename == src) {
  293. filename = src.split('/').pop();
  294. }
  295. return src !== "" ? filename : '';
  296. };
  297. /**
  298. * Clear the element, events are available
  299. */
  300. Dropify.prototype.clearElement = function()
  301. {
  302. if (this.errorsEvent.errors.length === 0) {
  303. var eventBefore = $.Event("dropify.beforeClear");
  304. this.input.trigger(eventBefore, [this]);
  305. if (eventBefore.result !== false) {
  306. this.resetFile();
  307. this.input.val('');
  308. this.resetPreview();
  309. this.input.trigger($.Event("dropify.afterClear"), [this]);
  310. }
  311. } else {
  312. this.resetFile();
  313. this.input.val('');
  314. this.resetPreview();
  315. }
  316. };
  317. /**
  318. * Reset file informations
  319. */
  320. Dropify.prototype.resetFile = function()
  321. {
  322. this.file.object = null;
  323. this.file.name = null;
  324. this.file.size = null;
  325. this.file.type = null;
  326. this.file.width = null;
  327. this.file.height = null;
  328. };
  329. /**
  330. * Set the container height
  331. */
  332. Dropify.prototype.setContainerSize = function()
  333. {
  334. if (this.settings.height) {
  335. this.wrapper.height(this.settings.height);
  336. }
  337. };
  338. /**
  339. * Test if it's touch screen
  340. *
  341. * @return {Boolean}
  342. */
  343. Dropify.prototype.isTouchDevice = function()
  344. {
  345. return (('ontouchstart' in window) ||
  346. (navigator.MaxTouchPoints > 0) ||
  347. (navigator.msMaxTouchPoints > 0));
  348. };
  349. /**
  350. * Get the file type.
  351. *
  352. * @return {String}
  353. */
  354. Dropify.prototype.getFileType = function()
  355. {
  356. return this.file.name.split('.').pop().toLowerCase();
  357. };
  358. /**
  359. * Test if the file is an image
  360. *
  361. * @return {Boolean}
  362. */
  363. Dropify.prototype.isImage = function()
  364. {
  365. if (this.settings.imgFileExtensions.indexOf(this.getFileType()) != "-1") {
  366. return true;
  367. }
  368. return false;
  369. };
  370. /**
  371. * Test if the file extension is allowed
  372. *
  373. * @return {Boolean}
  374. */
  375. Dropify.prototype.isFileExtensionAllowed = function () {
  376. if (this.settings.allowedFileExtensions.indexOf('*') != "-1" || 
  377. this.settings.allowedFileExtensions.indexOf(this.getFileType()) != "-1") {
  378. return true;
  379. }
  380. this.pushError("fileExtension");
  381. return false;
  382. };
  383. /**
  384. * Translate messages if needed.
  385. */
  386. Dropify.prototype.translateMessages = function()
  387. {
  388. for (var name in this.settings.tpl) {
  389. for (var key in this.settings.messages) {
  390. this.settings.tpl[name] = this.settings.tpl[name].replace('{{ ' + key + ' }}', this.settings.messages[key]);
  391. }
  392. }
  393. };
  394. /**
  395. * Check the limit filesize.
  396. */
  397. Dropify.prototype.checkFileSize = function()
  398. {
  399. if (this.sizeToByte(this.settings.maxFileSize) !== 0 && this.file.size > this.sizeToByte(this.settings.maxFileSize)) {
  400. this.pushError("fileSize");
  401. }
  402. };
  403. /**
  404. * Convert filesize to byte.
  405. *
  406. * @return {Int} value
  407. */
  408. Dropify.prototype.sizeToByte = function(size)
  409. {
  410. var value = 0;
  411. if (size !== 0) {
  412. var unit = size.slice(-1).toUpperCase(),
  413. kb = 1024,
  414. mb = kb * 1024,
  415. gb = mb * 1024;
  416. if (unit === 'K') {
  417. value = parseFloat(size) * kb;
  418. } else if (unit === 'M') {
  419. value = parseFloat(size) * mb;
  420. } else if (unit === 'G') {
  421. value = parseFloat(size) * gb;
  422. }
  423. }
  424. return value;
  425. };
  426. /**
  427. * Validate image dimensions and format
  428. */
  429. Dropify.prototype.validateImage = function()
  430. {
  431. if (this.settings.minWidth !== 0 && this.settings.minWidth >= this.file.width) {
  432. this.pushError("minWidth");
  433. }
  434. if (this.settings.maxWidth !== 0 && this.settings.maxWidth <= this.file.width) {
  435. this.pushError("maxWidth");
  436. }
  437. if (this.settings.minHeight !== 0 && this.settings.minHeight >= this.file.height) {
  438. this.pushError("minHeight");
  439. }
  440. if (this.settings.maxHeight !== 0 && this.settings.maxHeight <= this.file.height) {
  441. this.pushError("maxHeight");
  442. }
  443. if (this.settings.allowedFormats.indexOf(this.getImageFormat()) == "-1") {
  444. this.pushError("imageFormat");
  445. }
  446. };
  447. /**
  448. * Get image format.
  449. *
  450. * @return {String}
  451. */
  452. Dropify.prototype.getImageFormat = function()
  453. {
  454. if (this.file.width == this.file.height) {
  455. return "square";
  456. }
  457. if (this.file.width < this.file.height) {
  458. return "portrait";
  459. }
  460. if (this.file.width > this.file.height) {
  461. return "landscape";
  462. }
  463. };
  464. /**
  465. * Push error
  466. *
  467. * @param {String} errorKey
  468. */
  469. Dropify.prototype.pushError = function(errorKey) {
  470. var e = $.Event("dropify.error." + errorKey);
  471. this.errorsEvent.errors.push(e);
  472. this.input.trigger(e, [this]);
  473. };
  474. /**
  475. * Clear errors
  476. */
  477. Dropify.prototype.clearErrors = function()
  478. {
  479. if (typeof this.errorsContainer !== "undefined") {
  480. this.errorsContainer.children('ul').html('');
  481. }
  482. };
  483. /**
  484. * Show error in DOM
  485. *
  486. * @param {String} errorKey
  487. */
  488. Dropify.prototype.showError = function(errorKey)
  489. {
  490. if (typeof this.errorsContainer !== "undefined") {
  491. this.errorsContainer.children('ul').append('<li>' + this.getError(errorKey) + '</li>');
  492. }
  493. };
  494. /**
  495. * Get error message
  496. *
  497. * @return {String} message
  498. */
  499. Dropify.prototype.getError = function(errorKey)
  500. {
  501. var error = this.settings.error[errorKey],
  502. value = '';
  503. if (errorKey === 'fileSize') {
  504. value = this.settings.maxFileSize;
  505. } else if (errorKey === 'minWidth') {
  506. value = this.settings.minWidth;
  507. } else if (errorKey === 'maxWidth') {
  508. value = this.settings.maxWidth;
  509. } else if (errorKey === 'minHeight') {
  510. value = this.settings.minHeight;
  511. } else if (errorKey === 'maxHeight') {
  512. value = this.settings.maxHeight;
  513. } else if (errorKey === 'imageFormat') {
  514. value = this.settings.allowedFormats.join(', ');
  515. } else if (errorKey === 'fileExtension') {
  516. value = this.settings.allowedFileExtensions.join(', ');
  517. }
  518. if (value !== '') {
  519. return error.replace('{{ value }}', value);
  520. }
  521. return error;
  522. };
  523. /**
  524. * Show the loader
  525. */
  526. Dropify.prototype.showLoader = function()
  527. {
  528. if (typeof this.loader !== "undefined") {
  529. this.loader.show();
  530. }
  531. };
  532. /**
  533. * Hide the loader
  534. */
  535. Dropify.prototype.hideLoader = function()
  536. {
  537. if (typeof this.loader !== "undefined") {
  538. this.loader.hide();
  539. }
  540. };
  541. /**
  542. * Destroy dropify
  543. */
  544. Dropify.prototype.destroy = function()
  545. {
  546. this.input.siblings().remove();
  547. this.input.unwrap();
  548. this.isInit = false;
  549. };
  550. /**
  551. * Init dropify
  552. */
  553. Dropify.prototype.init = function()
  554. {
  555. this.createElements();
  556. };
  557. /**
  558. * Test if element is init
  559. */
  560. Dropify.prototype.isDropified = function()
  561. {
  562. return this.isInit;
  563. };
  564. $.fn[pluginName] = function(options) {
  565. this.each(function() {
  566. if (!$.data(this, pluginName)) {
  567. $.data(this, pluginName, new Dropify(this, options));
  568. }
  569. });
  570. return this;
  571. };
  572. return Dropify;
  573. }));