/*
* bootstrap-FileUpload.js
* @version: v0.7.0
* @author: Luke LeBlanc
*
* This plugin is very lightweight, highly customizable, easy to use,
* extremely easy to integrate into any website with minimal dependancies
* and of course cross compatible. It includes support for mobile devices
* with fallback options to a regular customized form input field. It has
* the ability to upload multiple files at once and provide thumbnail
* previews. All it needs is Twitter Bootstrap V3 and the latest version
* of jQuery!
*
* Copyright (c) 2016 Luke LeBlanc
*
* GNU General Public License v3 (http://www.gnu.org/licenses/)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
;(function ($, document, window, undefined) {
'use strict';
/**
* Array of instances of the plugin
* @type Array
*/
var instance = {};
/**
* Array of available file types that gets built later on
* @type Array
*/
var availableFileTypes = {};
/**
* Array of public functions
* @type Array
*/
var methods = {
/**
* Initializes the plugin
* @param {Array} opts The array of user modified variables/options
* @return {Object} Runs a loop that starts up the plugin
*/
init: function (opts) {
instance[$(this).attr('id')] = {
options: $.extend({}, $.fn.bootstrapFileUpload.defaults, opts || {}),
wrapper: null,
form: null,
btnBar: null,
btnWrapper: null,
btnAdd: null,
btnStart: null,
btnCancel: null,
btnReset: null,
overallProgressBar: null,
overallStatus: null,
filePreviewTable: null,
formData: null,
arrayFiles: {},
arrayLength: 0
};
return this.each(function () {
startup($(this).attr('id'));
});
},
/**
* Adds file to queue and performs various checks to ensure a proper file was uploaded to spec
* @param {Object} el Current element in loop
* @param {Object} event Event object from change event
*/
addFile: function (el, event) {
var curfiles = event.target.files;
var length = curfiles.length;
if (instance[el].options.multiFile === true && instance[el].options.maxFiles && length > instance[el].options.maxFiles) {
window.alert('You\'re trying to upload ' + length + ' files and only ' + instance[el].options.maxFiles + ' files is currently supported! The system will only upload what is supported and you will have to upload again.');
length = instance[el].options.maxFiles;
}
instance[el].filePreviewTable.detach();
for (var i = 0; i < length; i++) {
var fileName, fileExt, fileType, file, size, sizeDisplay, row;
fileName = "file-" + i;
file = curfiles[i];
size = (file.size / 1024) / 1024;
sizeDisplay = calcFileSize(file.size);
fileExt = file.name.split('.').pop().toLowerCase();
if (isValidFileType(el, fileExt) === false) {
window.alert('The file "' + file.name + '" is not a supported filetype!');
continue;
}
fileType = getFileType(el, fileExt);
if (size.toFixed(2) > instance[el].options.maxSize) {
window.alert('The file size for "' + file.name + '" is too large! Maximum supported file size is ' + instance[el].options.maxSize + 'MB and the size of the file is ' + sizeDisplay);
continue;
}
if (instance[el].arrayFiles && checkFile(el, file) >= 0) {
window.alert('The file "' + file.name + '" is already in queue!');
continue;
}
instance[el].arrayFiles[fileName] = file;
instance[el].arrayLength = ++instance[el].arrayLength;
instance[el].btnStart.fadeIn("slow", "linear");
instance[el].btnCancel.fadeIn("slow", "linear");
var progressBar = '
Uploaded Successfully!
';
if (instance[el].options.showThumb === true) {
var thumb = genThumb(el, file, fileType, fileExt);
if (instance[el].options.multiUpload === false) {
row = '
';
}
}
instance[el].filePreviewTable.append(row);
}
instance[el].wrapper.append(instance[el].filePreviewTable);
instance[el].filePreviewTable.fadeIn("slow", "linear");
if (typeof instance[el].options.onFileAdded === 'function') {
instance[el].options.onFileAdded.call(this);
}
},
/**
* Begins upload process
* @param {Object} el Current element in loop
*/
uploadStart: function (el) {
$(".fileupload-add, .fileupload-start, .fileupload-cancel, .fileupload-remove").attr("disabled", "disabled");
if (instance[el].options.hiddenInput) {
$.each(instance[el].options.hiddenInput, function (key, value) {
instance[el].formData.append(key, value);
});
}
if (instance[el].options.multiUpload === false) {
$.each(instance[el].arrayFiles, function (key, value) {
instance[el].formData.append(instance[el].options.inputName, value);
$("#" + key + " .fileupload-progress .progress-bar-striped").fadeIn("slow", "linear");
procAjax(el, key);
if (typeof instance[el].options.onUploadProgress === 'function') {
instance[el].options.onUploadProgress.call(this);
}
});
} else {
instance[el].overallProgressBar.fadeIn("slow", "linear");
$.each(instance[el].arrayFiles, function (key, value) {
instance[el].formData.append(instance[el].options.inputName + "[]", value);
});
procAjax(el, "");
}
instance[el].btnAdd.fadeOut("slow", "linear");
instance[el].btnStart.fadeOut("slow", "linear");
instance[el].btnCancel.fadeOut("slow", "linear");
instance[el].btnReset.delay(600).fadeIn("slow", "linear");
if (typeof instance[el].options.onUploadComplete === 'function') {
instance[el].options.onUploadComplete.call(this);
}
},
/**
* Removes file from upload list
* @param {Object} el Current element in loop
* @param {String} id Id of container
*/
removeFile: function (el, id) {
if (instance[el].arrayLength <= 1) {
methods.resetUpload(el);
} else {
$("#" + id).fadeOut("slow", "linear");
$("#" + id + " .alert").fadeOut("slow", "linear");
$("#" + id).remove();
delete instance[el].arrayFiles[id];
instance[el].arrayLength = --instance[el].arrayLength;
}
if (typeof instance[el].options.onFileRemoved === 'function') {
instance[el].options.onFileRemoved.call(this);
}
},
/**
* Resets upload list
* @param {Object} el Current element in loop
*/
resetUpload: function (el) {
instance[el].filePreviewTable.find("tbody").empty();
instance[el].form[0].reset();
instance[el].arrayFiles = {};
instance[el].arrayLength = 0;
instance[el].filePreviewTable.fadeOut("slow", "linear");
instance[el].btnStart.fadeOut("slow", "linear");
instance[el].btnCancel.fadeOut("slow", "linear");
$(".fileupload-previewrow .alert").fadeOut("slow", "linear");
$(".fileupload-add, .fileupload-start, .fileupload-cancel").removeAttr("disabled");
$(".fileupload-add").delay(800).fadeIn("slow", "linear");
instance[el].overallProgressBar.find(".progress-bar-success").attr("aria-valuenow", 0).css("width", "0%");
instance[el].overallProgressBar.fadeOut("slow", "linear");
instance[el].btnReset.fadeOut("slow", "linear");
if (typeof instance[el].options.onUploadReset === 'function') {
instance[el].options.onUploadReset.call(this);
}
}
};
/**
* Builds the form structure, file types and runs through prerequisite checks
* @param {Object} el Current element in loop
* @return {Void}
*/
function startup (el) {
instance[el].wrapper = $('#' + el);
availableFileTypes["archives"] = ["zip", "7z", "gz", "gzip", "rar", "tar"];
availableFileTypes["audio"] = ["mp3", "wav", "wma", "wpl", "aac", "flac", "m4a", "m4b", "m4p", "midi", "ogg"];
availableFileTypes["files"] = ["doc", "docx", "dotx", "docm", "ods", "odt", "ott", "ods", "pdf", "ppt", "pptm", "pptx", "pub", "rtf", "csv", "log", "txt", "xls", "xlsm", "xlsx"];
availableFileTypes["images"] = ["bmp", "tif", "tiff", "gif", "jpeg", "jpg", "png", "svg", "ico", "raw"];
availableFileTypes["video"] = ["avi", "flv", "swf", "m4v", "mkv", "mov", "mp4", "ogv", "wmv"];
buildFileTypes(el);
if (instance[el].options.debug !== true && instance[el].options.debug !== false) {
instance[el].options.debug = false;
}
if (typeof $().emulateTransitionEnd !== 'function') {
debug(el, "bootstrap");
return;
}
if (instance[el].options.showThumb === true && instance[el].options.useFontAwesome === true) {
if (!$("link[href*='fontawesome']").length && !$("link[href*='font-awesome']").length) {
debug(el, "fontAwesome");
return;
} else {
if (checkFontAwesomeVer(el) === false) {
debug(el, "fontAwesomeVersion");
return;
}
}
}
if (instance[el].options.url === null || !isUrlValid(instance[el].options.url)) {
debug(el, "url");
return;
}
if (instance[el].options.formMethod !== 'post' && instance[el].options.formMethod !== 'get') {
debug(el, "formMethod");
return;
}
if (instance[el].options.fallbackUrl !== null && !isUrlValid(instance[el].options.fallbackUrl)) {
debug(el, "fallbackUrl");
return;
}
if (testBrowser && instance[el].options.forceFallback === false) {
formStructure(el);
} else {
fallbackFormStructure(el);
}
if (typeof instance[el].options.onInit === 'function') {
instance[el].options.onInit.call(this);
}
}
/**
* Builds the file type array used to validate file types being uploaded
* @param {Object} el Current element in loop
* @return {Void}
*/
function buildFileTypes (el) {
$.each(instance[el].options.fileTypes, function (key, value) {
if ($.isNumeric(key)) {
instance[el].options.fileTypes[value] = availableFileTypes[value];
} else if (!$.isNumeric(key) && $.isEmptyObject(value)) {
instance[el].options.fileTypes[key] = availableFileTypes[key];
}
});
return;
}
/**
* Creates an invisible test span with the fa class and verifies what the version is based on the font family
* @param {Object} el Current element in loop
* @return {Boolean}
*/
function checkFontAwesomeVer (el) {
var faSpan, faFontFamily;
faSpan = $('').appendTo('body');
faFontFamily = faSpan.css('fontFamily');
faSpan.remove();
if (faFontFamily === 'FontAwesome' ) {
instance[el].options.fontAwesomeVer = 4;
} else if (faFontFamily.indexOf('Font Awesome 5') !== -1) {
instance[el].options.fontAwesomeVer = 5;
} else {
return false;
}
return true;
}
/**
* Assigns an error message based on the type passed and either sends error to console or to screen
* @param {Object} el Current element in loop
* @param {String} type Error type
*/
function debug (el, type) {
var alertMsg, alertWrapper = $('');
switch (type) {
case 'method':
alertMsg = "The passed method " + name + " is not a valid method. Please check the configuration.";
break;
case 'fontAwesome':
alertMsg = "The Font Awesome CSS is not available within the head of the website and is a required unless the option showThumb is set to false.";
break;
case 'fontAwesomeVersion':
alertMsg = "The Font Awesome version could not be detected. Please set manually with the fontAwesomeVer option.";
break;
case 'url':
alertMsg = "The URL provided in the configuration is not a valid URL.";
break;
case 'fallbackUrl':
alertMsg = "The Fallback URL provided in the configuration is not a valid URL.";
break;
case 'formMethod':
alertMsg = "The Form Method provided in the configuration is not a valid, please choose either get or post in the configuration.";
break;
case 'bootstrap':
alertMsg = "The Twitter Bootstrap API is not available on the current page. Please check to make sure all the dependencies are in place.";
break;
default:
alertMsg = "An unknown error occured.";
break;
}
if (instance[el].options.debug === false && (window.console && window.console.error)) {
window.console.error(alertMsg);
} else if (instance[el].options.debug === true) {
alertWrapper.append(alertMsg);
instance[el].wrapper.append(alertWrapper);
}
}
/**
* Basic check to ensure url is valid
* @param {String} url Url being validated
* @return {Boolean}
*/
function isUrlValid (url) {
return /((http(s)?|ftp(s)?):\/\/)?[\w-]+(\.[\w-]+)+([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])?/.test(url);
}
/**
* Tests browser to ensure it supports use of xhr upload/onprogress
* @return {Boolean}
*/
function testBrowser () {
var xhr = new XMLHttpRequest();
return !! (window.FormData && xhr && ('upload' in xhr) && ('onprogress' in xhr.upload));
}
/**
* Builds plugin form structure using bootstrap
* @param {Object} el Current element in loop
*/
function formStructure (el) {
instance[el].formData = new FormData();
instance[el].form = $('');
instance[el].btnBar = $('');
instance[el].btnWrapper = $('');
instance[el].btnAdd = $('