1223 lines
48 KiB
JavaScript
1223 lines
48 KiB
JavaScript
|
|
/* AJAX File upload simple */
|
|||
|
|
|
|||
|
|
// 'use strict';
|
|||
|
|
|
|||
|
|
/* jshint esversion: 6 */
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* CUSTOM SUB CALL FUNCTIONS as passsd on in init call
|
|||
|
|
* run for each uploaded file
|
|||
|
|
* fileChange(target_file, file_pos, target_router)
|
|||
|
|
* fileChangeAll(target_file, target_router)
|
|||
|
|
* fileRemove(target_file, file_pos)
|
|||
|
|
* fileClear(target_file)
|
|||
|
|
* fileBeforeUploadAll(target_file, target_router)
|
|||
|
|
* fileBeforeUpload(target_file, file_pos, target_router)
|
|||
|
|
* fileUploaded(target_file, file_pos, target_router, control_data)
|
|||
|
|
* fileUploadedAll(target_file, target_router)
|
|||
|
|
* fileUploadError(target_file, file_pos, target_router, control_data)
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
// translation strings
|
|||
|
|
var AFUS_strings = {};
|
|||
|
|
// file lists for each target file
|
|||
|
|
var AFUS_file_list = {};
|
|||
|
|
// file functions
|
|||
|
|
var AFUS_functions = {};
|
|||
|
|
// config settings
|
|||
|
|
var AFUS_config = {};
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [from edit.js]
|
|||
|
|
* simple sprintf formater for replace
|
|||
|
|
* usage: "{0} is cool, {1} is not".format("Alpha", "Beta");
|
|||
|
|
* First, checks if it isn't implemented yet.
|
|||
|
|
* @param {String} String.prototype.format string with elements to be replaced
|
|||
|
|
* @return {String} Formated string
|
|||
|
|
*/
|
|||
|
|
if (!String.prototype.format) {
|
|||
|
|
String.prototype.format = function()
|
|||
|
|
{
|
|||
|
|
var args = arguments;
|
|||
|
|
return this.replace(/{(\d+)}/g, function(match, number)
|
|||
|
|
{
|
|||
|
|
return typeof args[number] != 'undefined' ?
|
|||
|
|
args[number] :
|
|||
|
|
match
|
|||
|
|
;
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [from edit.js]
|
|||
|
|
* checks if a key exists in a given object
|
|||
|
|
* @param {String} key key name
|
|||
|
|
* @param {Object} object object to search key in
|
|||
|
|
* @return {Boolean} true/false if key exists in object
|
|||
|
|
*/
|
|||
|
|
function keyInObject(key, object)
|
|||
|
|
{
|
|||
|
|
return (Object.prototype.hasOwnProperty.call(object, key)) ? true : false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [from edit.js]
|
|||
|
|
* converts a int number into bytes with prefix in two decimals precision
|
|||
|
|
* currently precision is fixed, if dynamic needs check for max/min precision
|
|||
|
|
* @param {Number} bytes bytes in int
|
|||
|
|
* @return {String} string in GB/MB/KB
|
|||
|
|
*/
|
|||
|
|
function formatBytes(bytes)
|
|||
|
|
{
|
|||
|
|
var i = -1;
|
|||
|
|
do {
|
|||
|
|
bytes = bytes / 1024;
|
|||
|
|
i++;
|
|||
|
|
} while (bytes > 99);
|
|||
|
|
return parseFloat(Math.round(bytes * Math.pow(10, 2)) / Math.pow(10, 2)) + ['kB', 'MB', 'GB', 'TB', 'PB', 'EB'][i];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [from edit.js]
|
|||
|
|
* prints out error messages based on data available from the browser
|
|||
|
|
* @param {Object} err error from try/catch block
|
|||
|
|
*/
|
|||
|
|
function errorCatch(err)
|
|||
|
|
{
|
|||
|
|
// for FF & Chrome
|
|||
|
|
if (err.stack) {
|
|||
|
|
// only FF
|
|||
|
|
if (err.lineNumber) {
|
|||
|
|
console.log('ERROR[%s:%s] %s', err.name, err.lineNumber, err.message);
|
|||
|
|
} else if (err.line) {
|
|||
|
|
// only Safari
|
|||
|
|
console.log('ERROR[%s:%s] %s', err.name, err.line, err.message);
|
|||
|
|
} else {
|
|||
|
|
console.log('ERROR[%s] %s', err.name, err.message);
|
|||
|
|
}
|
|||
|
|
// stack trace
|
|||
|
|
console.log('ERROR[stack] %s', err.stack);
|
|||
|
|
} else if (err.number) {
|
|||
|
|
// IE
|
|||
|
|
console.log('ERROR[%s:%s] %s', err.name, err.number, err.message);
|
|||
|
|
console.log('ERROR[description] %s', err.description);
|
|||
|
|
} else {
|
|||
|
|
// the rest
|
|||
|
|
console.log('ERROR[%s] %s', err.name, err.message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [9-A] add one to the running queue
|
|||
|
|
* @param {String} target_file prefix for elements
|
|||
|
|
* @return {Number} current running queues
|
|||
|
|
*/
|
|||
|
|
function afusRunningStart(target_file)
|
|||
|
|
{
|
|||
|
|
AFUS_config[target_file].running ++;
|
|||
|
|
return AFUS_config[target_file].running;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [9-B] remove one from the running queue
|
|||
|
|
* @param {String} target_file prefix for elements
|
|||
|
|
* @return {Number} current running queues
|
|||
|
|
*/
|
|||
|
|
function afusRunningStop(target_file)
|
|||
|
|
{
|
|||
|
|
AFUS_config[target_file].running --;
|
|||
|
|
if (AFUS_config[target_file].running < 0) {
|
|||
|
|
AFUS_config[target_file].running = 0;
|
|||
|
|
}
|
|||
|
|
return AFUS_config[target_file].running;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [9-C] get current running queue count
|
|||
|
|
* @param {String} target_file prefix for elements
|
|||
|
|
* @return {Number} current running queues
|
|||
|
|
*/
|
|||
|
|
function afusRunningGet(target_file)
|
|||
|
|
{
|
|||
|
|
return AFUS_config[target_file].running;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [8-C] write out the cancel for upload
|
|||
|
|
* @param {String} target_file prefix for elements
|
|||
|
|
* @param {Number} file_pos position for progress info to clear
|
|||
|
|
*/
|
|||
|
|
function afusCancelUploadOutput(target_file, file_pos)
|
|||
|
|
{
|
|||
|
|
document.getElementById(target_file + '-status-progress-' + file_pos).innerHTML = AFUS_strings[target_file].upload_cancled || 'Upload cancled';
|
|||
|
|
document.getElementById(target_file + '-status-progress-' + file_pos).style.display = '';
|
|||
|
|
document.getElementById(target_file + '-status-progress-bar-span-' + file_pos).style.display = 'none';
|
|||
|
|
document.getElementById(target_file + '-status-delete-' + file_pos).style.display = 'none';
|
|||
|
|
document.getElementById(target_file + '-status-abort-' + file_pos).style.display = 'none';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [8-B] upload error
|
|||
|
|
* prints upload error messages into the status field
|
|||
|
|
* @param {String} target_file prefix for elements
|
|||
|
|
* @param {Number} file_pos position for progress info to clear
|
|||
|
|
* @param {Array} error_message error message, if not set standard error is shown
|
|||
|
|
* @param {Boolean} is_error if set true, then add error message class
|
|||
|
|
*/
|
|||
|
|
function afusUploadError(target_file, file_pos, error_message, is_error)
|
|||
|
|
{
|
|||
|
|
// set if empty
|
|||
|
|
if (error_message.length == 0) {
|
|||
|
|
error_message.push('[JS Upload Lib] Upload Error');
|
|||
|
|
}
|
|||
|
|
// write error message, and show
|
|||
|
|
document.getElementById(target_file + '-status-progress-' + file_pos)
|
|||
|
|
.innerHTML = error_message.join(', ');
|
|||
|
|
// show as block
|
|||
|
|
document.getElementById(target_file + '-status-progress-' + file_pos).style.display = '';
|
|||
|
|
document.getElementById(target_file + '-status-progress-bar-span-' + file_pos).style.display = 'none';
|
|||
|
|
// add error style to upload status
|
|||
|
|
if (is_error === true) {
|
|||
|
|
document.getElementById(target_file + '-status-progress-' + file_pos).classList.add('SubError');
|
|||
|
|
} else {
|
|||
|
|
document.getElementById(target_file + '-status-progress-' + file_pos).classList.remove('SubError');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [8-A] final step after upload
|
|||
|
|
* The last action to be called, fills all the hidden values
|
|||
|
|
* - uid
|
|||
|
|
* - file_name
|
|||
|
|
* - file_size
|
|||
|
|
* Inserts thumbnail if url exists
|
|||
|
|
* Adds uploaded file name and file size if flags show_name/show_size are set
|
|||
|
|
* The function checks for "fileUploaded" and if it exists
|
|||
|
|
* it will be called with the "other_data" object
|
|||
|
|
* @param {String} target_file Prefix for elements
|
|||
|
|
* @param {Number} file_pos Position in upload queue
|
|||
|
|
* @param {String} target_router Target router name
|
|||
|
|
* @param {Object} file_info Object with all the additional data returned from AJAX
|
|||
|
|
* @param {Object} data Full Object set (including file_info)
|
|||
|
|
*/
|
|||
|
|
function afusPostUpload(target_file, file_pos, target_router, file_info, data)
|
|||
|
|
{
|
|||
|
|
console.log('[AJAX Uploader: %s] Post upload: File: %s, Name: %s, Size: %s', target_file, file_info.file_uid, file_info.file_name, file_info.file_size);
|
|||
|
|
// set internal uid
|
|||
|
|
document.getElementById(target_file + '-uid-' + file_pos).value = file_info.file_uid;
|
|||
|
|
// append uid, file name, size into uploaded too
|
|||
|
|
if (typeof AFUS_functions[target_file].fileUploaded === 'function') {
|
|||
|
|
AFUS_functions[target_file].fileUploaded(target_file, file_pos, target_router, data);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [8-D] afusFinalPostUpload
|
|||
|
|
* is called after ALL uploads are finished
|
|||
|
|
* @param {String} target_file Prefix for elements
|
|||
|
|
*/
|
|||
|
|
function afusFinalPostUpload(target_file)
|
|||
|
|
{
|
|||
|
|
console.log('[AJAX Uploader: %s] Final Post upload', target_file);
|
|||
|
|
// reset internal variables
|
|||
|
|
afusResetInternalVariables(target_file);
|
|||
|
|
// hide abort
|
|||
|
|
document.getElementById(target_file + '-abort-all-div').style.display = 'none';
|
|||
|
|
// show clear button again
|
|||
|
|
document.getElementById(target_file + '-clear-div').style.display = '';
|
|||
|
|
// show the upload button again after upload
|
|||
|
|
document.getElementById(target_file + '-file-div').style.display = '';
|
|||
|
|
// reset file list?
|
|||
|
|
document.getElementById(target_file + '-file').value = '';
|
|||
|
|
// safari issues?
|
|||
|
|
document.getElementById(target_file + '-file').type = '';
|
|||
|
|
document.getElementById(target_file + '-file').type = 'file';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [8-E] reset all internal variables variables
|
|||
|
|
* @param {String} target_file Prefix for elements
|
|||
|
|
*/
|
|||
|
|
function afusResetInternalVariables(target_file)
|
|||
|
|
{
|
|||
|
|
AFUS_file_list[target_file] = [];
|
|||
|
|
AFUS_config[target_file].running = 0;
|
|||
|
|
AFUS_config[target_file].current_xhr = null;
|
|||
|
|
AFUS_config[target_file].current = -1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [1-A] Validate and init all config options
|
|||
|
|
* @param {Object} config Original config object to check
|
|||
|
|
* @return {Object} Check and validated config object
|
|||
|
|
*/
|
|||
|
|
function afusUploaderConfigCheck(config)
|
|||
|
|
{
|
|||
|
|
// first check if parameter is actualy object
|
|||
|
|
if (!(typeof config === 'object' && config !== null)) {
|
|||
|
|
config = {};
|
|||
|
|
}
|
|||
|
|
// if target file is not set, major abort
|
|||
|
|
let empty = false;
|
|||
|
|
for (let ent of ['target_file', 'target_form']) {
|
|||
|
|
if (!keyInObject(ent, config)) {
|
|||
|
|
config[ent] = '';
|
|||
|
|
}
|
|||
|
|
if (!(typeof config[ent] === 'string' || config[ent] instanceof String)) {
|
|||
|
|
// abort because of false type
|
|||
|
|
config[ent] = '';
|
|||
|
|
}
|
|||
|
|
// empty flag = abort
|
|||
|
|
if (!config[ent]) {
|
|||
|
|
empty = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// if one of those two is empty, abort
|
|||
|
|
if (empty === true) {
|
|||
|
|
console.log('[AJAX Uploader: %s] EMPTY target_file/target_form', config.target_file || '[UNSET TARGET FILE]');
|
|||
|
|
throw new Error('target_file and target_form must be set');
|
|||
|
|
}
|
|||
|
|
let target_file = config.target_file;
|
|||
|
|
// maximum files allowed to upload at once
|
|||
|
|
if (!keyInObject('max_files', config)) {
|
|||
|
|
config.max_files = 1;
|
|||
|
|
} else {
|
|||
|
|
config.max_files = parseInt(config.max_files);
|
|||
|
|
// must be between 0 and some reasonable number on upper bound
|
|||
|
|
if (config.max_files < 0 || config.max_files > 100) {
|
|||
|
|
// should we set to eg 100 as max?
|
|||
|
|
config.max_files = 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// maximum file size allowed (in bytes)
|
|||
|
|
if (!keyInObject('max_file_size', config)) {
|
|||
|
|
config.max_file_size = 0;
|
|||
|
|
} else {
|
|||
|
|
// TODO: if has M/G/etc multiply by 1024 to bytes
|
|||
|
|
// else use as is
|
|||
|
|
config.max_file_size = parseInt(config.max_file_size);
|
|||
|
|
}
|
|||
|
|
config.file_accept = [];
|
|||
|
|
// allowed file extensions (eg jpg, gif)
|
|||
|
|
if (!keyInObject('allowed_extensions', config)) {
|
|||
|
|
config.allowed_extensions = [];
|
|||
|
|
} else {
|
|||
|
|
// must be array and always lower case
|
|||
|
|
// copy to new
|
|||
|
|
let _temp_list = config.allowed_extensions;
|
|||
|
|
// reset
|
|||
|
|
config.allowed_extensions = [];
|
|||
|
|
// must be array and always convert to lower case
|
|||
|
|
for (let ent of _temp_list) {
|
|||
|
|
// if empty remove remove
|
|||
|
|
if (ent.length > 0) {
|
|||
|
|
config.allowed_extensions.push(ent.toLowerCase());
|
|||
|
|
config.file_accept.push('.' + ent.toLowerCase());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// allowed file types in mime format, image/jpeg, etc
|
|||
|
|
if (!keyInObject('allowed_file_types', config)) {
|
|||
|
|
config.allowed_file_types = [];
|
|||
|
|
} else {
|
|||
|
|
// copy to new
|
|||
|
|
let _temp_list = config.allowed_file_types;
|
|||
|
|
// reset
|
|||
|
|
config.allowed_file_types = [];
|
|||
|
|
// must be array and always convert to lower case
|
|||
|
|
for (let ent of _temp_list) {
|
|||
|
|
// if empty remove remove
|
|||
|
|
if (ent.length > 0) {
|
|||
|
|
config.allowed_file_types.push(ent.toLowerCase());
|
|||
|
|
config.file_accept.push(ent.toLowerCase());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// target router for ajax submit
|
|||
|
|
if (!keyInObject('target_router', config)) {
|
|||
|
|
config.target_router = '';
|
|||
|
|
} else {
|
|||
|
|
// must be string
|
|||
|
|
}
|
|||
|
|
// ajax form action target name
|
|||
|
|
if (!keyInObject('target_action', config)) {
|
|||
|
|
config.target_action = '';
|
|||
|
|
} else {
|
|||
|
|
// must be string
|
|||
|
|
}
|
|||
|
|
// any additional parameters to be sent to the server
|
|||
|
|
if (!keyInObject('form_parameters', config)) {
|
|||
|
|
config.form_parameters = {};
|
|||
|
|
} else if (!(
|
|||
|
|
typeof config.form_parameters === 'object' &&
|
|||
|
|
config.form_parameters !== null
|
|||
|
|
)) {
|
|||
|
|
// must be object
|
|||
|
|
config.form_parameters = {};
|
|||
|
|
}
|
|||
|
|
// upload without confirmation step
|
|||
|
|
if (!keyInObject('auto_submit', config)) {
|
|||
|
|
config.auto_submit = false;
|
|||
|
|
} else if (!(
|
|||
|
|
config.auto_submit === false ||
|
|||
|
|
config.auto_submit === true
|
|||
|
|
)) {
|
|||
|
|
// must be boolean
|
|||
|
|
config.auto_submit = false;
|
|||
|
|
}
|
|||
|
|
// set path name
|
|||
|
|
config.path = window.location.pathname;
|
|||
|
|
// do we end in .php, we need to remove the name then, we just want the path
|
|||
|
|
if (config.path.indexOf('.php') != -1) {
|
|||
|
|
// remove trailing filename (all past last / )
|
|||
|
|
config.path = config.path.replace(/\w+\.php/, '');
|
|||
|
|
}
|
|||
|
|
if (config.path.substr(-1) != '/') {
|
|||
|
|
config.path += '/';
|
|||
|
|
}
|
|||
|
|
// write general config things into config
|
|||
|
|
AFUS_config[target_file] = {};
|
|||
|
|
for (var ent of [
|
|||
|
|
'target_file', 'target_form', 'path',
|
|||
|
|
'max_files', 'max_file_size', 'allowed_extensions', 'allowed_file_types',
|
|||
|
|
'target_router', 'target_action', 'form_parameters', 'auto_submit'
|
|||
|
|
]) {
|
|||
|
|
AFUS_config[target_file][ent] = config[ent];
|
|||
|
|
}
|
|||
|
|
// all files uploaded ok AND accepted by the server flag
|
|||
|
|
AFUS_config[target_file].all_success = true;
|
|||
|
|
// overall abort flag
|
|||
|
|
AFUS_config[target_file].abort = false;
|
|||
|
|
// currently running number of uploads
|
|||
|
|
AFUS_config[target_file].running = 0;
|
|||
|
|
// current running file pos
|
|||
|
|
AFUS_config[target_file].current = -1;
|
|||
|
|
// current running xhr object
|
|||
|
|
AFUS_config[target_file].current_xhr = null;
|
|||
|
|
// functions check and set
|
|||
|
|
AFUS_functions[target_file] = {};
|
|||
|
|
for (var fkt of [
|
|||
|
|
'fileChange', 'fileChangeAll', 'fileRemove', 'fileClear', 'fileBeforeUploadAll',
|
|||
|
|
'fileBeforeUpload', 'fileUploaded', 'fileUploadedAll', 'fileUploadError'
|
|||
|
|
]) {
|
|||
|
|
if (!keyInObject(fkt, config)) {
|
|||
|
|
config[fkt] = '';
|
|||
|
|
}
|
|||
|
|
AFUS_functions[target_file][fkt] = config[fkt];
|
|||
|
|
}
|
|||
|
|
// init strings for this groups
|
|||
|
|
AFUS_strings[target_file] = {};
|
|||
|
|
// if set translation strings, set them to the AFUS string
|
|||
|
|
if (keyInObject('translation', config)) {
|
|||
|
|
// upload_start, upload_finished, too_many_files only
|
|||
|
|
for (var k of [
|
|||
|
|
'invalid_type', 'invalid_size', 'cancel', 'remove',
|
|||
|
|
'upload_start', 'upload_finished', 'upload_cancled',
|
|||
|
|
'too_many_files'
|
|||
|
|
]) {
|
|||
|
|
if (keyInObject(k, config.translation)) {
|
|||
|
|
AFUS_strings[target_file][k] = config.translation[k];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return config;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [2] Function that will allow us to know if Ajax uploads are supported
|
|||
|
|
* @return {Boolean} true on "ajax file uploaded supported", false on not possible
|
|||
|
|
*/
|
|||
|
|
function afusSupportAjaxUploadWithProgress()
|
|||
|
|
{
|
|||
|
|
return supportFileAPI() && supportAjaxUploadProgressEvents() && supportFormData();
|
|||
|
|
// Is the File API supported?
|
|||
|
|
function supportFileAPI()
|
|||
|
|
{
|
|||
|
|
var fi = document.createElement('INPUT');
|
|||
|
|
fi.type = 'file';
|
|||
|
|
return 'files' in fi;
|
|||
|
|
}
|
|||
|
|
// Are progress events supported?
|
|||
|
|
function supportAjaxUploadProgressEvents()
|
|||
|
|
{
|
|||
|
|
var xhr = new XMLHttpRequest();
|
|||
|
|
return !! (xhr && ('upload' in xhr) && ('onprogress' in xhr.upload));
|
|||
|
|
}
|
|||
|
|
// Is FormData supported?
|
|||
|
|
function supportFormData()
|
|||
|
|
{
|
|||
|
|
return !! window.FormData;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [1] this has to be called to start the uploader
|
|||
|
|
* checks if ajax upload is supported and if yes
|
|||
|
|
* checks if we need to set the pass through click event
|
|||
|
|
* then calls the final init that loads the form.submit catcher
|
|||
|
|
*
|
|||
|
|
* for config object, if missing will be inited with default values:
|
|||
|
|
* {String} target_file prefix for the element for the file upload
|
|||
|
|
* Must be set
|
|||
|
|
* {String} target_form the master form in which this element sits
|
|||
|
|
* Must be set
|
|||
|
|
* {Number} [max_files=1] maximum uploadable files, if 1, multiple will
|
|||
|
|
* be removed from input file field, if 0, no limit
|
|||
|
|
* {Number} [max_file_size=0] In bytes, maximum file size. If not set unlimited.
|
|||
|
|
* 0 is also unlimited
|
|||
|
|
* {Array} [allowed_extensions=[]] Allowed file extensions. Without leading '.'
|
|||
|
|
* Will be added to the input file accept list
|
|||
|
|
* {Array} [allowed_file_types=[]] Allowed file types in mime format
|
|||
|
|
* Will be added to the input file accept list
|
|||
|
|
* {String} [target_router=''] value of the action _POST variable, if not set or
|
|||
|
|
* if empty use "fileUpload"
|
|||
|
|
* {String} [target_action=''] override the target_form action field
|
|||
|
|
* {Object} [form_parameters={}] Key/Value list for additional parameters to add
|
|||
|
|
* to the form submit.
|
|||
|
|
* Added BEFORE fileBeforeUpload parameters
|
|||
|
|
* {Object} [translation={}] Translated strings pushed into the AFUS_strings object
|
|||
|
|
* {Boolean} [auto_submit=false] if we override the submit button and directly upload
|
|||
|
|
* {Function} [fileChange=''] Function run on change of -file element entry
|
|||
|
|
* Parameters are target_file, file_pos, target_router
|
|||
|
|
* {Function} [fileChangeAll=''] Function run on change of -file element entry
|
|||
|
|
* after all file changes for each entry are run
|
|||
|
|
* Parameters are target_file, target_router
|
|||
|
|
* {Function} [fileRemove=''] Called when an upload file is removed from the queue
|
|||
|
|
* before upload
|
|||
|
|
* Parameters are target_file, file_pos
|
|||
|
|
* {Functuon} [fileClear=''] called when clear button is pressed
|
|||
|
|
* Parameters are target_file
|
|||
|
|
* {Function} [fileBeforeUploadAll=''] Function called before uploads start
|
|||
|
|
* Parameters are target_file, target_router
|
|||
|
|
* {Function} [fileBeforeUpload=''] Function called before upload starts
|
|||
|
|
* Parameters are target_file, file_pos, target_router
|
|||
|
|
* {Function} [fileUploaded=''] Function called after upload has successfully finished
|
|||
|
|
* Parameters are target_file, file_pos, target_router, data
|
|||
|
|
* (object returned from upload function call)
|
|||
|
|
* {Function} [fileUploadedAll=''] After all uploads have been done, this one will be called
|
|||
|
|
* Parameters are target_file, target_router
|
|||
|
|
* {Function} [fileUploadError=''] Function called after upload has failed
|
|||
|
|
* Parameters are target_file, file_pos, target_router, data
|
|||
|
|
* (object returned from upload function call)
|
|||
|
|
*
|
|||
|
|
* @param {Object} config Configuration parameters. See explenatuion above
|
|||
|
|
*/
|
|||
|
|
function initAjaxUploader(config) // eslint-disable-line
|
|||
|
|
{
|
|||
|
|
let target_file = config.target_file || '[NO TARGET FILE]';
|
|||
|
|
// Actually confirm support
|
|||
|
|
if (afusSupportAjaxUploadWithProgress()) {
|
|||
|
|
console.log('[AJAX Uploader: %s] init [%s] with %o', target_file, config.target_form || '[NO TARGET FORM]', config);
|
|||
|
|
// FAIL with no submit or no mask entry
|
|||
|
|
if (document.getElementById(target_file + '-submit') !== null &&
|
|||
|
|
document.getElementById('mask-' + target_file) !== null
|
|||
|
|
) {
|
|||
|
|
// check config block all set
|
|||
|
|
config = afusUploaderConfigCheck(config);
|
|||
|
|
// console.log('[AJAX Uploader: %s] config cleanup: %o', target_file, config);
|
|||
|
|
|
|||
|
|
// remove multiple frmo -files input
|
|||
|
|
if (config.max_files == 1) {
|
|||
|
|
document.getElementById(target_file + '-file').removeAttribute('multiple');
|
|||
|
|
}
|
|||
|
|
// if we have allowed file type add acceept to the file selector
|
|||
|
|
if (config.file_accept.length > 0) {
|
|||
|
|
document.getElementById(target_file + '-file').setAttribute('accept', config.file_accept.join(','));
|
|||
|
|
}
|
|||
|
|
// add click event for the submit buttons to set which one submitted the file
|
|||
|
|
document.getElementById(target_file + '-submit').addEventListener('click', function() {
|
|||
|
|
this.form.submitted = this.id;
|
|||
|
|
});
|
|||
|
|
// clear upload list click action
|
|||
|
|
if (document.getElementById(target_file + '-clear') !== null) {
|
|||
|
|
document.getElementById(target_file + '-clear').addEventListener('click', function() {
|
|||
|
|
console.log('[AJAX Uploader: %s] Clear upload queue list', target_file);
|
|||
|
|
// reset array list
|
|||
|
|
AFUS_file_list[target_file] = [];
|
|||
|
|
// clear and hide status list
|
|||
|
|
document.getElementById(target_file + '-upload-status').innerHTML = '';
|
|||
|
|
document.getElementById(target_file + '-upload-status').style.display = 'none';
|
|||
|
|
// hide self
|
|||
|
|
document.getElementById(target_file + '-clear-div').style.display = 'none';
|
|||
|
|
// hide submit
|
|||
|
|
document.getElementById(target_file + '-submit-div').style.display = 'none';
|
|||
|
|
// hide abort all
|
|||
|
|
document.getElementById(target_file + '-abort-all-div').style.display = 'none';
|
|||
|
|
// call clear post
|
|||
|
|
if (typeof AFUS_functions[target_file].fileClear === 'function') {
|
|||
|
|
AFUS_functions[target_file].fileClear(target_file, config.target_router);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
// all abort all uploads
|
|||
|
|
if (document.getElementById(target_file + '-abort-all') !== null) {
|
|||
|
|
document.getElementById(target_file + '-abort-all').addEventListener('click', function() {
|
|||
|
|
console.log('[AJAX Uploader: %s] Abort all uploads, Current: %s | %o', target_file, AFUS_config[target_file].current, AFUS_config[target_file].current_xhr);
|
|||
|
|
// set full abort flag
|
|||
|
|
AFUS_config[target_file].abort = true;
|
|||
|
|
// abort current running xhr progress
|
|||
|
|
AFUS_config[target_file].current_xhr.abort();
|
|||
|
|
// cancel current
|
|||
|
|
afusCancelUploadOutput(target_file, AFUS_config[target_file].current);
|
|||
|
|
// write cancel to all future ones too
|
|||
|
|
for (const el of AFUS_file_list[target_file]) {
|
|||
|
|
afusCancelUploadOutput(target_file, el.afus_file_pos);
|
|||
|
|
}
|
|||
|
|
// final show file upload again
|
|||
|
|
afusFinalPostUpload(target_file);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
// set pass through events
|
|||
|
|
afusPassThroughEvent(
|
|||
|
|
target_file,
|
|||
|
|
config.max_files,
|
|||
|
|
config.target_router,
|
|||
|
|
config.auto_submit
|
|||
|
|
);
|
|||
|
|
// Ajax uploads are supported!
|
|||
|
|
// Init the Ajax form submission
|
|||
|
|
// pass on the target_file and the master form name
|
|||
|
|
afusInitFullFormAjaxUpload(
|
|||
|
|
target_file,
|
|||
|
|
config.target_form,
|
|||
|
|
{
|
|||
|
|
target_action: config.target_action,
|
|||
|
|
target_router: config.target_router,
|
|||
|
|
form_parameters: config.form_parameters
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
} else {
|
|||
|
|
console.log('[AJAX Uploader: %s] Element -submit and mask- not found for init', target_file);
|
|||
|
|
throw new Error('Cannot find element -submit and mask- to init: ' + target_file);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
console.log('[AJAX Uploader: %s] failed with missing submit or form capabilities', target_file);
|
|||
|
|
throw new Error('Failed with missing submit or form capabilities: ' + target_file);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [3] pass through event
|
|||
|
|
* Checks if a mask- element is present and then attaches
|
|||
|
|
* the passthrough event to the internal files button
|
|||
|
|
* checks if fileChange variable is a function and calls it
|
|||
|
|
* @param {String} target_file Prefix for elements
|
|||
|
|
* @param {Number} max_files maximum allowed file check
|
|||
|
|
* @param {String} target_router Router target name
|
|||
|
|
* @param {Boolean} auto_submit If set true will pass by submit button click
|
|||
|
|
* and automatically upload
|
|||
|
|
*/
|
|||
|
|
function afusPassThroughEvent(target_file, max_files, target_router, auto_submit)
|
|||
|
|
{
|
|||
|
|
console.log('[AJAX Uploader: %s] Pass through call: %s, Max: %s', target_file, document.getElementById('mask-' + target_file).length, max_files);
|
|||
|
|
// hide the other elements
|
|||
|
|
// hide submit button until file is selected
|
|||
|
|
document.getElementById(target_file + '-file').style.display = 'none';
|
|||
|
|
document.getElementById(target_file + '-submit-div').style.display = 'none';
|
|||
|
|
document.getElementById(target_file + '-clear-div').style.display = 'none';
|
|||
|
|
document.getElementById(target_file + '-abort-all-div').style.display = 'none';
|
|||
|
|
// init wipe upload status
|
|||
|
|
document.getElementById(target_file + '-upload-status').innerHTML = '';
|
|||
|
|
// write file name to upload status
|
|||
|
|
// show submit button
|
|||
|
|
document.getElementById(target_file + '-file').addEventListener('change', function() {
|
|||
|
|
// reset internal variables before adding files to the list
|
|||
|
|
afusResetInternalVariables(target_file);
|
|||
|
|
// upload status reseut
|
|||
|
|
document.getElementById(target_file + '-upload-status').innerHTML = '';
|
|||
|
|
var input_files = document.getElementById(target_file + '-file');
|
|||
|
|
// if max allowed is set, then check that we do not have more selected than allowed
|
|||
|
|
if (max_files > 0 && input_files.files.length > max_files) {
|
|||
|
|
// write error, abort
|
|||
|
|
console.log('[AJAX Uploader: %s] More files selected than allowed: %s/%s', target_file, input_files.files.length, max_files);
|
|||
|
|
// hide any clear/upload buttons as this is an error
|
|||
|
|
document.getElementById(target_file + '-submit-div').style.display = 'none';
|
|||
|
|
document.getElementById(target_file + '-clear-div').style.display = 'none';
|
|||
|
|
document.getElementById(target_file + '-abort-all-div').style.display = 'none';
|
|||
|
|
document.getElementById(target_file + '-upload-status').classList.add('SubError');
|
|||
|
|
document.getElementById(target_file + '-upload-status').innerHTML = (AFUS_strings[target_file].too_many_files || '{0} files selected, but maximum allowed is {1}').format(input_files.files.length, max_files);
|
|||
|
|
document.getElementById(target_file + '-upload-status').style.display = '';
|
|||
|
|
// abort
|
|||
|
|
return false;
|
|||
|
|
} else {
|
|||
|
|
document.getElementById(target_file + '-upload-status').classList.remove('SubError');
|
|||
|
|
}
|
|||
|
|
var el, el_sub, el_sub_sub;
|
|||
|
|
var valid_type = false, valid_size = false;
|
|||
|
|
for (let i = 0; i < input_files.files.length; i ++) {
|
|||
|
|
console.log('[AJAX Uploader: %s] Queue file %s with %o', target_file, i, input_files.files[i]);
|
|||
|
|
// check file type if requested
|
|||
|
|
// allow either of those. by extension or file type
|
|||
|
|
// Final valid check is done on backend
|
|||
|
|
if (
|
|||
|
|
// file extension
|
|||
|
|
afusHasExtension(target_file, input_files.files[i].name) ||
|
|||
|
|
// file type
|
|||
|
|
afusHasFileType(target_file, input_files.files[i].type)
|
|||
|
|
) {
|
|||
|
|
valid_type = true;
|
|||
|
|
}
|
|||
|
|
if (
|
|||
|
|
// file size
|
|||
|
|
(AFUS_config[target_file].max_file_size == 0 ||
|
|||
|
|
input_files.files[i].size <= AFUS_config[target_file].max_file_size)
|
|||
|
|
) {
|
|||
|
|
valid_size = true;
|
|||
|
|
}
|
|||
|
|
// we always add a base row, but we skip add to file list for submit
|
|||
|
|
// format:
|
|||
|
|
// <span>name<span>
|
|||
|
|
// <span>progress/info</span>
|
|||
|
|
// <span>delete from queue</span>
|
|||
|
|
// <span>cancel upload</span>
|
|||
|
|
// first set internal file pos
|
|||
|
|
if (valid_type === true && valid_size === true) {
|
|||
|
|
input_files.files[i].afus_file_pos = i;
|
|||
|
|
// push to global file list
|
|||
|
|
AFUS_file_list[target_file].push(input_files.files[i]);
|
|||
|
|
}
|
|||
|
|
// add to upload status list
|
|||
|
|
el = document.createElement('div');
|
|||
|
|
el.id = target_file + '-status-' + i;
|
|||
|
|
// file pos in queue? would need pos update on delete
|
|||
|
|
// file name
|
|||
|
|
el_sub = document.createElement('span');
|
|||
|
|
el_sub.id = target_file + '-status-name-' + i;
|
|||
|
|
el_sub.innerHTML = input_files.files[i].name;
|
|||
|
|
el.appendChild(el_sub);
|
|||
|
|
// progress info (just text)
|
|||
|
|
el_sub = document.createElement('span');
|
|||
|
|
el_sub.id = target_file + '-status-progress-' + i;
|
|||
|
|
el_sub.setAttribute('style', 'display:none;margin-left:5px;');
|
|||
|
|
el.appendChild(el_sub);
|
|||
|
|
// Toptional progress info as visual bar
|
|||
|
|
el_sub = document.createElement('span');
|
|||
|
|
el_sub.id = target_file + '-status-progress-bar-span-' + i;
|
|||
|
|
el_sub.setAttribute('style', 'display:none;margin-left:5px;');
|
|||
|
|
// attach progress element into it
|
|||
|
|
el_sub_sub = document.createElement('progress');
|
|||
|
|
el_sub_sub.id = target_file + '-status-progress-bar-' + i;
|
|||
|
|
el_sub_sub.max = 100;
|
|||
|
|
el_sub_sub.value = 0;
|
|||
|
|
el_sub.appendChild(el_sub_sub);
|
|||
|
|
el.appendChild(el_sub);
|
|||
|
|
// delete queue row, only of we have no auto upload
|
|||
|
|
el_sub = document.createElement('span');
|
|||
|
|
el_sub.id = target_file + '-status-delete-' + i;
|
|||
|
|
// if invalid types, show error
|
|||
|
|
if (valid_type === false || valid_size === false) {
|
|||
|
|
el_sub.setAttribute('style', 'margin-left:5px;');
|
|||
|
|
el_sub.classList.add('SubError');
|
|||
|
|
let error_list = [];
|
|||
|
|
if (valid_type === false) {
|
|||
|
|
error_list.push(AFUS_strings[target_file].invalid_type || 'Invalid file type');
|
|||
|
|
}
|
|||
|
|
if (valid_size === false) {
|
|||
|
|
error_list.push((AFUS_strings[target_file].invalid_size || 'Maximum file size is {0}').format(formatBytes(AFUS_config[target_file].max_file_size)));
|
|||
|
|
}
|
|||
|
|
el_sub.innerHTML = error_list.join(', ');
|
|||
|
|
} else {
|
|||
|
|
// else show remove
|
|||
|
|
el_sub.setAttribute('style', 'margin-left:5px;');
|
|||
|
|
// -W083
|
|||
|
|
el_sub.addEventListener('click', function() { // jshint ignore:line
|
|||
|
|
AFUS_file_list[target_file].splice(i, 1);
|
|||
|
|
console.log('[AJAX Uploader: %s] Remove pre-upload: %s, Length: %s', target_file, i, AFUS_file_list[target_file].length);
|
|||
|
|
document.getElementById(target_file + '-status-' + i).remove();
|
|||
|
|
// hide submit button if there are none to upload
|
|||
|
|
if (AFUS_file_list[target_file].length == 0) {
|
|||
|
|
document.getElementById(target_file + '-submit-div').style.display = 'none';
|
|||
|
|
document.getElementById(target_file + '-clear-div').style.display = 'none';
|
|||
|
|
document.getElementById(target_file + '-abort-all-div').style.display = 'none';
|
|||
|
|
}
|
|||
|
|
// delete
|
|||
|
|
if (typeof AFUS_functions[target_file].fileRemove === 'function') {
|
|||
|
|
AFUS_functions[target_file].fileRemove(target_file, i, target_router);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
el_sub.innerHTML = AFUS_strings[target_file].remove || 'Remove';
|
|||
|
|
}
|
|||
|
|
el.appendChild(el_sub);
|
|||
|
|
// for upload abort
|
|||
|
|
el_sub = document.createElement('span');
|
|||
|
|
el_sub.id = target_file + '-status-abort-' + i;
|
|||
|
|
el_sub.setAttribute('style', 'margin-left:5px;display:none;');
|
|||
|
|
el_sub.innerHTML = AFUS_strings[target_file].cancel || 'Cancel';
|
|||
|
|
el.appendChild(el_sub);
|
|||
|
|
// hidden info
|
|||
|
|
el_sub = document.createElement('input');
|
|||
|
|
el_sub.id = target_file + '-uid-' + i;
|
|||
|
|
el_sub.name = target_file + '-uid-' + i;
|
|||
|
|
el_sub.type = 'hidden';
|
|||
|
|
el.appendChild(el_sub);
|
|||
|
|
// append full block
|
|||
|
|
document.getElementById(target_file + '-upload-status').appendChild(el);
|
|||
|
|
// file change update per file
|
|||
|
|
if (typeof AFUS_functions[target_file].fileChange === 'function') {
|
|||
|
|
AFUS_functions[target_file].fileChange(target_file, i, target_router);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// file change update after all files processed
|
|||
|
|
if (typeof AFUS_functions[target_file].fileChangeAll === 'function') {
|
|||
|
|
AFUS_functions[target_file].fileChangeAll(target_file, target_router);
|
|||
|
|
}
|
|||
|
|
// updated file list render
|
|||
|
|
document.getElementById(target_file + '-upload-status').style.display = '';
|
|||
|
|
// show submit if all basic ok
|
|||
|
|
if (auto_submit === false && AFUS_file_list[target_file].length > 0) {
|
|||
|
|
document.getElementById(target_file + '-submit-div').style.display = '';
|
|||
|
|
}
|
|||
|
|
// show clear div
|
|||
|
|
document.getElementById(target_file + '-clear-div').style.display = '';
|
|||
|
|
// if auto submit flaged and basic file check ok
|
|||
|
|
if (auto_submit === true && AFUS_file_list[target_file].length > 0) {
|
|||
|
|
document.getElementById(target_file + '-submit').click();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [3-A] check if input file name has an allowed extension
|
|||
|
|
* @param {String} file_name File name from upload file object
|
|||
|
|
* @return {Boolean} True for file extension in list, else False
|
|||
|
|
*/
|
|||
|
|
function afusHasExtension(target_file, file_name)
|
|||
|
|
{
|
|||
|
|
if (AFUS_config[target_file].allowed_extensions.length == 0) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
return (
|
|||
|
|
new RegExp(
|
|||
|
|
'(' +
|
|||
|
|
AFUS_config[target_file].allowed_extensions
|
|||
|
|
.join('|')
|
|||
|
|
.replace(/\./g, '\\.') +
|
|||
|
|
')$',
|
|||
|
|
'i'
|
|||
|
|
)
|
|||
|
|
).test(file_name);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [3-B] check if input file type has matching file type listed
|
|||
|
|
* Note that this must have a full match
|
|||
|
|
* @param {String} file_type File type from upload file object
|
|||
|
|
* @param {Array} file_types Array of allowed file types (mime)
|
|||
|
|
* @return {Boolean} True for valid file, else False
|
|||
|
|
*/
|
|||
|
|
function afusHasFileType(target_file, file_type)
|
|||
|
|
{
|
|||
|
|
if (AFUS_config[target_file].allowed_file_types.length == 0) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
// if file tyoe is not set
|
|||
|
|
if (file_type.length == 0) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
return AFUS_config[target_file].allowed_file_types.includes(file_type.toLowerCase());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [4] MAIN CALL for upload
|
|||
|
|
* Creates the main ajax send request if a submit on the main form is detected
|
|||
|
|
* all the parameters are passed on from the init function
|
|||
|
|
* The function will throw an error of not action (target) can be found
|
|||
|
|
* From the config object the following parts are used
|
|||
|
|
* {String} target_router value of the action _POST variable
|
|||
|
|
* {String} target_action override the target_form action field (target file)
|
|||
|
|
* {Object} form_parameters additional parameters added to the form submit
|
|||
|
|
|
|||
|
|
* @param {String} target_file prefix for the element for the file upload
|
|||
|
|
* @param {String} target_form the master form in which this element sits
|
|||
|
|
* @param {Object} config config object, without translations and functuons
|
|||
|
|
* @return {Boolean} false to not trigger normal form submit
|
|||
|
|
*/
|
|||
|
|
function afusInitFullFormAjaxUpload(target_file, target_form, config)
|
|||
|
|
{
|
|||
|
|
// should check that form exists
|
|||
|
|
// + '-form'
|
|||
|
|
var form = document.getElementById(target_form);
|
|||
|
|
if (!form.getAttribute('action') && !config.target_action) {
|
|||
|
|
console.log('[%s] !!!!! MISSING FORM ACTION ENTRY: %s', target_file, target_form);
|
|||
|
|
throw new Error('MISSING FORM ACTION ENTRY FOR: ' + target_file + ', FORM: ' + target_form);
|
|||
|
|
}
|
|||
|
|
form.onsubmit = function()
|
|||
|
|
{
|
|||
|
|
// TARGET FILE is unset because this is an attechd action call
|
|||
|
|
// IT WILL have the LAST set target_file if there are MANY set
|
|||
|
|
// WE need to grab it from the form.submitted name
|
|||
|
|
// get the form.submitted text (remove -submit) and compare to target_file,
|
|||
|
|
// if different overwrite the target file
|
|||
|
|
let _target_file = form.submitted.split('-')[0];
|
|||
|
|
console.log('[AJAX Uploader: %s] Wanted: %s, Submitted: %s', target_file, _target_file, form.submitted);
|
|||
|
|
if (target_file != _target_file) {
|
|||
|
|
target_file = _target_file;
|
|||
|
|
}
|
|||
|
|
// remove previous highlight if set
|
|||
|
|
document.getElementById(target_file + '-upload-status').classList.remove('SubError');
|
|||
|
|
document.getElementById(target_file + '-upload-status').style.display = '';
|
|||
|
|
// hide the upload button itself so we don't press twice
|
|||
|
|
document.getElementById(target_file + '-file-div').style.display = 'none';
|
|||
|
|
// hide submit button too
|
|||
|
|
document.getElementById(target_file + '-submit-div').style.display = 'none';
|
|||
|
|
// hide clear button
|
|||
|
|
document.getElementById(target_file + '-clear-div').style.display = 'none';
|
|||
|
|
// show all abort if >1 upload
|
|||
|
|
if (AFUS_file_list[target_file].length > 1) {
|
|||
|
|
document.getElementById(target_file + '-abort-all-div').style.display = '';
|
|||
|
|
}
|
|||
|
|
// call class for promise type upload flow
|
|||
|
|
new afusAsyncUploader(target_file, afusSendFile).whenComplete
|
|||
|
|
.then(
|
|||
|
|
(t) => {
|
|||
|
|
// ALL OK
|
|||
|
|
console.log('[AJAX Uploader: %s] *** FILE UPLOAD *** GOT OK: %o', target_file, t);
|
|||
|
|
},
|
|||
|
|
(t) => {
|
|||
|
|
// one failed -> allow reupload?
|
|||
|
|
console.log('[AJAX Uploader: %s] *** FILE UPLOAD *** FAILED: %o', target_file, t);
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
.finally(() => {
|
|||
|
|
// done; FINAL calls here
|
|||
|
|
console.log('[AJAX Uploader: %s] **** FILE UPLOAD FINAL ****', target_file);
|
|||
|
|
// post all done function call, only once all files are processed
|
|||
|
|
// check overall running queue numbers for this
|
|||
|
|
// show uploader select again
|
|||
|
|
afusFinalPostUpload(target_file);
|
|||
|
|
// if there is a final function, call it
|
|||
|
|
if (typeof AFUS_functions[target_file].fileUploadedAll === 'function') {
|
|||
|
|
AFUS_functions[target_file].fileUploadedAll(target_file, config.target_router, AFUS_config[target_file].all_success);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
// Avoid normal form submission
|
|||
|
|
return false;
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [5] class group for handling promise loop uploads
|
|||
|
|
*/
|
|||
|
|
class afusAsyncUploader
|
|||
|
|
{
|
|||
|
|
/**
|
|||
|
|
* [5] constructor for afusAsyncUploader
|
|||
|
|
* @param {String} target_file prefix for the element for the file upload
|
|||
|
|
* @param {Function} function_call Function with internal Promise to be called
|
|||
|
|
* Is the actual send file function afusSendFile
|
|||
|
|
*/
|
|||
|
|
constructor(target_file, function_call)
|
|||
|
|
{
|
|||
|
|
// target file for AFUS_file shift
|
|||
|
|
this.target_file = target_file;
|
|||
|
|
// additional data to append to the form submit (global)
|
|||
|
|
this.form_append = {};
|
|||
|
|
if (typeof AFUS_functions[this.target_file].fileBeforeUploadAll === 'function') {
|
|||
|
|
for (const [key, value] of Object.entries(AFUS_functions[this.target_file].fileBeforeUploadAll(this.target_file, AFUS_config[this.target_file].target_router))) {
|
|||
|
|
this.form_append[key] = value;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// the function call (afusSendFile) with promise
|
|||
|
|
this.functionCall = function_call;
|
|||
|
|
// error flag for upload error
|
|||
|
|
this.errorFlag = false;
|
|||
|
|
// create a finish promise
|
|||
|
|
// this is called on the final iteration run
|
|||
|
|
// new afusAsyncUploader(target_file, afusSendFile).whenComplete ....
|
|||
|
|
this.whenComplete = new Promise((resolve, reject) => {
|
|||
|
|
this.resolve = resolve;
|
|||
|
|
this.reject = reject;
|
|||
|
|
});
|
|||
|
|
// call the iterator for files
|
|||
|
|
// this is a self caller (recursive)
|
|||
|
|
this.iterator();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [5-a] main iterator function
|
|||
|
|
* This one calls the function given in the contructor
|
|||
|
|
* which is the actual send file function.
|
|||
|
|
* it will wait for a reject/resolve from this function before
|
|||
|
|
* launching itself again
|
|||
|
|
*/
|
|||
|
|
iterator()
|
|||
|
|
{
|
|||
|
|
// get the first file
|
|||
|
|
// call the function with self and the file to upload
|
|||
|
|
this.functionCall.call(this, this.target_file, AFUS_file_list[this.target_file].shift(), this.form_append)
|
|||
|
|
// if the uploader throws an error, flag it
|
|||
|
|
.catch((error) => {
|
|||
|
|
// errors
|
|||
|
|
console.log('[aAU] ITERATOR: !!!ERROR!!! catch: %s', error);
|
|||
|
|
this.errorFlag = true;
|
|||
|
|
})
|
|||
|
|
// on final call iterator again if we have files left
|
|||
|
|
// or resolve/reject
|
|||
|
|
.finally(() => {
|
|||
|
|
console.log('[aAU] ITERATOR: finally: %s', AFUS_file_list[this.target_file].length);
|
|||
|
|
// after anything
|
|||
|
|
if (AFUS_file_list[this.target_file].length > 0) {
|
|||
|
|
// must catch/finally!
|
|||
|
|
this.iterator();
|
|||
|
|
} else {
|
|||
|
|
console.log('[aAU] ITERATOR FINAL CALL: %s', this.errorFlag);
|
|||
|
|
// some error case, error: reject();
|
|||
|
|
// else resolve as ok
|
|||
|
|
if (this.errorFlag === true) {
|
|||
|
|
this.reject('[aAU] ITERATOR FAILED');
|
|||
|
|
} else {
|
|||
|
|
this.resolve('[aAU] ITERATOR RESOLVED');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [6] action to create form and send file
|
|||
|
|
* @param {String} target_file prefix for the element for the file upload
|
|||
|
|
* @param {Object} file File to upload (object)
|
|||
|
|
* @param {Object} form_append Additional data to add to the form that gets submitted
|
|||
|
|
* In key: value format
|
|||
|
|
* @return {Promise} resolve, reject promise group
|
|||
|
|
*/
|
|||
|
|
function afusSendFile(target_file, file, form_append)
|
|||
|
|
{
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
// promise
|
|||
|
|
let form = document.getElementById(AFUS_config[target_file].target_form);
|
|||
|
|
// this is the actual index, we use this one because the array index can change
|
|||
|
|
// after an element gets removed
|
|||
|
|
let file_pos = file.afus_file_pos;
|
|||
|
|
console.log('[AJAX Uploader: %s/%s] Push submit %o, Abort: %s', target_file, file_pos, file, AFUS_config[target_file].abort);
|
|||
|
|
// if abort, reject
|
|||
|
|
if (AFUS_config[target_file].abort === true) {
|
|||
|
|
// reset file upload list
|
|||
|
|
AFUS_file_list[target_file] = [];
|
|||
|
|
reject('Abort All,' + target_file + ',' + file_pos);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
// set form, etc
|
|||
|
|
let formData = new FormData();
|
|||
|
|
// We send the data where the form wanted
|
|||
|
|
let action = form.getAttribute('action');
|
|||
|
|
// in case we have a target action set, we overwirde
|
|||
|
|
if (AFUS_config[target_file].target_action) {
|
|||
|
|
action = AFUS_config[target_file].target_action;
|
|||
|
|
}
|
|||
|
|
console.log('[AJAX Uploader: %s/%s] ACTION: %s, PATH: %s', target_file, file_pos, action, AFUS_config[target_file].path);
|
|||
|
|
// add action
|
|||
|
|
if (!AFUS_config[target_file].target_router) {
|
|||
|
|
AFUS_config[target_file].target_router = 'fileUpload';
|
|||
|
|
}
|
|||
|
|
formData.append('action', AFUS_config[target_file].target_router);
|
|||
|
|
formData.append('uploadQueuePos', file_pos);
|
|||
|
|
formData.append('uploadQueueMax', AFUS_file_list[target_file].length);
|
|||
|
|
formData.append('uploadName', target_file + '-file');
|
|||
|
|
// add file only (first file found) with target file name
|
|||
|
|
formData.append(target_file + '-file', file);
|
|||
|
|
formData.append(target_file + '-uid-' + file_pos, document.getElementById(target_file + '-uid-' + file_pos).value);
|
|||
|
|
// init params -> global -> per file
|
|||
|
|
// lower ones overwrite upper ones
|
|||
|
|
// add additional ones
|
|||
|
|
for (const [key, value] of Object.entries(AFUS_config[target_file].form_parameters)) {
|
|||
|
|
formData.append(key, value);
|
|||
|
|
}
|
|||
|
|
// add global additional data
|
|||
|
|
for (const [key, value] of Object.entries(form_append)) {
|
|||
|
|
formData.append(key, value);
|
|||
|
|
}
|
|||
|
|
// external data gets added
|
|||
|
|
if (typeof AFUS_functions[target_file].fileBeforeUpload === 'function') {
|
|||
|
|
for (const [key, value] of Object.entries(AFUS_functions[target_file].fileBeforeUpload(target_file, file_pos, AFUS_config[target_file].target_router))) {
|
|||
|
|
formData.append(key, value);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
console.log('[AJAX Uploader: %s/%s] Send data to: %s, with path: %s', target_file, file_pos, action, AFUS_config[target_file].path);
|
|||
|
|
// * Once the FormData instance is ready and we know
|
|||
|
|
// * where to send the data, the data gets submitted
|
|||
|
|
// * it adds event listeners for start/progress/load and general ready state change
|
|||
|
|
// * then it sends the data
|
|||
|
|
// * each event listener is called during the stages
|
|||
|
|
// * - start: afusOnLoadStartHandler
|
|||
|
|
// * - progress: afusOnProgressHandler
|
|||
|
|
// * - end: afusOnLoadHandler
|
|||
|
|
// * - finish: afusOnReadyStateChangeHandler
|
|||
|
|
// * - handler onload for resolve/reject flow only
|
|||
|
|
// * Note: on xhr.onerror function is called if an error happens
|
|||
|
|
// * and just calls the xhr.send again
|
|||
|
|
// * there are some issues on firefox that can trigger this
|
|||
|
|
|
|||
|
|
// Get an XMLHttpRequest instance
|
|||
|
|
let xhr = new XMLHttpRequest();
|
|||
|
|
let uri = AFUS_config[target_file].path + action;
|
|||
|
|
AFUS_config[target_file].current_xhr = xhr;
|
|||
|
|
// Set up events for upload progress
|
|||
|
|
xhr.upload.addEventListener('loadstart', afusOnLoadStartHandler.bind(null, target_file, file_pos), false);
|
|||
|
|
xhr.upload.addEventListener('progress', afusOnProgressHandler.bind(null, target_file, file_pos), false);
|
|||
|
|
xhr.upload.addEventListener('load', afusOnLoadHandler.bind(null, target_file, file_pos), false);
|
|||
|
|
// main events for loaded/error tracking
|
|||
|
|
// same as load level
|
|||
|
|
xhr.onload = () => {
|
|||
|
|
console.log('[AJAX Uploader: %s/%s] STATE: %s, Status: %s', target_file, file_pos, xhr.readyState, xhr.status);
|
|||
|
|
// on 4 + 200 resolve()
|
|||
|
|
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
|
|||
|
|
console.log('[AJAX Uploader: %s/%s] RESOLVED', target_file, file_pos);
|
|||
|
|
resolve('Upload OK,' + target_file + ',' + file_pos);
|
|||
|
|
} else {
|
|||
|
|
console.log('[AJAX Uploader: %s/%s] FAILED', target_file, file_pos);
|
|||
|
|
reject('Failed,' + target_file + ',' + file_pos);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
// this one also gets errors readyState == 0
|
|||
|
|
xhr.addEventListener('readystatechange', afusOnReadyStateChangeHandler.bind(null, target_file, file_pos, AFUS_config[target_file].target_router), false);
|
|||
|
|
// on error, open a new connection and try again
|
|||
|
|
xhr.onerror = function() {
|
|||
|
|
console.log('[AJAX Uploader: %s/%s] upload ERROR', target_file, file_pos);
|
|||
|
|
// try again
|
|||
|
|
xhr.open('POST', uri);
|
|||
|
|
xhr.send(formData);
|
|||
|
|
// limit max errors?
|
|||
|
|
};
|
|||
|
|
// Set up request
|
|||
|
|
xhr.open('POST', uri);
|
|||
|
|
// on error log & try again
|
|||
|
|
// Fire!
|
|||
|
|
xhr.send(formData);
|
|||
|
|
// add a abort request on the delete button
|
|||
|
|
document.getElementById(target_file + '-status-abort-' + file_pos).innerHTML = AFUS_strings[target_file].cancel || 'Cancel';
|
|||
|
|
document.getElementById(target_file + '-status-abort-' + file_pos).addEventListener('click', function() { // jshint ignore:line
|
|||
|
|
if (xhr) {
|
|||
|
|
console.log('[AJAX Uploader: %s/%s] Upload cancled', target_file, file_pos);
|
|||
|
|
// send abort
|
|||
|
|
xhr.abort();
|
|||
|
|
xhr = null;
|
|||
|
|
// remove from running count
|
|||
|
|
afusRunningStop(target_file);
|
|||
|
|
// write cancled text to file row
|
|||
|
|
afusCancelUploadOutput(target_file, file_pos);
|
|||
|
|
// reject as cancled
|
|||
|
|
reject('Cancled,' + target_file + ',' + file_pos);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
document.getElementById(target_file + '-status-abort-' + file_pos).style.display = '';
|
|||
|
|
console.log('[AJAX Uploader: %s/%s] RUNNING: %s, SEND DATA: %o', target_file, file_pos, afusRunningGet(target_file), formData);
|
|||
|
|
console.log('[AJAX Uploader: %s/%s] Data sent to: %s', target_file, file_pos, action);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [7-A] Handle the start of the transmission
|
|||
|
|
* @param {String} target_file element name prefix
|
|||
|
|
* @param {Number} file_pos Position in upload queue
|
|||
|
|
*/
|
|||
|
|
function afusOnLoadStartHandler(target_file, file_pos)
|
|||
|
|
{
|
|||
|
|
// console.log('[AJAX Uploader: %s] start uploading', target_file);
|
|||
|
|
// add one to the running queue
|
|||
|
|
afusRunningStart(target_file);
|
|||
|
|
// set current upload
|
|||
|
|
AFUS_config[target_file].current = file_pos;
|
|||
|
|
// progress init
|
|||
|
|
// clear progress info block
|
|||
|
|
document.getElementById(target_file + '-status-progress-' + file_pos).style.display = '';
|
|||
|
|
// bar block
|
|||
|
|
document.getElementById(target_file + '-status-progress-bar-span-' + file_pos).style.display = '';
|
|||
|
|
// hide delete (or delete)
|
|||
|
|
document.getElementById(target_file + '-status-delete-' + file_pos).style.display = 'none';
|
|||
|
|
// must set dynamic
|
|||
|
|
document.getElementById(target_file + '-status-progress-' + file_pos).innerHTML = AFUS_strings[target_file].upload_start || 'Upload start';
|
|||
|
|
document.getElementById(target_file + '-status-progress-bar-' + file_pos).value = 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [7-B] Handle the end of the transmission
|
|||
|
|
* @param {String} target_file element name prefix
|
|||
|
|
* @param {Number} file_pos Position in upload queue
|
|||
|
|
*/
|
|||
|
|
function afusOnLoadHandler(target_file, file_pos)
|
|||
|
|
{
|
|||
|
|
// console.log('[AJAX Uploader: %s] finished uploading', target_file);
|
|||
|
|
// unset current running
|
|||
|
|
AFUS_config[target_file].current = -1;
|
|||
|
|
// must set dynamic
|
|||
|
|
document.getElementById(target_file + '-status-progress-' + file_pos).innerHTML = AFUS_strings[target_file].upload_finished || 'Upload finished';
|
|||
|
|
// hide bar block
|
|||
|
|
document.getElementById(target_file + '-status-progress-bar-span-' + file_pos).style.displasy = 'none';
|
|||
|
|
// hide abort
|
|||
|
|
document.getElementById(target_file + '-status-abort-' + file_pos).style.display = 'none';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [7-C] Handle the progress
|
|||
|
|
* calculates percent and show that in the upload status element
|
|||
|
|
* @param {String} target_file element name prefix
|
|||
|
|
* @param {Number} queue_count Amount of files in the upload queue
|
|||
|
|
* @param {Event} evt event data for upload progress
|
|||
|
|
* holds file size and bytes transmitted
|
|||
|
|
*/
|
|||
|
|
function afusOnProgressHandler(target_file, file_pos, evt)
|
|||
|
|
{
|
|||
|
|
// must set dynamic
|
|||
|
|
var percent = evt.loaded / evt.total * 100;
|
|||
|
|
// console.log('[AJAX Uploader: %s] Uploading: %s', target_file, Math.round(percent));
|
|||
|
|
document.getElementById(target_file + '-status-progress-' + file_pos).innerHTML = Math.round(percent) + '%';
|
|||
|
|
// write progress bar too
|
|||
|
|
document.getElementById(target_file + '-status-progress-bar-' + file_pos).value = Math.round(percent);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [7-D] Handle the response from the server
|
|||
|
|
* If ready state is 4 it will call the final post upload function on status success
|
|||
|
|
* if status is other it will call the upload error function
|
|||
|
|
* on all other statii it currently only prints a debug log message
|
|||
|
|
* @param {String} target_file Element name prefix
|
|||
|
|
* @param {Number} file_pos Position in upload queue
|
|||
|
|
* @param {String} target_router Target router name
|
|||
|
|
* @param {Event} evt event data for return data and ready state info
|
|||
|
|
*/
|
|||
|
|
function afusOnReadyStateChangeHandler(target_file, file_pos, target_router, evt)
|
|||
|
|
{
|
|||
|
|
var status, readyState, responseText, responseData;
|
|||
|
|
try {
|
|||
|
|
readyState = evt.target.readyState;
|
|||
|
|
responseText = evt.target.responseText;
|
|||
|
|
status = evt.target.status;
|
|||
|
|
} catch(e) {
|
|||
|
|
errorCatch(e);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
// XMLHttpRequest.DONE == 4
|
|||
|
|
if (readyState == 4 && status == '200' && responseText) {
|
|||
|
|
responseData = JSON.parse(responseText);
|
|||
|
|
// upload finished
|
|||
|
|
afusRunningStop(target_file);
|
|||
|
|
// must set dynamic
|
|||
|
|
console.log('[AJAX Uploader ORSC: %s/%s] Running: %s, Uploader response: %s -> %o', target_file, file_pos, afusRunningGet(target_file), responseData.status, responseData);
|
|||
|
|
// then run status output
|
|||
|
|
afusUploadError(target_file, file_pos, responseData.content.msg, responseData.status == 'success' ? false : true);
|
|||
|
|
// run post uploader
|
|||
|
|
if (responseData.status == 'success') {
|
|||
|
|
afusPostUpload(target_file, file_pos, target_router, {
|
|||
|
|
file_uid: responseData.content.file_uid,
|
|||
|
|
file_size: responseData.content.file_size,
|
|||
|
|
file_size_raw: responseData.content.file_size_raw,
|
|||
|
|
file_name: responseData.content.file_name
|
|||
|
|
}, responseData.content);
|
|||
|
|
} else {
|
|||
|
|
// one file failed, we global flag not all ok
|
|||
|
|
AFUS_config[target_file].all_success = false;
|
|||
|
|
// call per file upload error function if exsts
|
|||
|
|
if (typeof AFUS_functions[target_file].fileUploadError === 'function') {
|
|||
|
|
AFUS_functions[target_file].fileUploadError(target_file, file_pos, target_router, responseData.content);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
console.log('[AJAX Uploader ORSC: %s/%s] Running: %s, ReadyState: %s, status: %s, Text: %o', target_file, file_pos, afusRunningGet(target_file), readyState, status, responseText);
|
|||
|
|
// return fail for error
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// __END__
|