AJAX simple file uploader

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
This commit is contained in:
2021-10-12 08:55:21 +09:00
commit 32455459da
14 changed files with 4355 additions and 0 deletions

336
test/backend.php Normal file
View File

@@ -0,0 +1,336 @@
<?php
header("Content-Type: application/json; charset=UTF-8");
session_start();
define('DS', DIRECTORY_SEPARATOR);
define('DIR', __DIR__ . DS);
define('LOG', 'log' . DS);
// run id
DEFINE('RUNUID', session_id());
function logWrite($message)
{
global $fh;
if (!$fh) {
$fh = fopen(DIR . LOG . 'backend.' . date("Y-m-d") . '.log', 'a');
}
fwrite($fh, '{' . RUNUID . '} [' . printTime() . '] ' . $message . "\n");
}
function logHandleClose()
{
global $fh;
if ($fh) {
fclose($fh);
}
}
function printTime()
{
$set_microtime = 4;
list($microtime, $timestamp) = explode(' ', microtime());
$string = date("Y-m-d H:i:s", $timestamp);
// if microtime flag is -1 no round, if 0, no microtime, if >= 1, round that size
$string .= substr(number_format(round($microtime, $set_microtime), $set_microtime), 1);
return $string;
}
function errMsg(string $level, string $message): void
{
global $error_string;
$error_string[] = [
'level' => $level,
'str' => $message
];
}
/**
* [printAr description]
* @param array $array [description]
* @return string [description]
*/
function printAr(array $array): string
{
return "<pre>" . print_r($array, true) . "</pre>";
}
/**
* helper function for PHP file upload error messgaes to messge string
* @param int $error_code integer _FILE upload error code
* @return string message string, translated
*/
function fileUploadErrorMessage(int $error_code): string
{
switch ($error_code) {
case UPLOAD_ERR_INI_SIZE:
$message = 'The uploaded file exceeds the upload_max_filesize directive in php.ini';
break;
case UPLOAD_ERR_FORM_SIZE:
$message = 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form';
break;
case UPLOAD_ERR_PARTIAL:
$message = 'The uploaded file was only partially uploaded';
break;
case UPLOAD_ERR_NO_FILE:
$message = 'No file was uploaded';
break;
case UPLOAD_ERR_NO_TMP_DIR:
$message = 'Missing a temporary folder';
break;
case UPLOAD_ERR_CANT_WRITE:
$message = 'Failed to write file to disk';
break;
case UPLOAD_ERR_EXTENSION:
$message = 'File upload stopped by extension';
break;
default:
$message = 'Unknown upload error';
break;
}
return $message;
}
/**
* creates psuedo random uuid v4
* Code take from class here:
* https://www.php.net/manual/en/function.uniqid.php#94959
* @return string pseudo random uuid v4
*/
function uuidv4(): string
{
return sprintf(
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
// 32 bits for "time_low"
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
// 16 bits for "time_mid"
mt_rand(0, 0xffff),
// 16 bits for "time_hi_and_version",
// four most significant bits holds version number 4
mt_rand(0, 0x0fff) | 0x4000,
// 16 bits, 8 bits for "clk_seq_hi_res",
// 8 bits for "clk_seq_low",
// two most significant bits holds zero and one for variant DCE1.1
mt_rand(0, 0x3fff) | 0x8000,
// 48 bits for "node"
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0xffff)
);
}
/**
* @param string|int|float $bytes bytes as string int or pure int
* @return string converted byte number (float) with suffix
*/
function humanReadableByteFormat($bytes): string
{
// if not numeric, return as is
if (is_numeric($bytes)) {
// space before name
$space = true;
// use sprintf instead of round
$adjust = true;
// use SI 1000 mod and not 1024 mod
$si = false;
// si or normal
$unit = $si ? 1000 : 1024;
// always positive
$abs_bytes = $bytes == PHP_INT_MIN ? PHP_INT_MAX : abs($bytes);
// smaller than unit is always B
if ($abs_bytes < $unit) {
return $bytes . 'B';
}
// labels in order of size [Y, Z]
$labels = array('', 'K', 'M', 'G', 'T', 'P', 'E');
// exp position calculation
$exp = floor(log($abs_bytes, $unit));
// avoid printing out anything larger than max labels
if ($exp >= count($labels)) {
$exp = count($labels) - 1;
}
// deviation calculation
$dev = pow($unit, $exp) * ($unit - 0.05);
// shift the exp +1 for on the border units
if (
$exp < 6 &&
$abs_bytes > ($dev - (((int)$dev & 0xfff) == 0xd00 ? 52 : 0))
) {
$exp++;
}
// label name, including leading space if flagged
$pre = ($space ? ' ' : '') . ($labels[$exp] ?? '>E') . ($si ? 'i' : '') . 'B';
$bytes_calc = $abs_bytes / pow($unit, $exp);
if ($adjust) {
return sprintf("%.2f%s", $bytes_calc, $pre);
} else {
return round($bytes_calc, 2) . $pre;
}
} else {
// if anything other return as string
return (string)$bytes;
}
}
$error = false;
$status = 'error';
$error_string = [];
$ajax_data = [];
$folder = DIR . 'uploaded' . DS;
logWrite(print_r($_POST, true));
// backend receiver calls
if (isset($_POST['action'])) {
// action switch statement
switch ($_POST['action']) {
case 'chainAction':
$ajax_data['chain'] = 'Just A chain action: ' . ($_POST['chain'] ?? '-');
$status = 'ok';
errMsg($status, 'Successful chained action');
break;
// list current files
case 'fileList':
$ajax_data['reference_id'] = uniqid();
if (is_dir($folder)) {
$file_array = [];
// sorted by date
$files = glob($folder . DIRECTORY_SEPARATOR . '*');
// oldest top
usort($files, function ($x, $y) {
return filemtime($x) > filemtime($y) ? 1 : -1;
});
foreach ($files as $file) {
$file_array[] = [
'name' => basename($file),
'create_date' => date("Y-m-d H:i:s", filemtime($file)),
'size' => humanReadableByteFormat((int)filesize($file))
];
}
$ajax_data['file_list'] = $file_array;
$status = 'ok';
if (!count($file_array)) {
$status = 'warn';
$ajax_data['info'] = 'No files uploaded yet';
}
} else {
$error = true;
errMsg($status, 'Uploaded folder not found');
}
break;
// for simple single file upload
case 'fileUpload':
// has a target name (file prefix flag)
$upload_name = $_POST['uploadName'];
// $this->debug('FILE UPLOAD', 'ALL FILES: '.$this->printAr($_FILES));
// errMsg('debug', 'Called for: ' . $upload_name . ' with data: '
// . (isset($_FILES[$upload_name]) ? printAr($_FILES[$upload_name]) : 'NOT FOUND'));
// return string & array init
$file_upload_message = [];
// do we have an upload actually
if (
isset($_FILES[$upload_name]['error']) &&
$_FILES[$upload_name]['error'] == UPLOAD_ERR_OK
) {
// set status to success post all checks
$status = 'success';
// strip out -file from upload name for search
$_upload_name = str_replace('-file', '', $upload_name);
$mime_type = null;
$ajax_data = [
'msg' => [],
'file_uid' => null,
'file_url' => null,
'file_name' => null,
'file_size' => null,
'file_size_raw' => null,
'file_type' => null,
];
// check mime type for file and do check if we have a "valid_files" settings
$finfo = new \finfo(FILEINFO_MIME_TYPE);
$mime_type = $finfo->file($_FILES[$upload_name]['tmp_name']);
if (!in_array($mime_type, ['text/csv', 'text/plain', 'text/x-php'])) {
$file_upload_message[] = 'File type must be CSV or PHP: ' . $mime_type;
$ajax_data['msg'] = $file_upload_message;
$ajax_data['error'] = 'File type must be CSV or PHP: ' . $mime_type;
$status = 'error';
}
// ON ERROR set status = 'error';
// file type ok and file size ok, we commence actualy upload
if ($status == 'success') {
// basic file id data
$file_uid = null;
$file_url = null;
$file_name = null;
$file_size = null;
$file_size_raw = null;
$file_upload_message[] = 'File upload successful';
// internal file id
$file_uid = uuidv4();
// move file to tmp location and return new name
move_uploaded_file(
$_FILES[$upload_name]['tmp_name'],
$folder . $file_uid
);
$file_name = $_FILES[$upload_name]['name'];
$file_size = humanReadableByteFormat($_FILES[$upload_name]['size']);
$file_size_raw = $_FILES[$upload_name]['size'];
// correct the image rotation
// $this->correctImageOrientation(BASE.TMP.$file_uid);
// make a copy to layout/cache/images for file url
// as a thumbnail in defined size
// $file_url = $this->form_base_path
// . $this->createThumbnailSimple(BASE.TMP.$file_uid, THUMB_WIDTH, THUMB_HEIGHT);
// write back data for frontend processing
$ajax_data = [
'msg' => $file_upload_message,
'file_uid' => $file_uid,
'file_url' => $file_url,
'file_name' => $file_name,
'file_size' => $file_size,
'file_size_raw' => $file_size_raw,
'file_type' => $mime_type,
];
} else {
$info_msg = 'File uploaded and check failed';
errMsg($status, $info_msg);
$ajax_data['msg'][] = $info_msg;
}
} else {
$info_msg = isset($_FILES[$upload_name]) ?
'File upload filed: ' . fileUploadErrorMessage($_FILES[$upload_name]['error']) :
'General file upload error';
errMsg($status, $info_msg);
$ajax_data = [
'msg' => [
$info_msg
]
];
}
break;
default:
$error = true;
errMsg($status, 'Abnormal action');
break;
}
} else {
$error = true;
errMsg($status, 'No action set');
}
$data = [
'status' => $status,
'msg' => $error_string,
'action' => $_POST['action'] ?? null,
'content' => $ajax_data
];
// print the JSON data out to the browsers
$output = json_encode($data);
print $output;
// __END__

1376
test/edit.jq.js Normal file

File diff suppressed because it is too large Load Diff

1
test/edit.js Symbolic link
View File

@@ -0,0 +1 @@
edit.jq.js

108
test/index.html Normal file
View File

@@ -0,0 +1,108 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>AJAX File upload TEST</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="jquery.min.js" type="text/javascript"></script>
<script defer src="edit.js" type="text/javascript"></script>
<script defer src="other.js" type="text/javascript"></script>
<script defer src="../src/ajaxFileUploadSimple.js" type="text/javascript"></script>
<link rel=stylesheet type="text/css" href="other.css">
</head>
<body>
<form method="post" name="formdata" id="formdata" enctype="multipart/form-data" action="backend.php">
<div id="alerts-div" class="hide"></div>
<div id="container">
<!-- uploader block: START -->
<div id="first_upload-input">
<!--
The following element must be present
<name>-input: MASTER DIV, holds all below
<name>-file-div: upload button
maks-<name>: nested in above div, hold -info and -file (inside <name>-file-div)
<name>-info: Button name + layout block (inside <name>-file-div > mask-<name>)
<name>-file: file upload input (inside <name>-file-div > mask-<name>)
<name>-submit-div: submit input element for submit call
<name>-submit: submit button (inside <name>-submit-div)
<name>-upload-status: div field for progress of upload/info
<name>-clear-div: reset/clear the upload-status list
<name>-clear: button for clear upload queue (inside <name>-clear-div)
-->
<!-- main upload file pointer -->
<div id="first_upload-file-div" class="file-upload mg-30">
<!-- upload label must be present -->
<label id="mask-first_upload" for="first_upload-file">
<div id="first_upload-info">ファイル選ぶ</div>
<!-- main file control container -->
<input id="first_upload-file" name="first_upload-file" type="file" class="file-upload-input" style="display:none;" multiple>
</label>
</div>
<div id="first_upload-submit-div" class="mg-30" style="display:none;">
<!-- the actual upload, must have id set -->
<button type="submit" value="アップロード" id="first_upload-submit" name="first_upload-submit" class="submitBtn">アップロード</button>
</div>
<!-- all status blocks go here -->
<div id="first_upload-upload-status" style="display:none;"></div>
<!-- clear entries from the upload-status list -->
<div id="first_upload-clear-div" class="mg-10" style="display:none;">
<button type="button" value="クリア" id="first_upload-clear" name="first_upload-clear">クリア</button>
</div>
<div id="first_upload-abort-all-div" class="mg-10" style="display:none;">
<button type="button" value="クリア" id="first_upload-abort-all" name="first_upload-abort-all">全部キャンセル</button>
</div>
</div>
<!-- uploader block: END -->
<!-- OTHER UPLOAD BLOCK single -->
<div id="second_upload-input">
<div id="second_upload-file-div" class="mg-30">
<label id="mask-second_upload" for="second_upload-file">
<div id="second_upload-info" style="border: 2px solid red; font-size: 20px; width: 10%; margin: 10px; padding: 10px; text-align: center;">Choose A</div>
<input id="second_upload-file" name="second_upload-file" type="file" style="display:none;" multiple>
</label>
</div>
<div id="second_upload-submit-div" class="mg-30" style="display:none;">
<button type="submit" value="Upload A" id="second_upload-submit" name="second_upload-submit" class="">Upload A</button>
</div>
<div id="second_upload-upload-status" style="display:none;"></div>
<div id="second_upload-clear-div" class="mg-10" style="display:none;">
<button type="button" value="Clear A" id="second_upload-clear" name="second_upload-clear">Clear A</button>
</div>
<div id="second_upload-abort-all-div" class="mg-10" style="display:none;">
<button type="button" value="クリア" id="second_upload-abort-all" name="second_upload-abort-all">Abort all</button>
</div>
</div>
<!-- OTHER UPLOAD BLOCK unlimited -->
<div id="third_upload-input">
<div id="third_upload-file-div" class="mg-30">
<label id="mask-third_upload" for="third_upload-file">
<div id="third_upload-info" style="border: 2px solid red; font-size: 20px; width: 10%; margin: 10px; padding: 10px; text-align: center;">Choose B</div>
<input id="third_upload-file" name="third_upload-file" type="file" style="display:none;" multiple>
</label>
</div>
<div id="third_upload-submit-div" class="mg-30" style="display:none;">
<button type="submit" value="Upload B" id="third_upload-submit" name="third_upload-submit" class="">Upload B</button>
</div>
<div id="third_upload-upload-status" style="display:none;"></div>
<div id="third_upload-clear-div" class="mg-10" style="display:none;">
<button type="button" value="Clear B" id="third_upload-clear" name="third_upload-clear">Clear B</button>
</div>
<div id="third_upload-abort-all-div" class="mg-10" style="display:none;">
<button type="button" value="クリア" id="third_upload-abort-all" name="third_upload-abort-all">Abort all</button>
</div>
</div>
<!-- previously uploaded list -->
<div id="uploaded" class="uploaded">
<div class="title">Previous uploaded</div>
<div id="file-info"></div>
<div id="file-list"></div>
</div>
</div>
<input type="hidden" name="reference-id" id="reference-id" value="">
<div id="indicator" class="hide"></div>
<div id="overlayBox" class="overlayBoxElement hide"></div>
</form>
</body>
</html>

2
test/jquery-3.6.0.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1
test/jquery.min.js vendored Symbolic link
View File

@@ -0,0 +1 @@
jquery-3.6.0.min.js

2
test/log/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*
!.gitignore

66
test/multiple_files_test.html Executable file
View File

@@ -0,0 +1,66 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>AJAX File multiple upload TEST</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="jquery.min.js" type="text/javascript"></script>
<script defer src="edit.js" type="text/javascript"></script>
<!-- <script defer src="other.js" type="text/javascript"></script> -->
<!-- <script defer src="../src/ajaxFileUploadSimple.js" type="text/javascript"></script> -->
<!-- <link rel=stylesheet type="text/css" href="other.css"> -->
<script type="text/Javascript">
$(document).ready(function () {
var fileInput = document.getElementById('file-input');
var fileCatcher = document.getElementById('file-catcher');
var fileListDisplay = document.getElementById('file-list-display');
var fileList = [];
var renderFileList, sendFile;
fileInput.addEventListener('change', function (ev) {
fileList = [];
console.log('Selected files: %s', fileInput.files.length);
for (var i = 0; i < fileInput.files.length; i ++) {
console.log('Push file: %s with %o', i, fileInput.files[i]);
fileList.push(fileInput.files[i]);
}
renderFileList();
});
fileCatcher.addEventListener('submit', function (ev) {
ev.preventDefault();
fileList.forEach(function (file) {
console.log('Send file: %o', file);
sendFile(file);
});
});
renderFileList = function () {
fileListDisplay.innerHTML = '';
fileList.forEach(function (file, index) {
var fileDisplayEl = document.createElement('p');
fileDisplayEl.innerHTML = (index + 1) + ': ' + file.name;
fileListDisplay.appendChild(fileDisplayEl);
});
};
sendFile = function (file) {
var formData = new FormData();
var request = new XMLHttpRequest();
formData.set('file', file);
request.open('POST', 'backend.php');
request.send(formData);
};
});
</script>
</head>
<body>
<form name="file-catcher" id="file-catcher">
<input id="file-input" type="file" multiple/>
<button type="submit">Submit</button>
</form>
<div id='file-list-display'></div>
</body>
</html>

140
test/other.css Normal file
View File

@@ -0,0 +1,140 @@
/*
CSS Other
*/
/* the overlay background black cover */
.overlayBoxElement {
background-color: rgba(0, 0, 0, 0.3);
height: 100%;
left: 0;
position: fixed;
top: 0;
width: 100%;
z-index: 98;
}
/* the progress guruguru */
.progress {
width: 100px;
height: 100px;
background: rgba(255, 255, 255, 0.6);
border: 20px solid rgba(255, 255, 255, 0.25);
border-left-color: rgba(3, 155, 229 ,1);
border-top-color: rgba(3, 155, 229 ,1);
border-radius: 50%;
display: inline-block;
animation: rotate 600ms infinite linear;
/* align */
left: 0;
top: 0;
position: absolute;
z-index: 120;
}
/* Animation for above progress */
@keyframes rotate {
to {
transform: rotate(1turn)
}
}
.tac { text-align: center; }
.mg-30 { margin: 30px; }
.mg-5 { margin: 5px; }
.flx-ss { display: flex; }
.hide { display: none; }
.uploaded { margin-top: 20px; }
.title { font-size: 1.5em; font-weight: bold; margin-bottom: 10px; }
.file-upload label div {
text-align: center;
color: #e60012;
background: #eee;
width: 240px;
height: 60px;
margin: 10px 5px 5px;
line-height: 60px;
border-radius: 5px;
box-shadow: 0 0 3px 2px rgba(0,0,0,.2);
font-size: 137.5%;
font-weight: 600;
transition: .7s;
cursor: pointer;
}
.file-upload label div:hover {
opacity: .7;
}
/* .file-upload-input {
} */
.submitBtn {
text-align: center;
color: #e60012;
background: #eee;
width: 240px;
height: 60px;
margin: 10px 5px 5px;
/* line-height: 60px; */
border-radius: 5px;
box-shadow: 0 0 3px 2px rgba(0,0,0,.2);
font-size: 137.5%;
font-weight: 600;
transition: .7s;
cursor: pointer;
}
.SubError {
color: red;
}
/* all error messages */
.error-warn {
text-align: center;
color: orange;
font-size: 1.1em;
line-height: 1.2em;
}
.error-error {
text-align: center;
color: red;
font-size: 1.1em;
line-height: 1.2em;
}
.error-abort {
text-align: center;
color: #ffffff;
background-color: #ff0000;
font-weight: bold;
font-size: 1.4em;
line-height: 1.5em;
padding: 2px;
margin: 2px 0 2px 0;
}
.error-crash {
text-align: center;
color: #fbffe0;
background-color: #a00000;
font-weight: bold;
font-size: 1.6em;
line-height: 1.7em;
border: 2px solid #000000;
padding: 2px;
margin: 2px 0 2px 0;
}
.error-info, .error-ok {
text-align: center;
color: green;
font-size: 1.1em;
line-height: 1.2em;
}
/* special debug */
.error-debug {
text-align: left;
color: gray;
font-size: 0.9em;
line-height: 1.0em;
}
.afus-uploaded {
margin-top: 10px;
border-top: 1px solid black;
}

382
test/other.js Normal file
View File

@@ -0,0 +1,382 @@
/*
* JAVASCRIPT other
*/
/* jshint esversion: 6 */
/* global initAjaxUploader, keyInObject, errorCatch, showActionIndicator, hideActionIndicator, exists, phfo, aelx, cel */
/**
* clear the alert div and hide it
*/
function clearAlerts() {
$('#alerts-div').html('').hide();
}
/**
* prints a single message line
* @param {String} msg message text
* @param {String} level level as style sheet name for highlight
*/
function printMsgStr(msg, level) {
printMsg([{ level: level, str: msg }]);
}
/**
* prints master error message in case of master error
* @param {Object} msg messages as hash object
*/
function printMsg(msg) {
var content = [];
for (const t of msg) {
// console.log('v: %s, t: %o', v, t);
if (keyInObject('code', t) && t.code != null && t.code.length > 0) {
t.str = '[' + t.code + '] ' + t.str;
}
content.push(phfo(cel('div', '', t.str, ['error-' + t.level])));
}
$('#alerts-div')
.html(content.join(''))
.show();
// calcHeightTopHeader();
}
/**
* wrapper for ajax calls
* This will do a beforeSend call
* Success generall call (before .done)
* error call for general error catch (can be outside extended with .fail)
* complete call to hide the action indicator
* note: if pre-function is small we might want to put indicator call in this function
* @param {String} call_id Caller id for debug and action indicator debug
* @param {Object} queryString Query string to pass on, default empty
* @param {Object} control Control for action indicator and others
* no_action_indicator: true/false/empty, if true do not use action indicator
* @param {String} url Target url, defaults to generalBackend.php
* @return {jqXHR} JQuery XJR Object for .done, .fail, etc connection calls
*/
function ajaxWrapper(call_id, queryString = {}, control = {}, url = 'backend.php') {
var no_action_indicator = false;
if (keyInObject('no_action_indicator', control)) {
no_action_indicator = control.no_action_indicator ? true : false;
}
// if inidicator not visible, show before
if (!no_action_indicator) {
showActionIndicator(call_id);
}
// general ajax wrapper
// AJAX call
return $.ajax({
url: url,
type: 'POST',
data: queryString,
// genera; before Sending
beforeSend: function () {
console.log('MAIN RUN: ' + call_id);
},
success: function (data, status, xhr) {
// additional success call
console.log('[' + call_id + '] Return with [%s] as status: %s, xhr: %s, action: %s', status, data.status, xhr.status, data.action);
},
// general error
error: function (xhr) {
console.log('[' + call_id + '] An error occured: %s %s', xhr.status, xhr.statusText);
// critical error
printMsgStr(call_id + ' Error', 'crash');
},
complete: function () {
console.log('[' + call_id + '] Complete');
if (!no_action_indicator) {
hideActionIndicator(call_id);
}
}
});
}
/**
* FILE CHANGE:
* helper call for ajax file upload on file selected
* @param {Number} file_pos Position in upload queue
* @param {String} target_file The file upload target prefix id
*/
function fileChangeFunction(target_file, file_pos, target_router)
{
console.log('{FILE} CHANGE [%s/%s] FUNCTION CALL [%s]', target_file, file_pos, target_router);
clearAlerts();
// console.log('Upload Status: %s', $('#' + target_file + '-upload-status').outerHeight());
}
/**
* [fileChangeFunctionAll description]
* @param {String} target_file [description]
* @param {String} target_router [description]
*/
function fileChangeFunctionAll(target_file, target_router)
{
console.log('{FILE} CHANGE ALL [%s] FUNCTION CALL [%s]', target_file, target_router);
clearAlerts();
// console.log('Upload Status: %s', $('#' + target_file + '-upload-status').outerHeight());
}
/**
* [fileRemoveFunction description]
* @param {string} target_file [description]
* @param {Number} file_pos [description]
* @param {String} target_router [description]
*c
*/
function fileRemoveFunction(target_file, file_pos, target_router)
{
console.log('{FILE} REMOVE [%s/%s] FUNCTION CALL [%s]', target_file, file_pos, target_router);
// console.log('Upload Status: %s', $('#' + target_file + '-upload-status').outerHeight());
// clearAlerts();
}
/**
* [fileClearFunction description]
* @param {String} target_file [description]
* @param {String} target_router [description]
*/
function fileClearFunction(target_file, target_router)
{
console.log('{FILE} CLEAR [%s] FUNCTION CALL [%s]', target_file, target_router);
// console.log('Upload Status: %s', $('#' + target_file + '-upload-status').outerHeight());
// clearAlerts();
}
/**
* [fileAppendBeforeUploadFunctionAllFunction description]
* @param {String} target_file [description]
* @param {String} target_router [description]
* @return {Object} [description]
*/
function fileAppendBeforeUploadFunctionAllFunction(target_file, target_router)
{
console.log('{FILE} BEFORE UPLOAD ALL [%s] FUNCTION CALL [%s]', target_file, target_router);
return {
'primary_key': $('#reference-id').val(),
'same-entry': 'A',
'target-files': target_file,
'target-router': target_router
};
}
/**
* FILE BEFORE UPLOAD:
* attach additional data to be sent to the server
* This is called before the data is submitted to the server
* @param {String} target_file The file upload target prefix id
* @param {Number} file_pos Position in upload queue
* @param {String} target_router
* @return {Object} key -> value object to be attached to the form
*/
function fileAppendBeforeUploadFunction(target_file, file_pos, target_router)
{
console.log('{FILE} BEFORE UPLOAD [%s/%s] FUNCTION CALL [%s]', target_file, file_pos, target_router);
return {
'file_data_count': $('#file-list').children().length,
'same-entry': 'B',
'target-files': target_file,
'file-pos': file_pos,
'target-router': target_router
};
}
/**
* FILE ULOAD FINISHED:
* helper call for ajax file upload on upload completed
* set the uploaded file list (hidden & visible), shows import button,
* adjusts hight of action box
* @param {String} target_file The file upload target prefix id
* @param {Object} control_data The data returned from the ajax call after upload
*/
function fileUploadedFunction(target_file, file_pos, target_router, control_data)
{
console.log('{FILE} UPLAODED [%s/%s] FUNCTION CALL [%s]: %o', target_file, file_pos, target_router, control_data);
var file_id = target_file;
var file_entry_exists = false;
// add new uploaded file entry to the push list
// first find if there is an entry for this file yet (-file-name)
try {
console.log('Uploaded file UID: %s', $('#' + file_id + '-uid-' + file_pos).val());
if (exists(file_id + '-uid-' + file_pos) && $('#' + file_id + '-uid-' + file_pos).val()) {
// hide file info first
$('#file-info').html('').hide();
// add new uplaoded files
$('#file-list').append(phfo(
aelx(cel('div', '', '', ['flx-ss']),
cel('div', '', control_data.file_name, ['mg-5']),
cel('div', '', '(NOW)', ['mg-5']),
cel('div', '', control_data.file_size, ['mg-5']),
cel('input', file_id + '-uploaded-' + file_pos, '', [], {type: 'hidden', value: control_data.file_uid})
)
));
// DOUBLE entry checker used in FormControl control file upload
// console.log('MAP: %o', $('#file-list :input'));
file_entry_exists = Object.values($('#file-list :input').map(function() {
// console.log('Matching: %s - %s', control_data.file_uid, $(this).val());
return control_data.file_uid == $(this).val() ? true : false;
})).find(value => value === true);
console.log('Matched: %s', file_entry_exists);
if (!file_entry_exists) {
// add hidden and visible elment
// addControlFileElement(file_id, $('#' + file_id + '-file_name').val(), control_data.other_data.status_text);
}
}
} catch(err) {
errorCatch(err);
}
// chain action test
var call_id = 'chainAction';
var queryString = {
action: call_id,
chain: control_data.file_uid
};
ajaxWrapper(call_id, queryString).done(function (data) {
console.log('Data: %o', data);
try {
if (data.status == 'error') {
//
} else {
// list files
if (data.status == 'warn') {
//
} else {
//
}
}
// top message
printMsg(data.msg);
} catch (err) {
errorCatch(err);
}
});
}
/**
* [fileUploadedAllFunction description]
* @param {String} target_file [description]
* @param {Number} target_router [description]
* @param {Boolean} all_success If true all files where accepted by the servers
*/
function fileUploadedAllFunction(target_file, target_router, all_success)
{
console.log('{FILE} UPLAODED ALL [%s] FUNCTION CALL [%s]: %o', target_file, target_router, all_success);
}
/**
* [fileUploadErrorFunction description]
* @param {String} target_file [description]
* @param {Number} file_pos [description]
* @param {String} target_router [description]
* @param {Object} control_data [description]
*/
function fileUploadErrorFunction(target_file, file_pos, target_router, control_data)
{
console.log('{FILE} UPLOAD ERROR [%s/%s] FUNCTION CALL [%s]: %o', target_file, file_pos, target_router, control_data);
// eg print special error data from control
}
// ** init here **/file-list
$(document).ready(function () {
// run and fill uploaded
var call_id = 'fileList';
var queryString = {
action: call_id
};
ajaxWrapper(call_id, queryString).done(function (data) {
console.log('Data: %o', data);
try {
if (data.status == 'error') {
//
} else {
$('#reference-id').val(data.content.reference_id);
// list files
if (data.status == 'warn') {
$('#file-info')
.addClass('error-warn')
.html(data.content.info);
} else {
$('#file-info').html('');
for (const files of data.content.file_list) {
// console.log('F: %o', files);
$('#file-list').append(phfo(
aelx(cel('div', '', '', ['flx-ss']),
cel('div', '', files.name, ['mg-5']),
cel('div', '', files.create_date, ['mg-5']),
cel('div', '', files.size, ['mg-5']),
cel('input', 'uploaded-' + files.name, '', [], {type: 'hidden', value: files.name})
)
));
}
}
}
// top message
printMsg(data.msg);
} catch (err) {
errorCatch(err);
}
});
// check that html elements needed are there
// fill the file upload part
initAjaxUploader({
target_file: 'first_upload',
target_form: 'formdata',
max_files: 5,
target_router: 'fileUpload',
target_action: '',
form_parameters: {'parameter_a': 'Value 123'},
auto_submit: false,
fileChange: fileChangeFunction,
fileChangeAll: fileChangeFunctionAll,
fileRemove: fileRemoveFunction,
fileClear: fileClearFunction,
fileBeforeUploadAll: fileAppendBeforeUploadFunctionAllFunction,
fileBeforeUpload: fileAppendBeforeUploadFunction,
fileUploaded: fileUploadedFunction,
fileUploadedAll: fileUploadedAllFunction,
fileUploadError: fileUploadErrorFunction
});
// for second test
initAjaxUploader({
target_file: 'second_upload',
target_form: 'formdata',
max_files: 1,
max_file_size: 500000, // ~500KB
// allowed_extensions: ['txt', 'csv'],
allowed_file_types: ['text/plain', 'text/csv'],
target_router: 'fileUpload',
target_action: '',
form_parameters: {'parameter_b': 'Value 456'},
auto_submit: false,
fileChange: fileChangeFunction,
fileChangeAll: fileChangeFunctionAll,
fileRemove: fileRemoveFunction,
fileClear: fileClearFunction,
fileBeforeUploadAll: fileAppendBeforeUploadFunctionAllFunction,
fileBeforeUpload: fileAppendBeforeUploadFunction,
fileUploaded: fileUploadedFunction,
fileUploadedAll: fileUploadedAllFunction,
fileUploadError: fileUploadErrorFunction
});
// for third test
initAjaxUploader({
target_file: 'third_upload',
target_form: 'formdata',
max_files: 0,
target_router: 'fileUpload',
target_action: '',
form_parameters: {'parameter_c': 'Value 789'},
auto_submit: false,
fileChange: fileChangeFunction,
fileChangeAll: fileChangeFunctionAll,
fileRemove: fileRemoveFunction,
fileClear: fileClearFunction,
fileBeforeUploadAll: fileAppendBeforeUploadFunctionAllFunction,
fileBeforeUpload: fileAppendBeforeUploadFunction,
fileUploaded: fileUploadedFunction,
fileUploadedAll: fileUploadedAllFunction,
fileUploadError: fileUploadErrorFunction
});
});

2
test/uploaded/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*
!.gitignore