/app/assets/javascripts/jquery.uploadifive.js

https://bitbucket.org/timgluz/droplocally · JavaScript · 887 lines · 636 code · 109 blank · 142 comment · 166 complexity · 34866d4b71a4ea5e46db5a225d9cfe8c MD5 · raw file

  1. /*
  2. UploadiFive 1.1.1
  3. Copyright (c) 2012 Reactive Apps, Ronnie Garcia
  4. Released under the UploadiFive Standard License <http://www.uploadify.com/uploadifive-standard-license>
  5. */
  6. ;(function($) {
  7. var methods = {
  8. init : function(options) {
  9. return this.each(function() {
  10. // Create a reference to the jQuery DOM object
  11. var $this = $(this);
  12. $this.data('uploadifive', {
  13. inputs : {}, // The object that contains all the file inputs
  14. inputCount : 0, // The total number of file inputs created
  15. fileID : 0,
  16. queue : {
  17. count : 0, // Total number of files in the queue
  18. selected : 0, // Number of files selected in the last select operation
  19. replaced : 0, // Number of files replaced in the last select operation
  20. errors : 0, // Number of files that returned an error in the last select operation
  21. queued : 0, // Number of files added to the queue in the last select operation
  22. cancelled : 0 // Total number of files that have been cancelled or removed from the queue
  23. },
  24. uploads : {
  25. current : 0, // Number of files currently being uploaded
  26. attempts : 0, // Number of file uploads attempted in the last upload operation
  27. successful : 0, // Number of files successfully uploaded in the last upload operation
  28. errors : 0, // Number of files returning errors in the last upload operation
  29. count : 0 // Total number of files uploaded successfully
  30. }
  31. });
  32. var $data = $this.data('uploadifive');
  33. // Set the default options
  34. var settings = $data.settings = $.extend({
  35. 'auto' : true, // Automatically upload a file when it's added to the queue
  36. 'buttonClass' : false, // A class to add to the UploadiFive button
  37. 'buttonText' : 'Select Files', // The text that appears on the UploadiFive button
  38. 'checkScript' : false, // Path to the script that checks for existing file names
  39. 'dnd' : true, // Allow drag and drop into the queue
  40. 'dropTarget' : false, // Selector for the drop target
  41. 'fileObjName' : 'Filedata', // The name of the file object to use in your server-side script
  42. 'fileSizeLimit' : 0, // Maximum allowed size of files to upload
  43. 'fileType' : false, // Type of files allowed (image, etc)
  44. 'formData' : {}, // Additional data to send to the upload script
  45. 'height' : 30, // The height of the button
  46. 'itemTemplate' : false, // The HTML markup for the item in the queue
  47. 'method' : 'post', // The method to use when submitting the upload
  48. 'multi' : true, // Set to true to allow multiple file selections
  49. 'overrideEvents' : [], // An array of events to override
  50. 'queueID' : false, // The ID of the file queue
  51. 'queueSizeLimit' : 0, // The maximum number of files that can be in the queue
  52. 'removeCompleted' : false, // Set to true to remove files that have completed uploading
  53. 'simUploadLimit' : 0, // The maximum number of files to upload at once
  54. 'truncateLength' : 0, // The length to truncate the file names to
  55. 'uploadLimit' : 0, // The maximum number of files you can upload
  56. 'uploadScript' : 'uploadifive.php', // The path to the upload script
  57. 'width' : 100 // The width of the button
  58. /*
  59. // Events
  60. 'onAddQueueItem' : function(file) {}, // Triggered for each file that is added to the queue
  61. 'onCancel' : function(file) {}, // Triggered when a file is cancelled or removed from the queue
  62. 'onCheck' : function(file, exists) {}, // Triggered when the server is checked for an existing file
  63. 'onClearQueue' : function(queue) {}, // Triggered during the clearQueue function
  64. 'onDestroy' : function() {} // Triggered during the destroy function
  65. 'onDrop' : function(files, numberOfFilesDropped) {}, // Triggered when files are dropped into the file queue
  66. 'onError' : function(file, fileType, data) {}, // Triggered when an error occurs
  67. 'onFallback' : function() {}, // Triggered if the HTML5 File API is not supported by the browser
  68. 'onInit' : function() {}, // Triggered when UploadiFive if initialized
  69. 'onQueueComplete' : function() {}, // Triggered once when an upload queue is done
  70. 'onProgress' : function(file, event) {}, // Triggered during each progress update of an upload
  71. 'onSelect' : function() {}, // Triggered once when files are selected from a dialog box
  72. 'onUpload' : function(file) {}, // Triggered when an upload queue is started
  73. 'onUploadComplete' : function(file, data) {}, // Triggered when a file is successfully uploaded
  74. 'onUploadFile' : function(file) {}, // Triggered for each file being uploaded
  75. */
  76. }, options);
  77. // Calculate the file size limit
  78. if (isNaN(settings.fileSizeLimit)) {
  79. var fileSizeLimitBytes = parseInt(settings.fileSizeLimit) * 1.024
  80. if (settings.fileSizeLimit.indexOf('KB') > -1) {
  81. settings.fileSizeLimit = fileSizeLimitBytes * 1000;
  82. } else if (settings.fileSizeLimit.indexOf('MB') > -1) {
  83. settings.fileSizeLimit = fileSizeLimitBytes * 1000000;
  84. } else if (settings.fileSizeLimit.indexOf('GB') > -1) {
  85. settings.fileSizeLimit = fileSizeLimitBytes * 1000000000;
  86. }
  87. } else {
  88. settings.fileSizeLimit = settings.fileSizeLimit * 1024;
  89. }
  90. // Create a template for a file input
  91. $data.inputTemplate = $('<input type="file">')
  92. .css({
  93. 'font-size' : settings.height + 'px',
  94. 'opacity' : 0,
  95. 'position' : 'absolute',
  96. 'right' : '-3px',
  97. 'top' : '-3px',
  98. 'z-index' : 999
  99. });
  100. // Create a new input
  101. $data.createInput = function() {
  102. // Create a clone of the file input
  103. var input = $data.inputTemplate.clone();
  104. // Create a unique name for the input item
  105. var inputName = input.name = 'input' + $data.inputCount++;
  106. // Set the multiple attribute
  107. if (settings.multi) {
  108. input.attr('multiple', true);
  109. }
  110. // Set the onchange event for the input
  111. input.bind('change', function() {
  112. $data.queue.selected = 0;
  113. $data.queue.replaced = 0;
  114. $data.queue.errors = 0;
  115. $data.queue.queued = 0;
  116. // Add a queue item to the queue for each file
  117. var limit = this.files.length;
  118. $data.queue.selected = limit;
  119. if (($data.queue.count + limit) > settings.queueSizeLimit && settings.queueSizeLimit !== 0) {
  120. if ($.inArray('onError', settings.overrideEvents) < 0) {
  121. alert('The maximum number of queue items has been reached (' + settings.queueSizeLimit + '). Please select fewer files.');
  122. }
  123. // Trigger the error event
  124. if (typeof settings.onError === 'function') {
  125. settings.onError.call($this, 'QUEUE_LIMIT_EXCEEDED');
  126. }
  127. } else {
  128. for (var n = 0; n < limit; n++) {
  129. file = this.files[n];
  130. $data.addQueueItem(file);
  131. }
  132. $data.inputs[inputName] = this;
  133. $data.createInput();
  134. }
  135. // Upload the file if auto-uploads are enabled
  136. if (settings.auto) {
  137. methods.upload.call($this);
  138. }
  139. // Trigger the select event
  140. if (typeof settings.onSelect === 'function') {
  141. settings.onSelect.call($this, $data.queue);
  142. }
  143. });
  144. // Hide the existing current item and add the new one
  145. if ($data.currentInput) {
  146. $data.currentInput.hide();
  147. }
  148. $data.button.append(input);
  149. $data.currentInput = input;
  150. }
  151. // Remove an input
  152. $data.destroyInput = function(key) {
  153. $($data.inputs[key]).remove();
  154. delete $data.inputs[key];
  155. $data.inputCount--;
  156. }
  157. // Drop a file into the queue
  158. $data.drop = function(e) {
  159. $data.queue.selected = 0;
  160. $data.queue.replaced = 0;
  161. $data.queue.errors = 0;
  162. $data.queue.queued = 0;
  163. var fileData = e.dataTransfer;
  164. var inputName = fileData.name = 'input' + $data.inputCount++;
  165. // Add a queue item to the queue for each file
  166. var limit = fileData.files.length;
  167. $data.queue.selected = limit;
  168. if (($data.queue.count + limit) > settings.queueSizeLimit && settings.queueSizeLimit !== 0) {
  169. // Check if the queueSizeLimit was reached
  170. if ($.inArray('onError', settings.overrideEvents) < 0) {
  171. alert('The maximum number of queue items has been reached (' + settings.queueSizeLimit + '). Please select fewer files.');
  172. }
  173. // Trigger the onError event
  174. if (typeof settings.onError === 'function') {
  175. settings.onError.call($this, 'QUEUE_LIMIT_EXCEEDED');
  176. }
  177. } else {
  178. // Add a queue item for each file
  179. for (var n = 0; n < limit; n++) {
  180. file = fileData.files[n];
  181. $data.addQueueItem(file);
  182. }
  183. // Save the data to the inputs object
  184. $data.inputs[inputName] = fileData;
  185. }
  186. // Upload the file if auto-uploads are enabled
  187. if (settings.auto) {
  188. methods.upload.call($this);
  189. }
  190. // Trigger the onDrop event
  191. if (typeof settings.onDrop === 'function') {
  192. settings.onDrop.call($this, fileData.files, fileData.files.length);
  193. }
  194. // Stop FireFox from opening the dropped file(s)
  195. e.preventDefault();
  196. e.stopPropagation();
  197. }
  198. // Check if a filename exists in the queue
  199. $data.fileExistsInQueue = function(file) {
  200. for (var key in $data.inputs) {
  201. input = $data.inputs[key];
  202. limit = input.files.length;
  203. for (var n = 0; n < limit; n++) {
  204. existingFile = input.files[n];
  205. // Check if the filename matches
  206. if (existingFile.name == file.name && !existingFile.complete) {
  207. return true;
  208. }
  209. }
  210. }
  211. return false;
  212. }
  213. // Remove an existing file in the queue
  214. $data.removeExistingFile = function(file) {
  215. for (var key in $data.inputs) {
  216. input = $data.inputs[key];
  217. limit = input.files.length;
  218. for (var n = 0; n < limit; n++) {
  219. existingFile = input.files[n];
  220. // Check if the filename matches
  221. if (existingFile.name == file.name && !existingFile.complete) {
  222. $data.queue.replaced++;
  223. methods.cancel.call($this, existingFile, true);
  224. }
  225. }
  226. }
  227. }
  228. // Create the file item template
  229. if (settings.itemTemplate == false) {
  230. $data.queueItem = $('<div class="uploadifive-queue-item">\
  231. <a class="close" href="#">X</a>\
  232. <div><span class="filename"></span><span class="fileinfo"></span></div>\
  233. <div class="progress">\
  234. <div class="progress-bar"></div>\
  235. </div>\
  236. </div>');
  237. } else {
  238. $data.queueItem = $(settings.itemTemplate);
  239. }
  240. // Add an item to the queue
  241. $data.addQueueItem = function(file) {
  242. if ($.inArray('onAddQueueItem', settings.overrideEvents) < 0) {
  243. // Check if the filename already exists in the queue
  244. $data.removeExistingFile(file);
  245. // Create a clone of the queue item template
  246. file.queueItem = $data.queueItem.clone();
  247. // Add an ID to the queue item
  248. file.queueItem.attr('id', settings.id + '-file-' + $data.fileID++);
  249. // Bind the close event to the close button
  250. file.queueItem.find('.close').bind('click', function() {
  251. methods.cancel.call($this, file);
  252. return false;
  253. });
  254. var fileName = file.name;
  255. if (fileName.length > settings.truncateLength && settings.truncateLength != 0) {
  256. fileName = fileName.substring(0, settings.truncateLength) + '...';
  257. }
  258. file.queueItem.find('.filename').html(fileName);
  259. // Add a reference to the file
  260. file.queueItem.data('file', file);
  261. $data.queueEl.append(file.queueItem);
  262. }
  263. // Trigger the addQueueItem event
  264. if (typeof settings.onAddQueueItem === 'function') {
  265. settings.onAddQueueItem.call($this, file);
  266. }
  267. // Check the filetype
  268. if (settings.fileType) {
  269. if ($.isArray(settings.fileType)) {
  270. var isValidFileType = false;
  271. for (var n = 0; n < settings.fileType.length; n++) {
  272. if (file.type.indexOf(settings.fileType[n]) > -1) {
  273. isValidFileType = true;
  274. }
  275. }
  276. if (!isValidFileType) {
  277. $data.error('FORBIDDEN_FILE_TYPE', file);
  278. }
  279. } else {
  280. if (file.type.indexOf(settings.fileType) < 0) {
  281. $data.error('FORBIDDEN_FILE_TYPE', file);
  282. }
  283. }
  284. }
  285. // Check the filesize
  286. if (file.size > settings.fileSizeLimit && settings.fileSizeLimit != 0) {
  287. $data.error('FILE_SIZE_LIMIT_EXCEEDED', file);
  288. } else {
  289. $data.queue.queued++;
  290. $data.queue.count++;
  291. }
  292. }
  293. // Remove an item from the queue
  294. $data.removeQueueItem = function(file, instant, delay) {
  295. // Set the default delay
  296. if (!delay) delay = 0;
  297. var fadeTime = instant ? 0 : 500;
  298. if (file.queueItem) {
  299. if (file.queueItem.find('.fileinfo').html() != ' - Completed') {
  300. file.queueItem.find('.fileinfo').html(' - Cancelled');
  301. }
  302. file.queueItem.find('.progress-bar').width(0);
  303. file.queueItem.delay(delay).fadeOut(fadeTime, function() {
  304. $(this).remove();
  305. });
  306. delete file.queueItem;
  307. $data.queue.count--;
  308. }
  309. }
  310. // Count the number of files that need to be uploaded
  311. $data.filesToUpload = function() {
  312. var filesToUpload = 0;
  313. for (var key in $data.inputs) {
  314. input = $data.inputs[key];
  315. limit = input.files.length;
  316. for (var n = 0; n < limit; n++) {
  317. file = input.files[n];
  318. if (!file.skip && !file.complete) {
  319. filesToUpload++;
  320. }
  321. }
  322. }
  323. return filesToUpload;
  324. }
  325. // Check if a file exists
  326. $data.checkExists = function(file) {
  327. if ($.inArray('onCheck', settings.overrideEvents) < 0) {
  328. // This request needs to be synchronous
  329. $.ajaxSetup({
  330. 'async' : false
  331. });
  332. // Send the filename to the check script
  333. var checkData = $.extend(settings.formData, {filename: file.name});
  334. $.post(settings.checkScript, checkData, function(fileExists) {
  335. file.exists = parseInt(fileExists);
  336. });
  337. if (file.exists) {
  338. if (!confirm('A file named ' + file.name + ' already exists in the upload folder.\nWould you like to replace it?')) {
  339. // If not replacing the file, cancel the upload
  340. methods.cancel.call($this, file);
  341. return true;
  342. }
  343. }
  344. }
  345. // Trigger the check event
  346. if (typeof settings.onCheck === 'function') {
  347. settings.onCheck.call($this, file, file.exists);
  348. }
  349. return false;
  350. }
  351. // Upload a single file
  352. $data.uploadFile = function(file, uploadAll) {
  353. if (!file.skip && !file.complete && !file.uploading) {
  354. file.uploading = true;
  355. $data.uploads.current++;
  356. $data.uploads.attempted++;
  357. // Create a new AJAX request
  358. xhr = file.xhr = new XMLHttpRequest();
  359. // Start the upload
  360. // Use the faster FormData if it exists
  361. if (typeof FormData === 'function' || typeof FormData === 'object') {
  362. // Create a new FormData object
  363. var formData = new FormData();
  364. // Add the form data
  365. formData.append(settings.fileObjName, file);
  366. // Add the rest of the formData
  367. for (i in settings.formData) {
  368. formData.append(i, settings.formData[i]);
  369. }
  370. // Open the AJAX call
  371. xhr.open(settings.method, settings.uploadScript, true);
  372. // On progress function
  373. xhr.upload.addEventListener('progress', function(e) {
  374. if (e.lengthComputable) {
  375. $data.progress(e, file);
  376. }
  377. }, false);
  378. // On complete function
  379. xhr.addEventListener('load', function(e) {
  380. if (this.readyState == 4) {
  381. file.uploading = false;
  382. if (this.status == 200) {
  383. if (file.xhr.responseText !== 'Invalid file type.') {
  384. $data.uploadComplete(e, file, uploadAll);
  385. } else {
  386. $data.error(file.xhr.responseText, file, uploadAll);
  387. }
  388. } else if (this.status == 404) {
  389. $data.error('404_FILE_NOT_FOUND', file, uploadAll);
  390. } else if (this.status == 403) {
  391. $data.error('403_FORBIDDEN', file, uplaodAll);
  392. } else {
  393. $data.error('Unknown Error', file, uploadAll);
  394. }
  395. }
  396. });
  397. // Send the form data (multipart/form-data)
  398. xhr.send(formData);
  399. } else {
  400. // Send as binary
  401. var reader = new FileReader();
  402. reader.onload = function(e) {
  403. // Set some file builder variables
  404. var boundary = '-------------------------' + (new Date).getTime(),
  405. dashes = '--',
  406. eol = '\r\n',
  407. binFile = '';
  408. // Build an RFC2388 String
  409. binFile += dashes + boundary + eol;
  410. // Generate the headers
  411. binFile += 'Content-Disposition: form-data; name="' + settings.fileObjName + '"';
  412. if (file.name) {
  413. binFile += '; filename="' + file.name + '"';
  414. }
  415. binFile += eol;
  416. binFile += 'Content-Type: application/octet-stream' + eol + eol;
  417. binFile += e.target.result + eol;
  418. for (key in settings.formData) {
  419. binFile += dashes + boundary + eol;
  420. binFile += 'Content-Disposition: form-data; name="' + key + '"' + eol + eol;
  421. binFile += settings.formData[key] + eol;
  422. }
  423. binFile += dashes + boundary + dashes + eol;
  424. // On progress function
  425. xhr.upload.addEventListener('progress', function(e) {
  426. $data.progress(e, file);
  427. }, false);
  428. // On complete function
  429. xhr.addEventListener('load', function(e) {
  430. file.uploading = false;
  431. var status = this.status;
  432. if (status == 404) {
  433. $data.error('404_FILE_NOT_FOUND', file, uploadAll);
  434. } else {
  435. if (file.xhr.responseText != 'Invalid file type.') {
  436. $data.uploadComplete(e, file, uploadAll);
  437. } else {
  438. $data.error(file.xhr.responseText, file, uploadAll);
  439. }
  440. }
  441. }, false);
  442. // Open the ajax request
  443. var url = settings.uploadScript;
  444. if (settings.method == 'get') {
  445. var params = $(settings.formData).param();
  446. url += params;
  447. }
  448. xhr.open(settings.method, settings.uploadScript, true);
  449. xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
  450. // Trigger the uploadFile event
  451. if (typeof settings.onUploadFile === 'function') {
  452. settings.onUploadFile.call($this, file);
  453. }
  454. // Send the file for upload
  455. xhr.sendAsBinary(binFile);
  456. }
  457. reader.readAsBinaryString(file);
  458. }
  459. }
  460. }
  461. // Update a file upload's progress
  462. $data.progress = function(e, file) {
  463. if ($.inArray('onProgress', settings.overrideEvents) < 0) {
  464. if (e.lengthComputable) {
  465. var percent = Math.round((e.loaded / e.total) * 100);
  466. }
  467. file.queueItem.find('.fileinfo').html(' - ' + percent + '%');
  468. file.queueItem.find('.progress-bar').css('width', percent + '%');
  469. }
  470. // Trigger the progress event
  471. if (typeof settings.onProgress === 'function') {
  472. settings.onProgress.call($this, file, e);
  473. }
  474. }
  475. // Trigger an error
  476. $data.error = function(errorType, file, uploadAll) {
  477. if ($.inArray('onError', settings.overrideEvents) < 0) {
  478. // Get the error message
  479. switch(errorType) {
  480. case '404_FILE_NOT_FOUND':
  481. errorMsg = '404 Error';
  482. break;
  483. case '403_FORBIDDEN':
  484. errorMsg = '403 Forbidden';
  485. break;
  486. case 'FORBIDDEN_FILE_TYPE':
  487. errorMsg = 'Forbidden File Type';
  488. break;
  489. case 'FILE_SIZE_LIMIT_EXCEEDED':
  490. errorMsg = 'File Too Large';
  491. break;
  492. default:
  493. errorMsg = 'Unknown Error';
  494. break;
  495. }
  496. // Add the error class to the queue item
  497. file.queueItem.addClass('error')
  498. // Output the error in the queue item
  499. .find('.fileinfo').html(' - ' + errorMsg);
  500. // Hide the
  501. file.queueItem.find('.progress').remove();
  502. }
  503. // Trigger the error event
  504. if (typeof settings.onError === 'function') {
  505. settings.onError.call($this, errorType, file);
  506. }
  507. file.skip = true;
  508. if (errorType == '404_FILE_NOT_FOUND') {
  509. $data.uploads.errors++;
  510. } else {
  511. $data.queue.errors++;
  512. }
  513. if (uploadAll) {
  514. methods.upload.call($this, null, true);
  515. }
  516. }
  517. // Trigger when a single file upload is complete
  518. $data.uploadComplete = function(e, file, uploadAll) {
  519. if ($.inArray('onUploadComplete', settings.overrideEvents) < 0) {
  520. file.queueItem.find('.progress-bar').css('width', '100%');
  521. file.queueItem.find('.fileinfo').html(' - Completed');
  522. file.queueItem.find('.progress').slideUp(250);
  523. file.queueItem.addClass('complete');
  524. }
  525. // Trigger the complete event
  526. if (typeof settings.onUploadComplete === 'function') {
  527. settings.onUploadComplete.call($this, file, file.xhr.responseText);
  528. }
  529. if (settings.removeCompleted) {
  530. setTimeout(function() { methods.cancel.call($this, file); }, 3000);
  531. }
  532. file.complete = true;
  533. $data.uploads.successful++;
  534. $data.uploads.count++;
  535. $data.uploads.current--;
  536. delete file.xhr;
  537. if (uploadAll) {
  538. methods.upload.call($this, null, true);
  539. }
  540. }
  541. // Trigger when all the files are done uploading
  542. $data.queueComplete = function() {
  543. // Trigger the queueComplete event
  544. if (typeof settings.onQueueComplete === 'function') {
  545. settings.onQueueComplete.call($this, $data.uploads);
  546. }
  547. }
  548. // ----------------------
  549. // Initialize UploadiFive
  550. // ----------------------
  551. // Check if HTML5 is available
  552. if (window.File && window.FileList && window.Blob && (window.FileReader || window.FormData)) {
  553. // Assign an ID to the object
  554. settings.id = 'uploadifive-' + $this.attr('id');
  555. // Wrap the file input in a div with overflow set to hidden
  556. $data.button = $('<div id="' + settings.id + '" class="uploadifive-button">' + settings.buttonText + '</div>');
  557. if (settings.buttonClass) $data.button.addClass(settings.buttonClass);
  558. // Style the button wrapper
  559. $data.button.css({
  560. 'height' : settings.height,
  561. 'line-height' : settings.height + 'px',
  562. 'overflow' : 'hidden',
  563. 'position' : 'relative',
  564. 'text-align' : 'center',
  565. 'width' : settings.width
  566. });
  567. // Insert the button above the file input
  568. $this.before($data.button)
  569. // Add the file input to the button
  570. .appendTo($data.button)
  571. // Modify the styles of the file input
  572. .hide();
  573. // Create a new input
  574. $data.createInput.call($this);
  575. // Create the queue container
  576. if (!settings.queueID) {
  577. settings.queueID = settings.id + '-queue';
  578. $data.queueEl = $('<div id="' + settings.queueID + '" class="uploadifive-queue" />');
  579. $data.button.after($data.queueEl);
  580. } else {
  581. $data.queueEl = $('#' + settings.queueID);
  582. }
  583. // Add drag and drop functionality
  584. if (settings.dnd) {
  585. var $dropTarget = settings.dropTarget ? $(settings.dropTarget) : $data.queueEl.get(0);
  586. $dropTarget.addEventListener('dragleave', function(e) {
  587. // Stop FireFox from opening the dropped file(s)
  588. e.preventDefault();
  589. e.stopPropagation();
  590. }, false);
  591. $dropTarget.addEventListener('dragenter', function(e) {
  592. // Stop FireFox from opening the dropped file(s)
  593. e.preventDefault();
  594. e.stopPropagation();
  595. }, false);
  596. $dropTarget.addEventListener('dragover', function(e) {
  597. // Stop FireFox from opening the dropped file(s)
  598. e.preventDefault();
  599. e.stopPropagation();
  600. }, false);
  601. $dropTarget.addEventListener('drop', $data.drop, false);
  602. }
  603. // Send as binary workaround for Chrome
  604. if (!XMLHttpRequest.prototype.sendAsBinary) {
  605. XMLHttpRequest.prototype.sendAsBinary = function(datastr) {
  606. function byteValue(x) {
  607. return x.charCodeAt(0) & 0xff;
  608. }
  609. var ords = Array.prototype.map.call(datastr, byteValue);
  610. var ui8a = new Uint8Array(ords);
  611. this.send(ui8a.buffer);
  612. }
  613. }
  614. // Trigger the oninit event
  615. if (typeof settings.onInit === 'function') {
  616. settings.onInit.call($this);
  617. }
  618. } else {
  619. // Trigger the fallback event
  620. if (typeof settings.onFallback === 'function') {
  621. settings.onFallback.call($this);
  622. }
  623. return false;
  624. }
  625. });
  626. },
  627. // Write some data to the console
  628. debug : function() {
  629. return this.each(function() {
  630. console.log($(this).data('uploadifive'));
  631. });
  632. },
  633. // Clear all the items from the queue
  634. clearQueue : function() {
  635. this.each(function() {
  636. var $this = $(this),
  637. $data = $this.data('uploadifive'),
  638. settings = $data.settings;
  639. for (var key in $data.inputs) {
  640. input = $data.inputs[key];
  641. limit = input.files.length;
  642. for (i = 0; i < limit; i++) {
  643. file = input.files[i];
  644. methods.cancel.call($this, file);
  645. }
  646. }
  647. // Trigger the onClearQueue event
  648. if (typeof settings.onClearQueue === 'function') {
  649. settings.onClearQueue.call($this, $('#' + $data.settings.queueID));
  650. }
  651. });
  652. },
  653. // Cancel a file upload in progress or remove a file from the queue
  654. cancel : function(file, fast) {
  655. this.each(function() {
  656. var $this = $(this),
  657. $data = $this.data('uploadifive'),
  658. settings = $data.settings;
  659. // If user passed a queue item ID instead of file...
  660. if (typeof file === 'string') {
  661. if (!isNaN(file)) {
  662. fileID = 'uploadifive-' + $(this).attr('id') + '-file-' + file;
  663. }
  664. file = $('#' + fileID).data('file');
  665. }
  666. file.skip = true;
  667. $data.filesCancelled++;
  668. if (file.uploading) {
  669. $data.uploads.current--;
  670. file.uploading = false;
  671. file.xhr.abort();
  672. delete file.xhr;
  673. methods.upload.call($this);
  674. }
  675. if ($.inArray('onCancel', settings.overrideEvents) < 0) {
  676. $data.removeQueueItem(file, fast);
  677. }
  678. // Trigger the cancel event
  679. if (typeof settings.onCancel === 'function') {
  680. settings.onCancel.call($this, file);
  681. }
  682. });
  683. },
  684. // Upload the files in the queue
  685. upload : function(file, keepVars) {
  686. this.each(function() {
  687. var $this = $(this),
  688. $data = $this.data('uploadifive'),
  689. settings = $data.settings;
  690. if (file) {
  691. $data.uploadFile.call($this, file);
  692. } else {
  693. // Check if the upload limit was reached
  694. if (($data.uploads.count + $data.uploads.current) < settings.uploadLimit || settings.uploadLimit == 0) {
  695. if (!keepVars) {
  696. $data.uploads.attempted = 0;
  697. $data.uploads.successsful = 0;
  698. $data.uploads.errors = 0;
  699. var filesToUpload = $data.filesToUpload();
  700. // Trigger the onUpload event
  701. if (typeof settings.onUpload === 'function') {
  702. settings.onUpload.call($this, filesToUpload);
  703. }
  704. }
  705. // Loop through the files
  706. $('#' + settings.queueID).find('.uploadifive-queue-item').not('.error, .complete').each(function() {
  707. _file = $(this).data('file');
  708. // Check if the simUpload limit was reached
  709. if (($data.uploads.current >= settings.simUploadLimit && settings.simUploadLimit !== 0) || ($data.uploads.current >= settings.uploadLimit && settings.uploadLimit !== 0) || ($data.uploads.count >= settings.uploadLimit && settings.uploadLimit !== 0)) {
  710. return false;
  711. }
  712. if (settings.checkScript) {
  713. // Let the loop know that we're already processing this file
  714. _file.checking = true;
  715. skipFile = $data.checkExists(_file);
  716. _file.checking = false;
  717. if (!skipFile) {
  718. $data.uploadFile(_file, true);
  719. }
  720. } else {
  721. $data.uploadFile(_file, true);
  722. }
  723. });
  724. if ($('#' + settings.queueID).find('.uploadifive-queue-item').not('.error, .complete').size() == 0) {
  725. $data.queueComplete();
  726. }
  727. } else {
  728. if ($data.uploads.current == 0) {
  729. if ($.inArray('onError', settings.overrideEvents) < 0) {
  730. if ($data.filesToUpload() > 0 && settings.uploadLimit != 0) {
  731. alert('The maximum upload limit has been reached.');
  732. }
  733. }
  734. // Trigger the onError event
  735. if (typeof settings.onError === 'function') {
  736. settings.onError.call($this, 'UPLOAD_LIMIT_EXCEEDED', $data.filesToUpload());
  737. }
  738. }
  739. }
  740. }
  741. });
  742. },
  743. // Destroy an instance of UploadiFive
  744. destroy : function() {
  745. this.each(function() {
  746. var $this = $(this),
  747. $data = $this.data('uploadifive'),
  748. settings = $data.settings;
  749. // Clear the queue
  750. methods.clearQueue.call($this);
  751. // Destroy the queue if it was created
  752. if (!settings.queueID) $('#' + settings.queueID).remove();
  753. // Remove extra inputs
  754. $this.siblings('input').remove();
  755. // Show the original file input
  756. $this.show()
  757. // Move the file input out of the button
  758. .insertBefore($data.button);
  759. // Delete the button
  760. $data.button.remove();
  761. // Trigger the destroy event
  762. if (typeof settings.onDestroy === 'function') {
  763. settings.onDestroy.call($this);
  764. }
  765. });
  766. }
  767. }
  768. $.fn.uploadifive = function(method) {
  769. if (methods[method]) {
  770. return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
  771. } else if (typeof method === 'object' || !method) {
  772. return methods.init.apply(this, arguments);
  773. } else {
  774. $.error('The method ' + method + ' does not exist in $.uploadify');
  775. }
  776. }
  777. })(jQuery);
  778. /* I gave the queueItems IDs and they each have a reference to the file held in the 'data' obj. */