single-upload.js 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. /*
  2. * Project: Symfony2Admingenerator
  3. * Description: jQuery plugin for SingleUpload form widget
  4. * Author: loostro <loostro@gmail.com>
  5. * License: MIT
  6. * Dependencies:
  7. * - blueimp / JavaScript Load Image 1.2.3
  8. * https://github.com/blueimp/JavaScript-Load-Image
  9. */
  10. // the semi-colon before function invocation is a safety net against concatenated
  11. // scripts and/or other plugins which may not be closed properly.
  12. ;(function ( $, window, undefined ) {
  13. // undefined is used here as the undefined global variable in ECMAScript 3 is
  14. // mutable (ie. it can be changed by someone else). undefined isn't really being
  15. // passed in so we can ensure the value of it is truly undefined. In ES5, undefined
  16. // can no longer be modified.
  17. // window is passed through as local variable rather than global
  18. // as this (slightly) quickens the resolution process and can be more efficiently
  19. // minified (especially when both are regularly referenced in your plugin).
  20. // Create the defaults once
  21. var pluginName = 'agen$singleUpload',
  22. document = window.document,
  23. defaults = {
  24. maxWidth: 320,
  25. maxHeight: 180,
  26. minWidth: 16,
  27. minHeight: 16,
  28. previewImages: true,
  29. previewAsCanvas: true,
  30. isEmpty: true,
  31. nameable: false,
  32. deleteable: false,
  33. filetypes: {
  34. 'audio': "Audio",
  35. 'archive': "Archive",
  36. 'html': "HTML",
  37. 'image': "Image",
  38. 'pdf-document': "PDF<br />Document",
  39. 'plain-text': "Plain<br />Text",
  40. 'presentation': "Presentation",
  41. 'spreadsheet': "Spreadsheet",
  42. 'text-document': "Text<br />Document",
  43. 'unknown': "Unknown<br />Filetype",
  44. 'video': "Video"
  45. }
  46. };
  47. // The actual plugin constructor
  48. function Plugin( element, options ) {
  49. this.element = element;
  50. // jQuery has an extend method which merges the contents of two or
  51. // more objects, storing the result in the first object. The first object
  52. // is generally empty as we don't want to alter the default options for
  53. // future instances of the plugin
  54. this.options = $.extend( {}, defaults, options) ;
  55. this._defaults = defaults;
  56. this._name = pluginName;
  57. this._init();
  58. }
  59. Plugin.prototype = {
  60. _init: function() {
  61. // Plugin-scope helper
  62. var that = this;
  63. // Select container
  64. var $widgetContainer = $('#'+that.element.id+'_widget_container');
  65. var $cancelButton = $widgetContainer.find('.singleupload-buttonbar .cancel');
  66. var $deleteButton = $widgetContainer.find('.singleupload-buttonbar .delete');
  67. // Grant plugin-scope access to button selectors
  68. that.$cancelButton = $cancelButton;
  69. that.$deleteButton = $deleteButton;
  70. // Prepare nameable behavior if enabled
  71. if(that.options.nameable) {
  72. var $nameableInput = $('#'+that.element.id+'_nameable');
  73. that.$nameableInput = $nameableInput;
  74. that.originalName = $nameableInput.val();
  75. }
  76. // Set isDeletable
  77. that.isDeletable = (!that.options.isEmpty && that.options.deleteable);
  78. // Add deletable behaviour
  79. if (that.isDeletable) {
  80. var $deleteableInput = $('#'+that.element.id+'_deleteable');
  81. that.$deleteableInput = $deleteableInput;
  82. $deleteButton.show();
  83. }
  84. // Make sure upload input is empty (prevent cached form data)
  85. that._resetInput(that);
  86. // bind onChange to file input change event
  87. $(that.element).on('change', function(){
  88. that._onChange(that);
  89. });
  90. // bind onCancel to button click event
  91. $cancelButton.click(function(){
  92. that._onCancel(that);
  93. });
  94. // bind onDelete to button click event
  95. $deleteButton.click(function(){
  96. that._onDelete(that);
  97. });
  98. },
  99. _onChange: function(that) {
  100. var file = that.element.files[0];
  101. // show cancel button
  102. that.$cancelButton.removeClass('disabled').show('slide', {direction: 'left'}, 'slow');
  103. // hide delete button
  104. (that.isDeletable) ? that.$deleteButton.addClass('disabled').hide('slide', {direction: 'right'}, 'slow') : '';
  105. // change name
  106. (that.options.nameable) ? that.$nameableInput.removeAttr('disabled').val(file.name) : '';
  107. // trigger preview
  108. (that.options.previewImages && that._isImage(file))
  109. ? that._onPreviewImage(that)
  110. : that._onPreviewFile(that);
  111. },
  112. _onCancel: function(that) {
  113. // hide cancel button
  114. that.$cancelButton.addClass('disabled').hide('slide', {direction: 'left'}, 'slow');
  115. // show delete button
  116. (that.isDeletable) ? that.$deleteButton.removeClass('disabled').show('slide', {direction: 'right'}, 'slow') : '';
  117. // revert name
  118. (that.options.nameable) ? that.$nameableInput.val(that.originalName) : '';
  119. var $activePreview = $('.'+that.element.id+'_preview_image.active');
  120. var $previewDownload = $('.'+that.element.id+'_preview_image.download');
  121. // sanity-check
  122. if ($activePreview.hasClass('download')) return;
  123. // reset input
  124. that._resetInput(that);
  125. // animate preview toggle
  126. $previewDownload.slideDown({
  127. duration: 'slow',
  128. queue: false,
  129. complete: function() { $(this).addClass('active'); }
  130. });
  131. $activePreview.slideUp({
  132. duration: 'slow',
  133. queue: false,
  134. complete: function() { $(this).remove(); }
  135. });
  136. },
  137. _onDelete: function(that) {
  138. var $previewDownload = $('.'+that.element.id+'_preview_image.download');
  139. // sanity check
  140. if (!that.isDeletable) return;
  141. // clear name
  142. (that.options.nameable) ? that.$nameableInput.attr('disabled', 'disabled').val('') : '';
  143. // hide and remove delete button
  144. that.$deleteButton.parent().addClass('removed').end()
  145. that.$deleteButton.addClass('disabled').hide('slide', {
  146. direction: 'left',
  147. queue: false,
  148. complete: function(){
  149. that.$deleteButton.parent().remove();
  150. }
  151. }, 'slow');
  152. // hide previewDownload and remove the image
  153. $previewDownload.slideUp({
  154. duration: 'slow',
  155. queue: false,
  156. complete: function() { $(this).empty(); }
  157. });
  158. // Set deletable flag
  159. that.$deletableInput.val(true);
  160. // Disable delete button animation
  161. that.isDeletable = false;
  162. },
  163. _onPreviewImage: function(that) {
  164. var file = that.element.files[0];
  165. // load preview image
  166. window.loadImage(
  167. file,
  168. function (img) {
  169. var $activePreview = $('.'+that.element.id+'_preview_image.active');
  170. var $previewUpload = $activePreview.clone().empty().hide().removeClass('download').addClass('upload');
  171. var $filelabel = $('<div/>').addClass('row-fluid').text(file.name);
  172. var $filesize = $('<div/>').addClass('row-fluid').text(that._bytesToSize(file.size));
  173. // create and insert new preview node
  174. $previewUpload.appendTo($activePreview.parent())
  175. .html($(img).addClass('img-polaroid')).append($filelabel).append($filesize);
  176. // animate preview toggle
  177. $previewUpload.slideDown({
  178. duration: 'slow',
  179. queue: false,
  180. complete: function() { $(this).addClass('active'); }
  181. });
  182. $activePreview.slideUp({
  183. duration: 'slow',
  184. queue: false,
  185. complete: function() {
  186. ($(this).hasClass('download'))
  187. ? $(this).removeClass('active')
  188. : $(this).remove();
  189. }
  190. });
  191. }, {
  192. maxWidth: that.options.maxWidth,
  193. maxHeight: that.options.maxHeight,
  194. minWidth: that.options.minWidth,
  195. minHeight: that.options.minHeight,
  196. canvas: that.options.previewAsCanvas,
  197. noRevoke: true
  198. }
  199. );
  200. },
  201. _onPreviewFile: function(that) {
  202. var $activePreview = $('.'+that.element.id+'_preview_image.active');
  203. var $previewUpload = $activePreview.clone().empty().hide().removeClass('download').addClass('upload');
  204. var file = that.element.files[0];
  205. var filetype = that._checkFileType(that, file);
  206. var $fileicon = $('<div/>').addClass('fileicon').addClass(filetype)
  207. .html(that.options.filetypes[filetype]);
  208. var $filelabel = $('<div/>').addClass('row-fluid').text(file.name);
  209. var $filesize = $('<div/>').addClass('row-fluid').text(that._bytesToSize(file.size));
  210. // create and insert new preview node
  211. $previewUpload.appendTo($activePreview.parent())
  212. .html($fileicon).append($filelabel).append($filesize);
  213. // animate preview toggle
  214. $previewUpload.slideDown({
  215. duration: 'slow',
  216. queue: false,
  217. complete: function() { $(this).addClass('active'); }
  218. });
  219. $activePreview.slideUp({
  220. duration: 'slow',
  221. queue: false,
  222. complete: function() {
  223. ($(this).hasClass('download'))
  224. ? $(this).removeClass('active')
  225. : $(this).remove();
  226. }
  227. });
  228. },
  229. _bytesToSize: function(bytes) {
  230. var kilobyte = 1024;
  231. var megabyte = kilobyte * 1024;
  232. var gigabyte = megabyte * 1024;
  233. var terabyte = gigabyte * 1024;
  234. if (bytes < kilobyte) return bytes + ' B';
  235. else if (bytes < megabyte) return (bytes / kilobyte).toFixed(2) + ' KB';
  236. else if (bytes < gigabyte) return (bytes / megabyte).toFixed(2) + ' MB';
  237. else if (bytes < terabyte) return (bytes / gigabyte).toFixed(2) + ' GB';
  238. else return (bytes / terabyte).toFixed(2) + ' TB';
  239. },
  240. _checkFileType: function(that, file) {
  241. if (that._isAudio(file)) return 'audio';
  242. if (that._isArchive(file)) return 'archive';
  243. if (that._isHTML(file)) return 'html';
  244. if (that._isImage(file)) return 'image';
  245. if (that._isPDFDocument(file)) return 'pdf-document';
  246. if (that._isPlainText(file)) return 'plain-text';
  247. if (that._isPresentation(file)) return 'presentation';
  248. if (that._isSpreadsheet(file)) return 'spreadsheet';
  249. if (that._isTextDocument(file)) return 'text-document';
  250. if (that._isVideo(file)) return 'video';
  251. // else
  252. return 'unknown';
  253. },
  254. _isAudio: function(file) {
  255. return (file.type.match('audio/.*'));
  256. },
  257. _isArchive: function(file) {
  258. return (
  259. file.type.match('application/.*compress.*') ||
  260. file.type.match('application/.*archive.*') ||
  261. file.type.match('application/.*zip.*') ||
  262. file.type.match('application/.*tar.*') ||
  263. file.type.match('application/x\-ace') ||
  264. file.type.match('application/x\-bz2') ||
  265. file.type.match('gzip/document')
  266. );
  267. },
  268. _isHTML: function(file) {
  269. return (file.type.match('text/html'));
  270. },
  271. _isImage: function(file) {
  272. return (file.type.match('image/.*'));
  273. },
  274. _isPDFDocument: function(file) {
  275. return (
  276. file.type.match('application/acrobat') ||
  277. file.type.match('applications?/.*pdf.*') ||
  278. file.type.match('text/.*pdf.*')
  279. );
  280. },
  281. _isPlainText: function(file) {
  282. return (file.type.match('text/plain'));
  283. },
  284. _isPresentation: function(file) {
  285. return (
  286. file.type.match('application/.*ms\-powerpoint.*') ||
  287. file.type.match('application/.*officedocument\.presentationml.*') ||
  288. file.type.match('application/.*opendocument\.presentation.*')
  289. );
  290. },
  291. _isSpreadsheet: function(file) {
  292. return (
  293. file.type.match('application/.*ms\-excel.*') ||
  294. file.type.match('application/.*officedocument\.spreadsheetml.*') ||
  295. file.type.match('application/.*opendocument\.spreadsheet.*')
  296. );
  297. },
  298. _isTextDocument: function(file) {
  299. return (
  300. file.type.match('application/.*ms\-?word.*') ||
  301. file.type.match('application/.*officedocument\.wordprocessingml.*') ||
  302. file.type.match('application/.*opendocument\.text.*')
  303. );
  304. },
  305. _isVideo: function(file) {
  306. return (file.type.match('video/.*'));
  307. },
  308. _resetInput: function(that) {
  309. // create replacement input
  310. var $replacement = $(that.element).val('').clone(true);
  311. // replace inputs
  312. $(that.element).replaceWith( $replacement );
  313. // point plugin to new element
  314. that.element = $replacement[0];
  315. }
  316. };
  317. // You don't need to change something below:
  318. // A really lightweight plugin wrapper around the constructor,
  319. // preventing against multiple instantiations and allowing any
  320. // public function (ie. a function whose name doesn't start
  321. // with an underscore) to be called via the jQuery plugin,
  322. // e.g. $(element).defaultPluginName('functionName', arg1, arg2)
  323. $.fn[pluginName] = function ( options ) {
  324. var args = arguments;
  325. // Is the first parameter an object (options), or was omitted,
  326. // instantiate a new instance of the plugin.
  327. if (options === undefined || typeof options === 'object') {
  328. return this.each(function () {
  329. // Only allow the plugin to be instantiated once,
  330. // so we check that the element has no plugin instantiation yet
  331. if (!$.data(this, 'plugin_' + pluginName)) {
  332. // if it has no instance, create a new one,
  333. // pass options to our plugin constructor,
  334. // and store the plugin instance
  335. // in the elements jQuery data object.
  336. $.data(this, 'plugin_' + pluginName, new Plugin( this, options ));
  337. }
  338. });
  339. // If the first parameter is a string and it doesn't start
  340. // with an underscore or "contains" the `init`-function,
  341. // treat this as a call to a public method.
  342. } else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') {
  343. // Cache the method call
  344. // to make it possible
  345. // to return a value
  346. var returns;
  347. this.each(function () {
  348. var instance = $.data(this, 'plugin_' + pluginName);
  349. // Tests that there's already a plugin-instance
  350. // and checks that the requested public method exists
  351. if (instance instanceof Plugin && typeof instance[options] === 'function') {
  352. // Call the method of our plugin instance,
  353. // and pass it the supplied arguments.
  354. returns = instance[options].apply( instance, Array.prototype.slice.call( args, 1 ) );
  355. }
  356. // Allow instances to be destroyed via the 'destroy' method
  357. if (options === 'destroy') {
  358. $.data(this, 'plugin_' + pluginName, null);
  359. }
  360. });
  361. // If the earlier cached method
  362. // gives a value back return the value,
  363. // otherwise return this to preserve chainability.
  364. return returns !== undefined ? returns : this;
  365. }
  366. };
  367. }(jQuery, window));