/* UploadiFive 1.2.3 Copyright (c) 2012 Reactive Apps, Ronnie Garcia Released under the UploadiFive Standard License */ ;(function($) { var methods = { init : function(options) { return this.each(function() { // Create a reference to the jQuery DOM object var $this = $(this); $this.data('uploadifive', { inputs : {}, // The object that contains all the file inputs inputCount : 0, // The total number of file inputs created fileID : 0, queue : { count : 0, // Total number of files in the queue selected : 0, // Number of files selected in the last select operation replaced : 0, // Number of files replaced in the last select operation errors : 0, // Number of files that returned an error in the last select operation queued : 0, // Number of files added to the queue in the last select operation cancelled : 0 // Total number of files that have been cancelled or removed from the queue }, uploads : { current : 0, // Number of files currently being uploaded attempts : 0, // Number of file uploads attempted in the last upload operation successful : 0, // Number of files successfully uploaded in the last upload operation errors : 0, // Number of files returning errors in the last upload operation count : 0 // Total number of files uploaded successfully } }); var $data = $this.data('uploadifive'); // Set the default options var settings = $data.settings = $.extend({ 'auto' : true, // Automatically upload a file when it's added to the queue 'buttonClass' : false, // A class to add to the UploadiFive button 'buttonText' : 'Select Files', // The text that appears on the UploadiFive button 'checkScript' : false, // Path to the script that checks for existing file names 'dnd' : true, // Allow drag and drop into the queue 'dropTarget' : false, // Selector for the drop target 'fileObjName' : 'Filedata', // The name of the file object to use in your server-side script 'fileSizeLimit' : 0, // Maximum allowed size of files to upload 'fileType' : false, // Type of files allowed (image, etc), separate with a pipe character | 'formData' : {}, // Additional data to send to the upload script 'height' : 30, // The height of the button 'itemTemplate' : false, // The HTML markup for the item in the queue 'method' : 'post', // The method to use when submitting the upload 'multi' : true, // Set to true to allow multiple file selections 'overrideEvents' : [], // An array of events to override 'queueID' : false, // The ID of the file queue 'queueSizeLimit' : 0, // The maximum number of files that can be in the queue 'removeCompleted' : false, // Set to true to remove files that have completed uploading 'simUploadLimit' : 0, // The maximum number of files to upload at once 'truncateLength' : 0, // The length to truncate the file names to 'uploadLimit' : 0, // The maximum number of files you can upload 'uploadScript' : 'uploadifive.php', // The path to the upload script 'width' : 100 // The width of the button /* // Events 'onAddQueueItem' : function(file) {}, // Triggered for each file that is added to the queue 'onCancel' : function(file) {}, // Triggered when a file is cancelled or removed from the queue 'onCheck' : function(file, exists) {}, // Triggered when the server is checked for an existing file 'onClearQueue' : function(queue) {}, // Triggered during the clearQueue function 'onDestroy' : function() {} // Triggered during the destroy function 'onDrop' : function(files, numberOfFilesDropped) {}, // Triggered when files are dropped into the file queue 'onError' : function(file, fileType, data) {}, // Triggered when an error occurs 'onFallback' : function() {}, // Triggered if the HTML5 File API is not supported by the browser 'onInit' : function() {}, // Triggered when UploadiFive if initialized 'onQueueComplete' : function() {}, // Triggered once when an upload queue is done 'onProgress' : function(file, event) {}, // Triggered during each progress update of an upload 'onSelect' : function() {}, // Triggered once when files are selected from a dialog box 'onUpload' : function(file) {}, // Triggered when an upload queue is started 'onUploadComplete' : function(file, data) {}, // Triggered when a file is successfully uploaded 'onUploadFile' : function(file) {}, // Triggered for each file being uploaded */ }, options); // Create an array of file types var file_types; if (settings.fileType) { file_types = settings.fileType.split('|'); } // Calculate the file size limit if (isNaN(settings.fileSizeLimit)) { var fileSizeLimitBytes = parseInt(settings.fileSizeLimit) * 1.024; if (settings.fileSizeLimit.indexOf('KB') > -1) { settings.fileSizeLimit = fileSizeLimitBytes * 1000; } else if (settings.fileSizeLimit.indexOf('MB') > -1) { settings.fileSizeLimit = fileSizeLimitBytes * 1000000; } else if (settings.fileSizeLimit.indexOf('GB') > -1) { settings.fileSizeLimit = fileSizeLimitBytes * 1000000000; } } else { settings.fileSizeLimit = settings.fileSizeLimit * 1024; } // Create a template for a file input $data.inputTemplate = $('') .css({ 'font-size' : settings.height + 'px', 'opacity' : 0, 'position' : 'absolute', 'right' : '-3px', 'top' : '-3px', 'z-index' : 999 }); // Create a new input $data.createInput = function() { // Create a clone of the file input var input = $data.inputTemplate.clone(); // Create a unique name for the input item var inputName = input.name = 'input' + $data.inputCount++; // Set the multiple attribute if (settings.multi) { input.attr('multiple', true); } // Set the accept attribute on the input if (settings.fileType) { input.attr('accept', settings.fileType); } // Set the onchange event for the input input.bind('change', function() { $data.queue.selected = 0; $data.queue.replaced = 0; $data.queue.errors = 0; $data.queue.queued = 0; // Add a queue item to the queue for each file var limit = this.files.length; $data.queue.selected = limit; if (($data.queue.count + limit) > settings.queueSizeLimit && settings.queueSizeLimit !== 0) { if ($.inArray('onError', settings.overrideEvents) < 0) { alert('The maximum number of queue items has been reached (' + settings.queueSizeLimit + '). Please select fewer files.'); } // Trigger the error event if (typeof settings.onError === 'function') { settings.onError.call($this, 'QUEUE_LIMIT_EXCEEDED'); } } else { for (var n = 0; n < limit; n++) { file = this.files[n]; $data.addQueueItem(file); } $data.inputs[inputName] = this; $data.createInput(); } // Upload the file if auto-uploads are enabled if (settings.auto) { methods.upload.call($this); } // Trigger the select event if (typeof settings.onSelect === 'function') { settings.onSelect.call($this, $data.queue); } }); // Hide the existing current item and add the new one if ($data.currentInput) { $data.currentInput.hide(); } $data.button.append(input); $data.currentInput = input; }; // Remove an input $data.destroyInput = function(key) { $($data.inputs[key]).remove(); delete $data.inputs[key]; $data.inputCount--; }; // Drop a file into the queue $data.drop = function(e) { // Stop FireFox from opening the dropped file(s) e.preventDefault(); e.stopPropagation(); $data.queue.selected = 0; $data.queue.replaced = 0; $data.queue.errors = 0; $data.queue.queued = 0; var fileData = e.dataTransfer; var inputName = fileData.name = 'input' + $data.inputCount++; // Add a queue item to the queue for each file var limit = fileData.files.length; $data.queue.selected = limit; if (($data.queue.count + limit) > settings.queueSizeLimit && settings.queueSizeLimit !== 0) { // Check if the queueSizeLimit was reached if ($.inArray('onError', settings.overrideEvents) < 0) { alert('The maximum number of queue items has been reached (' + settings.queueSizeLimit + '). Please select fewer files.'); } // Trigger the onError event if (typeof settings.onError === 'function') { settings.onError.call($this, 'QUEUE_LIMIT_EXCEEDED'); } } else { // Add a queue item for each file for (var n = 0; n < limit; n++) { file = fileData.files[n]; $data.addQueueItem(file); if (file_types && file_types.indexOf(file.type) === -1) { $data.error('FORBIDDEN_FILE_TYPE', file); } } // Save the data to the inputs object $data.inputs[inputName] = fileData; } // Upload the file if auto-uploads are enabled if (settings.auto) { methods.upload.call($this); } // Trigger the onDrop event if (typeof settings.onDrop === 'function') { settings.onDrop.call($this, fileData.files, fileData.files.length); } }; // Check if a filename exists in the queue $data.fileExistsInQueue = function(file) { for (var key in $data.inputs) { input = $data.inputs[key]; limit = input.files.length; for (var n = 0; n < limit; n++) { existingFile = input.files[n]; // Check if the filename matches if (existingFile.name == file.name && !existingFile.complete) { return true; } } } return false; }; // Remove an existing file in the queue $data.removeExistingFile = function(file) { for (var key in $data.inputs) { input = $data.inputs[key]; limit = input.files.length; for (var n = 0; n < limit; n++) { existingFile = input.files[n]; // Check if the filename matches if (existingFile.name == file.name && !existingFile.complete) { $data.queue.replaced++; methods.cancel.call($this, existingFile, true); } } } }; // Create the file item template if (settings.itemTemplate === false) { $data.queueItem = $('
' + 'X' + '
' + '
' + '
' + '
' + '
'); } else { $data.queueItem = $(settings.itemTemplate); } // Add an item to the queue $data.addQueueItem = function(file) { if ($.inArray('onAddQueueItem', settings.overrideEvents) < 0) { // Check if the filename already exists in the queue $data.removeExistingFile(file); // Create a clone of the queue item template file.queueItem = $data.queueItem.clone(); // Add an ID to the queue item file.queueItem.attr('id', settings.id + '-file-' + $data.fileID++); // Bind the close event to the close button file.queueItem.find('.close').bind('click', function() { methods.cancel.call($this, file); return false; }); var fileName = file.name; if (fileName.length > settings.truncateLength && settings.truncateLength !== 0) { fileName = fileName.substring(0, settings.truncateLength) + '...'; } file.queueItem.find('.filename').html(fileName); // Add a reference to the file file.queueItem.data('file', file); $data.queueEl.append(file.queueItem); } // Trigger the addQueueItem event if (typeof settings.onAddQueueItem === 'function') { settings.onAddQueueItem.call($this, file); } // Check the filesize if (file.size > settings.fileSizeLimit && settings.fileSizeLimit !== 0) { $data.error('FILE_SIZE_LIMIT_EXCEEDED', file); } else { $data.queue.queued++; $data.queue.count++; } }; // Remove an item from the queue $data.removeQueueItem = function(file, instant, delay) { // Set the default delay if (!delay) delay = 0; var fadeTime = instant ? 0 : 500; if (file.queueItem) { if (file.queueItem.find('.fileinfo').html() != ' - Completed') { file.queueItem.find('.fileinfo').html(' - Cancelled'); } file.queueItem.find('.progress-bar').width(0); file.queueItem.delay(delay).fadeOut(fadeTime, function() { $(this).remove(); }); delete file.queueItem; $data.queue.count--; } }; // Count the number of files that need to be uploaded $data.filesToUpload = function() { var filesToUpload = 0; for (var key in $data.inputs) { input = $data.inputs[key]; limit = input.files.length; for (var n = 0; n < limit; n++) { file = input.files[n]; if (!file.skip && !file.complete) { filesToUpload++; } } } return filesToUpload; }; // Check if a file exists $data.checkExists = function(file) { if ($.inArray('onCheck', settings.overrideEvents) < 0) { // This request needs to be synchronous $.ajaxSetup({ 'async' : false }); // Send the filename to the check script var checkData = $.extend(settings.formData, {filename: file.name}); $.post(settings.checkScript, checkData, function(fileExists) { file.exists = parseInt(fileExists); }); if (file.exists) { if (!confirm('A file named ' + file.name + ' already exists in the upload folder.\nWould you like to replace it?')) { // If not replacing the file, cancel the upload methods.cancel.call($this, file); return true; } } } // Trigger the check event if (typeof settings.onCheck === 'function') { settings.onCheck.call($this, file, file.exists); } return false; }; // Upload a single file $data.uploadFile = function(file, uploadAll) { if (!file.skip && !file.complete && !file.uploading) { file.uploading = true; $data.uploads.current++; $data.uploads.attempted++; // Create a new AJAX request xhr = file.xhr = new XMLHttpRequest(); // Start the upload // Use the faster FormData if it exists if (typeof FormData === 'function' || typeof FormData === 'object') { // Create a new FormData object var formData = new FormData(); // Add the form data formData.append(settings.fileObjName, file); // Add the rest of the formData for (var i in settings.formData) { formData.append(i, settings.formData[i]); } // Open the AJAX call xhr.open(settings.method, settings.uploadScript, true); // On progress function xhr.upload.addEventListener('progress', function(e) { if (e.lengthComputable) { $data.progress(e, file); } }, false); // On complete function xhr.addEventListener('load', function(e) { if (this.readyState == 4) { file.uploading = false; if (this.status == 200) { if (file.xhr.responseText !== 'Invalid file type.') { $data.uploadComplete(e, file, uploadAll); } else { $data.error(file.xhr.responseText, file, uploadAll); } } else if (this.status == 404) { $data.error('404_FILE_NOT_FOUND', file, uploadAll); } else if (this.status == 403) { $data.error('403_FORBIDDEN', file, uploadAll); } else { $data.error('Unknown Error', file, uploadAll); } } }); // Send the form data (multipart/form-data) xhr.send(formData); } else { // Send as binary var reader = new FileReader(); reader.onload = function(e) { // Set some file builder variables var boundary = '-------------------------' + (new Date()).getTime(), dashes = '--', eol = '\r\n', binFile = ''; // Build an RFC2388 String binFile += dashes + boundary + eol; // Generate the headers binFile += 'Content-Disposition: form-data; name="' + settings.fileObjName + '"'; if (file.name) { binFile += '; filename="' + file.name + '"'; } binFile += eol; binFile += 'Content-Type: application/octet-stream' + eol + eol; binFile += e.target.result + eol; for (var key in settings.formData) { binFile += dashes + boundary + eol; binFile += 'Content-Disposition: form-data; name="' + key + '"' + eol + eol; binFile += settings.formData[key] + eol; } binFile += dashes + boundary + dashes + eol; // On progress function xhr.upload.addEventListener('progress', function(e) { $data.progress(e, file); }, false); // On complete function xhr.addEventListener('load', function(e) { file.uploading = false; var status = this.status; if (status == 404) { $data.error('404_FILE_NOT_FOUND', file, uploadAll); } else { if (file.xhr.responseText != 'Invalid file type.') { $data.uploadComplete(e, file, uploadAll); } else { $data.error(file.xhr.responseText, file, uploadAll); } } }, false); // Open the ajax request var url = settings.uploadScript; if (settings.method == 'get') { var params = $(settings.formData).param(); url += params; } xhr.open(settings.method, settings.uploadScript, true); xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + boundary); // Trigger the uploadFile event if (typeof settings.onUploadFile === 'function') { settings.onUploadFile.call($this, file); } // Send the file for upload xhr.sendAsBinary(binFile); }; reader.readAsBinaryString(file); } } }; // Update a file upload's progress $data.progress = function(e, file) { var percent; if ($.inArray('onProgress', settings.overrideEvents) < 0) { if (e.lengthComputable) { percent = Math.round((e.loaded / e.total) * 100); } file.queueItem.find('.fileinfo').html(' - ' + percent + '%'); file.queueItem.find('.progress-bar').css('width', percent + '%'); } // Trigger the progress event if (typeof settings.onProgress === 'function') { settings.onProgress.call($this, file, e); } }; // Trigger an error $data.error = function(errorType, file, uploadAll) { if ($.inArray('onError', settings.overrideEvents) < 0) { // Get the error message switch(errorType) { case '404_FILE_NOT_FOUND': errorMsg = '404 Error'; break; case '403_FORBIDDEN': errorMsg = '403 Forbidden'; break; case 'FORBIDDEN_FILE_TYPE': errorMsg = 'Forbidden File Type'; break; case 'FILE_SIZE_LIMIT_EXCEEDED': errorMsg = 'File Too Large'; break; default: errorMsg = 'Unknown Error'; break; } // Add the error class to the queue item file.queueItem.addClass('error') // Output the error in the queue item .find('.fileinfo').html(' - ' + errorMsg); // Hide the file.queueItem.find('.progress').remove(); } // Trigger the error event if (typeof settings.onError === 'function') { settings.onError.call($this, errorType, file); } file.skip = true; if (errorType == '404_FILE_NOT_FOUND') { $data.uploads.errors++; } else { $data.queue.errors++; } if (uploadAll) { methods.upload.call($this, null, true); } }; // Trigger when a single file upload is complete $data.uploadComplete = function(e, file, uploadAll) { if ($.inArray('onUploadComplete', settings.overrideEvents) < 0) { file.queueItem.find('.progress-bar').css('width', '100%'); file.queueItem.find('.fileinfo').html(' - Completed'); file.queueItem.find('.progress').slideUp(250); file.queueItem.addClass('complete'); } // Trigger the complete event if (typeof settings.onUploadComplete === 'function') { settings.onUploadComplete.call($this, file, file.xhr.responseText); } if (settings.removeCompleted) { setTimeout(function() { methods.cancel.call($this, file); }, 3000); } file.complete = true; $data.uploads.successful++; $data.uploads.count++; $data.uploads.current--; delete file.xhr; if (uploadAll) { methods.upload.call($this, null, true); } }; // Trigger when all the files are done uploading $data.queueComplete = function() { // Trigger the queueComplete event if (typeof settings.onQueueComplete === 'function') { settings.onQueueComplete.call($this, $data.uploads); } }; // ---------------------- // Initialize UploadiFive // ---------------------- // Check if HTML5 is available if (window.File && window.FileList && window.Blob && (window.FileReader || window.FormData)) { // Assign an ID to the object settings.id = 'uploadifive-' + $this.attr('id'); // Wrap the file input in a div with overflow set to hidden $data.button = $('
' + settings.buttonText + '
'); if (settings.buttonClass) $data.button.addClass(settings.buttonClass); // Style the button wrapper $data.button.css({ 'height' : settings.height, 'line-height' : settings.height + 'px', 'overflow' : 'hidden', 'position' : 'relative', 'text-align' : 'center', 'width' : settings.width }); // Insert the button above the file input $this.before($data.button) // Add the file input to the button .appendTo($data.button) // Modify the styles of the file input .hide(); // Create a new input $data.createInput.call($this); // Create the queue container if (!settings.queueID) { settings.queueID = settings.id + '-queue'; $data.queueEl = $('
'); $data.button.after($data.queueEl); } else { $data.queueEl = $('#' + settings.queueID); } // Add drag and drop functionality if (settings.dnd) { var $dropTarget = settings.dropTarget ? $(settings.dropTarget) : $data.queueEl.get(0); $dropTarget.addEventListener('dragleave', function(e) { // Stop FireFox from opening the dropped file(s) e.preventDefault(); e.stopPropagation(); }, false); $dropTarget.addEventListener('dragenter', function(e) { // Stop FireFox from opening the dropped file(s) e.preventDefault(); e.stopPropagation(); }, false); $dropTarget.addEventListener('dragover', function(e) { // Stop FireFox from opening the dropped file(s) e.preventDefault(); e.stopPropagation(); }, false); $dropTarget.addEventListener('drop', $data.drop, false); } // Send as binary workaround for Chrome if (!XMLHttpRequest.prototype.sendAsBinary) { XMLHttpRequest.prototype.sendAsBinary = function(datastr) { function byteValue(x) { return x.charCodeAt(0) & 0xff; } var ords = Array.prototype.map.call(datastr, byteValue); var ui8a = new Uint8Array(ords); this.send(ui8a.buffer); }; } // Trigger the oninit event if (typeof settings.onInit === 'function') { settings.onInit.call($this); } } else { // Trigger the fallback event if (typeof settings.onFallback === 'function') { settings.onFallback.call($this); } return false; } }); }, // Write some data to the console debug : function() { return this.each(function() { console.log($(this).data('uploadifive')); }); }, // Clear all the items from the queue clearQueue : function() { this.each(function() { var $this = $(this), $data = $this.data('uploadifive'), settings = $data.settings; for (var key in $data.inputs) { input = $data.inputs[key]; limit = input.files.length; for (i = 0; i < limit; i++) { file = input.files[i]; methods.cancel.call($this, file); } } // Trigger the onClearQueue event if (typeof settings.onClearQueue === 'function') { settings.onClearQueue.call($this, $('#' + $data.settings.queueID)); } }); }, // Cancel a file upload in progress or remove a file from the queue cancel : function(file, fast) { this.each(function() { var $this = $(this), $data = $this.data('uploadifive'), settings = $data.settings; // If user passed a queue item ID instead of file... if (typeof file === 'string') { if (!isNaN(file)) { fileID = 'uploadifive-' + $(this).attr('id') + '-file-' + file; } file = $('#' + fileID).data('file'); } file.skip = true; $data.filesCancelled++; if (file.uploading) { $data.uploads.current--; file.uploading = false; file.xhr.abort(); delete file.xhr; methods.upload.call($this); } if ($.inArray('onCancel', settings.overrideEvents) < 0) { $data.removeQueueItem(file, fast); } // Trigger the cancel event if (typeof settings.onCancel === 'function') { settings.onCancel.call($this, file); } }); }, // Upload the files in the queue upload : function(file, keepVars) { this.each(function() { var $this = $(this), $data = $this.data('uploadifive'), settings = $data.settings; if (file) { $data.uploadFile.call($this, file); } else { // Check if the upload limit was reached if (($data.uploads.count + $data.uploads.current) < settings.uploadLimit || settings.uploadLimit === 0) { if (!keepVars) { $data.uploads.attempted = 0; $data.uploads.successsful = 0; $data.uploads.errors = 0; var filesToUpload = $data.filesToUpload(); // Trigger the onUpload event if (typeof settings.onUpload === 'function') { settings.onUpload.call($this, filesToUpload); } } // Loop through the files $('#' + settings.queueID).find('.uploadifive-queue-item').not('.error, .complete').each(function() { _file = $(this).data('file'); // Check if the simUpload limit was reached 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)) { return false; } if (settings.checkScript) { // Let the loop know that we're already processing this file _file.checking = true; skipFile = $data.checkExists(_file); _file.checking = false; if (!skipFile) { $data.uploadFile(_file, true); } } else { $data.uploadFile(_file, true); } }); if ($('#' + settings.queueID).find('.uploadifive-queue-item').not('.error, .complete').size() === 0) { $data.queueComplete(); } } else { if ($data.uploads.current === 0) { if ($.inArray('onError', settings.overrideEvents) < 0) { if ($data.filesToUpload() > 0 && settings.uploadLimit !== 0) { alert('The maximum upload limit has been reached.'); } } // Trigger the onError event if (typeof settings.onError === 'function') { settings.onError.call($this, 'UPLOAD_LIMIT_EXCEEDED', $data.filesToUpload()); } } } } }); }, // Destroy an instance of UploadiFive destroy : function() { this.each(function() { var $this = $(this), $data = $this.data('uploadifive'), settings = $data.settings; // Clear the queue methods.clearQueue.call($this); // Destroy the queue if it was created if (!settings.queueID) $('#' + settings.queueID).remove(); // Remove extra inputs $this.siblings('input').remove(); // Show the original file input $this.show() // Move the file input out of the button .insertBefore($data.button); // Delete the button $data.button.remove(); // Trigger the destroy event if (typeof settings.onDestroy === 'function') { settings.onDestroy.call($this); } }); } }; $.fn.uploadifive = function(method) { if (methods[method]) { return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); } else if (typeof method === 'object' || !method) { return methods.init.apply(this, arguments); } else { $.error('The method ' + method + ' does not exist in $.uploadify'); } }; })(jQuery);