A simple javascript powered file uploader with progress and hooks support. Was created as a loose replacement for FineUploader. Currently only works with local uploads Below is the log history from the previous folder: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ commit 6ea59f91314be8b2b936920d4b0a3f04932c9e73 ┃ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━ Author: Clemens Schwaighofer <clemens.schwaighofer@egplusww.com> Date: Thu Jun 3 10:31:54 2021 +0900 AJAX file uploader code clean up Add global abort and connected clean up of function calls and element show/hide blocks Add simple progress bar for upload (in connection with percent upload) Move running AFUS object into the config object Added new config object entries for this: - abort: flagged true if global abort is triggered - running: moved from AFUS_running into config, how many uploads are currently running - current: current file_pos running (that is queue position in array) - current_xhr: current XHR upload object, used for abort calls ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ commit b061008ca0b496f6157b8f073d7ca14abb469ef5 ┃ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━ Author: Clemens Schwaighofer <clemens.schwaighofer@egplusww.com> Date: Thu May 27 11:51:47 2021 +0900 Update AJAX file uploader to work sequential Rewrite flow in ajax uploader to use promsies to launch a new upload only if the previous upload was finished (in any way) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ commit a2ec369a8cae65e216fe402ac8b5d106e3db99af ┃ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━ Author: Clemens Schwaighofer <clemens.schwaighofer@egplusww.com> Date: Thu May 20 11:48:38 2021 +0900 Updated ajax simple file uploader to allow multiple file uploads ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ commit 66878f89c82fe914031e113e780fd72e9ff09b78 ┃ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━ Author: Clemens Schwaighofer <clemens.schwaighofer@egplusww.com> Date: Mon May 10 09:13:25 2021 +0900 Excel vendor sub module, ajax delay test, ajax file upload fix, others ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ commit e4efbbfdf843d8b2e53e4d3c873799b839875721 ┃ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━ Author: Clemens Schwaighofer <clemens.schwaighofer@egplusww.com> Date: Fri Apr 9 13:44:28 2021 +0900 ajax file upload updates, byte format test fixes, ssh2 test fixes, array append test add, nested array recursive walk test add ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ commit 88734ebaf7420a704a538f0a923ce6996b0b5237 ┃ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━ Author: Clemens Schwaighofer <clemens.schwaighofer@egplusww.com> Date: Mon Jan 25 17:15:14 2021 +0900 Add ajax uploader on error external function call Like post success uploaded, this function is called on any "non success" return falue. Passes on the full returned ajax data object ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ commit 2098fe53d510c03f291eaa5c4b2aefd60c24448e ┃ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━ Author: Clemens Schwaighofer <clemens.schwaighofer@egplusww.com> Date: Wed Jan 13 06:29:20 2021 +0900 Add additional parameters method parameter Added on init of form, when submitted before additional external form parameters function is called ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ commit 0c2211e2312abf368c4151dbdf54d4fa96a121dc ┃ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━ Author: Clemens Schwaighofer <clemens.schwaighofer@egplusww.com> Date: Tue Jan 12 10:09:42 2021 +0900 AJAX File uploader change for external function call. External functions are passed into the uploader as parameters. With this we can have unique functions for each init ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ commit c1eb15e30df319db1d5104bc5e4ab0cd8bb7cc8c ┃ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━ Author: Clemens Schwaighofer <clemens.schwaighofer@egplusww.com> Date: Fri Jan 8 09:59:33 2021 +0900 AJAX file uploader target action router value dynamic setting update, other test php files updates and additions ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ commit 429af6db892d6968ad87a3db662f3ff1a9d74270 ┃ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━ Author: Clemens Schwaighofer <clemens.schwaighofer@egplusww.com> Date: Thu Nov 5 11:33:30 2020 +0900 AJAX file uploader test code Simple single file AJAX file uploader with backend code sample. Work in progress with open - better, simpler frontend code insert part - multiple file upload - flag auto handling zip files (or tar, etc) - make it not rely on Jquery at all
1223 lines
48 KiB
JavaScript
Executable File
1223 lines
48 KiB
JavaScript
Executable File
/* 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__
|