filereader.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. /*!
  2. FileReader.js - v0.99
  3. A lightweight wrapper for common FileReader usage.
  4. Copyright 2014 Brian Grinstead - MIT License.
  5. See http://github.com/bgrins/filereader.js for documentation.
  6. */
  7. (function(window, document) {
  8. var FileReader = window.FileReader;
  9. var FileReaderSyncSupport = false;
  10. var workerScript = "self.addEventListener('message', function(e) { var data=e.data; try { var reader = new FileReaderSync; postMessage({ result: reader[data.readAs](data.file), extra: data.extra, file: data.file})} catch(e){ postMessage({ result:'error', extra:data.extra, file:data.file}); } }, false);";
  11. var syncDetectionScript = "onmessage = function(e) { postMessage(!!FileReaderSync); };";
  12. var fileReaderEvents = ['loadstart', 'progress', 'load', 'abort', 'error', 'loadend'];
  13. var sync = false;
  14. var FileReaderJS = window.FileReaderJS = {
  15. enabled: false,
  16. setupInput: setupInput,
  17. setupBlob: setupBlob,
  18. setupDrop: setupDrop,
  19. setupClipboard: setupClipboard,
  20. setSync: function (value) {
  21. sync = value;
  22. if (sync && !FileReaderSyncSupport) {
  23. checkFileReaderSyncSupport();
  24. }
  25. },
  26. getSync: function() {
  27. return sync && FileReaderSyncSupport;
  28. },
  29. output: [],
  30. opts: {
  31. dragClass: "drag",
  32. accept: false,
  33. readAsDefault: 'DataURL',
  34. readAsMap: {
  35. },
  36. on: {
  37. loadstart: noop,
  38. progress: noop,
  39. load: noop,
  40. abort: noop,
  41. error: noop,
  42. loadend: noop,
  43. skip: noop,
  44. groupstart: noop,
  45. groupend: noop,
  46. beforestart: noop
  47. }
  48. }
  49. };
  50. // Setup jQuery plugin (if available)
  51. if (typeof(jQuery) !== "undefined") {
  52. jQuery.fn.fileReaderJS = function(opts) {
  53. return this.each(function() {
  54. if (jQuery(this).is("input")) {
  55. setupInput(this, opts);
  56. }
  57. else {
  58. setupDrop(this, opts);
  59. }
  60. });
  61. };
  62. jQuery.fn.fileClipboard = function(opts) {
  63. return this.each(function() {
  64. setupClipboard(this, opts);
  65. });
  66. };
  67. }
  68. // Not all browsers support the FileReader interface. Return with the enabled bit = false.
  69. if (!FileReader) {
  70. return;
  71. }
  72. // makeWorker is a little wrapper for generating web workers from strings
  73. function makeWorker(script) {
  74. var URL = window.URL || window.webkitURL;
  75. var Blob = window.Blob;
  76. var Worker = window.Worker;
  77. if (!URL || !Blob || !Worker || !script) {
  78. return null;
  79. }
  80. var blob = new Blob([script]);
  81. var worker = new Worker(URL.createObjectURL(blob));
  82. return worker;
  83. }
  84. // setupClipboard: bind to clipboard events (intended for document.body)
  85. function setupClipboard(element, opts) {
  86. if (!FileReaderJS.enabled) {
  87. return;
  88. }
  89. var instanceOptions = extend(extend({}, FileReaderJS.opts), opts);
  90. element.addEventListener("paste", onpaste, false);
  91. function onpaste(e) {
  92. var files = [];
  93. var clipboardData = e.clipboardData || {};
  94. var items = clipboardData.items || [];
  95. for (var i = 0; i < items.length; i++) {
  96. var file = items[i].getAsFile();
  97. if (file) {
  98. // Create a fake file name for images from clipboard, since this data doesn't get sent
  99. var matches = new RegExp("/\(.*\)").exec(file.type);
  100. if (!file.name && matches) {
  101. var extension = matches[1];
  102. file.name = "clipboard" + i + "." + extension;
  103. }
  104. files.push(file);
  105. }
  106. }
  107. if (files.length) {
  108. processFileList(e, files, instanceOptions);
  109. e.preventDefault();
  110. e.stopPropagation();
  111. }
  112. }
  113. }
  114. // setupInput: bind the 'change' event to an input[type=file]
  115. function setupInput(input, opts) {
  116. if (!FileReaderJS.enabled) {
  117. return;
  118. }
  119. var instanceOptions = extend(extend({}, FileReaderJS.opts), opts);
  120. input.addEventListener("change", inputChange, false);
  121. input.addEventListener("drop", inputDrop, false);
  122. function inputChange(e) {
  123. processFileList(e, input.files, instanceOptions);
  124. }
  125. function inputDrop(e) {
  126. e.stopPropagation();
  127. e.preventDefault();
  128. processFileList(e, e.dataTransfer.files, instanceOptions);
  129. }
  130. }
  131. // setupFile: bind the 'change' event to an input[type=file]
  132. function setupBlob(blob, opts) {
  133. if (!FileReaderJS.enabled) {
  134. return;
  135. }
  136. if(blob.constructor !== Array && blob.constructor !== Function){
  137. if(blob.name === undefined){
  138. blob.name = "blob";
  139. }
  140. blob = [blob];
  141. }else{
  142. if(blob[0].name === undefined){
  143. blob[0].name = "blob";
  144. }
  145. }
  146. var instanceOptions = extend(extend({}, FileReaderJS.opts), opts);
  147. processFileList(null, blob, instanceOptions);
  148. }
  149. // setupDrop: bind the 'drop' event for a DOM element
  150. function setupDrop(dropbox, opts) {
  151. if (!FileReaderJS.enabled) {
  152. return;
  153. }
  154. var instanceOptions = extend(extend({}, FileReaderJS.opts), opts);
  155. var dragClass = instanceOptions.dragClass;
  156. var initializedOnBody = false;
  157. // Bind drag events to the dropbox to add the class while dragging, and accept the drop data transfer.
  158. dropbox.addEventListener("dragenter", onlyWithFiles(dragenter), false);
  159. dropbox.addEventListener("dragleave", onlyWithFiles(dragleave), false);
  160. dropbox.addEventListener("dragover", onlyWithFiles(dragover), false);
  161. dropbox.addEventListener("drop", onlyWithFiles(drop), false);
  162. // Bind to body to prevent the dropbox events from firing when it was initialized on the page.
  163. document.body.addEventListener("dragstart", bodydragstart, true);
  164. document.body.addEventListener("dragend", bodydragend, true);
  165. document.body.addEventListener("drop", bodydrop, false);
  166. function bodydragend(e) {
  167. initializedOnBody = false;
  168. }
  169. function bodydragstart(e) {
  170. initializedOnBody = true;
  171. }
  172. function bodydrop(e) {
  173. if (e.dataTransfer.files && e.dataTransfer.files.length ){
  174. e.stopPropagation();
  175. e.preventDefault();
  176. }
  177. }
  178. function onlyWithFiles(fn) {
  179. return function() {
  180. if (!initializedOnBody) {
  181. fn.apply(this, arguments);
  182. }
  183. };
  184. }
  185. function drop(e) {
  186. e.stopPropagation();
  187. e.preventDefault();
  188. if (dragClass) {
  189. removeClass(dropbox, dragClass);
  190. }
  191. processFileList(e, e.dataTransfer.files, instanceOptions);
  192. }
  193. function dragenter(e) {
  194. e.stopPropagation();
  195. e.preventDefault();
  196. if (dragClass) {
  197. addClass(dropbox, dragClass);
  198. }
  199. }
  200. function dragleave(e) {
  201. if (dragClass) {
  202. removeClass(dropbox, dragClass);
  203. }
  204. }
  205. function dragover(e) {
  206. e.stopPropagation();
  207. e.preventDefault();
  208. if (dragClass) {
  209. addClass(dropbox, dragClass);
  210. }
  211. }
  212. }
  213. // setupCustomFileProperties: modify the file object with extra properties
  214. function setupCustomFileProperties(files, groupID) {
  215. for (var i = 0; i < files.length; i++) {
  216. var file = files[i];
  217. file.extra = {
  218. nameNoExtension: file.name.substring(0, file.name.lastIndexOf('.')),
  219. extension: file.name.substring(file.name.lastIndexOf('.') + 1),
  220. fileID: i,
  221. uniqueID: getUniqueID(),
  222. groupID: groupID,
  223. prettySize: prettySize(file.size)
  224. };
  225. }
  226. }
  227. // getReadAsMethod: return method name for 'readAs*' - http://www.w3.org/TR/FileAPI/#reading-a-file
  228. function getReadAsMethod(type, readAsMap, readAsDefault) {
  229. for (var r in readAsMap) {
  230. if (type.match(new RegExp(r))) {
  231. return 'readAs' + readAsMap[r];
  232. }
  233. }
  234. return 'readAs' + readAsDefault;
  235. }
  236. // processFileList: read the files with FileReader, send off custom events.
  237. function processFileList(e, files, opts) {
  238. var filesLeft = files.length;
  239. var group = {
  240. groupID: getGroupID(),
  241. files: files,
  242. started: new Date()
  243. };
  244. function groupEnd() {
  245. group.ended = new Date();
  246. opts.on.groupend(group);
  247. }
  248. function groupFileDone() {
  249. if (--filesLeft === 0) {
  250. groupEnd();
  251. }
  252. }
  253. FileReaderJS.output.push(group);
  254. setupCustomFileProperties(files, group.groupID);
  255. opts.on.groupstart(group);
  256. // No files in group - end immediately
  257. if (!files.length) {
  258. groupEnd();
  259. return;
  260. }
  261. var supportsSync = sync && FileReaderSyncSupport;
  262. var syncWorker;
  263. // Only initialize the synchronous worker if the option is enabled - to prevent the overhead
  264. if (supportsSync) {
  265. syncWorker = makeWorker(workerScript);
  266. syncWorker.onmessage = function(e) {
  267. var file = e.data.file;
  268. var result = e.data.result;
  269. // Workers seem to lose the custom property on the file object.
  270. if (!file.extra) {
  271. file.extra = e.data.extra;
  272. }
  273. file.extra.ended = new Date();
  274. // Call error or load event depending on success of the read from the worker.
  275. opts.on[result === "error" ? "error" : "load"]({ target: { result: result } }, file);
  276. groupFileDone();
  277. };
  278. }
  279. Array.prototype.forEach.call(files, function(file) {
  280. file.extra.started = new Date();
  281. if (opts.accept && !file.type.match(new RegExp(opts.accept))) {
  282. opts.on.skip(file);
  283. groupFileDone();
  284. return;
  285. }
  286. if (opts.on.beforestart(file) === false) {
  287. opts.on.skip(file);
  288. groupFileDone();
  289. return;
  290. }
  291. var readAs = getReadAsMethod(file.type, opts.readAsMap, opts.readAsDefault);
  292. if (syncWorker) {
  293. syncWorker.postMessage({
  294. file: file,
  295. extra: file.extra,
  296. readAs: readAs
  297. });
  298. }
  299. else {
  300. var reader = new FileReader();
  301. reader.originalEvent = e;
  302. fileReaderEvents.forEach(function(eventName) {
  303. reader['on' + eventName] = function(e) {
  304. if (eventName == 'load' || eventName == 'error') {
  305. file.extra.ended = new Date();
  306. }
  307. opts.on[eventName](e, file);
  308. if (eventName == 'loadend') {
  309. groupFileDone();
  310. }
  311. };
  312. });
  313. reader[readAs](file);
  314. }
  315. });
  316. }
  317. // checkFileReaderSyncSupport: Create a temporary worker and see if FileReaderSync exists
  318. function checkFileReaderSyncSupport() {
  319. var worker = makeWorker(syncDetectionScript);
  320. if (worker) {
  321. worker.onmessage =function(e) {
  322. FileReaderSyncSupport = e.data;
  323. };
  324. worker.postMessage({});
  325. }
  326. }
  327. // noop: do nothing
  328. function noop() {
  329. }
  330. // extend: used to make deep copies of options object
  331. function extend(destination, source) {
  332. for (var property in source) {
  333. if (source[property] && source[property].constructor &&
  334. source[property].constructor === Object) {
  335. destination[property] = destination[property] || {};
  336. arguments.callee(destination[property], source[property]);
  337. }
  338. else {
  339. destination[property] = source[property];
  340. }
  341. }
  342. return destination;
  343. }
  344. // hasClass: does an element have the css class?
  345. function hasClass(el, name) {
  346. return new RegExp("(?:^|\\s+)" + name + "(?:\\s+|$)").test(el.className);
  347. }
  348. // addClass: add the css class for the element.
  349. function addClass(el, name) {
  350. if (!hasClass(el, name)) {
  351. el.className = el.className ? [el.className, name].join(' ') : name;
  352. }
  353. }
  354. // removeClass: remove the css class from the element.
  355. function removeClass(el, name) {
  356. if (hasClass(el, name)) {
  357. var c = el.className;
  358. el.className = c.replace(new RegExp("(?:^|\\s+)" + name + "(?:\\s+|$)", "g"), " ").replace(/^\s\s*/, '').replace(/\s\s*$/, '');
  359. }
  360. }
  361. // prettySize: convert bytes to a more readable string.
  362. function prettySize(bytes) {
  363. var s = ['bytes', 'kb', 'MB', 'GB', 'TB', 'PB'];
  364. var e = Math.floor(Math.log(bytes)/Math.log(1024));
  365. return (bytes/Math.pow(1024, Math.floor(e))).toFixed(2)+" "+s[e];
  366. }
  367. // getGroupID: generate a unique int ID for groups.
  368. var getGroupID = (function(id) {
  369. return function() {
  370. return id++;
  371. };
  372. })(0);
  373. // getUniqueID: generate a unique int ID for files
  374. var getUniqueID = (function(id) {
  375. return function() {
  376. return id++;
  377. };
  378. })(0);
  379. // The interface is supported, bind the FileReaderJS callbacks
  380. FileReaderJS.enabled = true;
  381. })(this, document);