Files Uploader
Documentation, 28 June 2019
Coded by: Szymon Fox (Trzebu)
Documented by: Szymon Fox (Trzebu)
Version: 1.0
Licence
MIT License
Copyright (c) 2019 Szymon Fox
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
About
Files uploader is a very light and fast HTML client to uploading files in your web site. You can configure what can be uploaded, how much files can be uploaded or you can set maximum files size. Simple code construction allows you to fast modernize and implementate this client in your web stie.Download
You can download client source from my github or from this link.Installation
CSS
Copy-paste the stylesheet link into your head tag.<link rel="stylesheet" href="/src/css/files-uploader.css">
JS
Files uploader require the use of JavaScript to function. Copy-paste the scripts link into your head tag.<script src="/src/js/files-uploader.js"></script>
HTML
Copy the HTML code below and paste the code in the most convenient place for you.<div id="fu-container">
<div class="fu-buttons">
<div class="fu-upload-button-selector">
<label for="fuFiles">
<i class="ico images-folder-i"></i> Select Files
</label>
<input title="Select Files" multiple type="file" name="fuFiles" id="fuFiles">
</div>
<button id="fuManualUploadTrigger">
<i class="ico upload-i"></i> Upload
</button>
</div>
<div class="fu-drop-text">Drop files here</div>
<div id="fu-upload-progressbar">
<div>
<div id="fu-upload-progress"></div>
<span>21MB / 37MB</span>
</div>
</div>
<div class="fu-height-limiter">
<ul id="fuFilesList"></ul>
</div>
</div>
Configuration
The following are all configuration constants:var FU_CONFIG = {
"MANUAL_UPLOAD_TRIGGER": false,
"MAX_FILES": 0,
"MAX_FILE_SIZE": 0,
"MAX_FILES_SIZE": 0,
"MIN_FILE_SIZE": 0,
"ALLOWED_EXTENSIONS": [],
"UNALLOWED_EXTENSIONS": [],
"IMAGE_MAX_WIDTH": 0,
"IMAGE_MIN_WIDTH": 0,
"IMAGE_MAX_HEIGHT": 0,
"IMAGE_MIN_HEIGHT": 0,
"SERVER_API": {
"UPLOAD": "",
"DELETE": "",
"EDIT": "",
"PREVIEW": ""
}
};
Server links
Below you can see how configuration constants must be settled so that the client works properly.Uploading
FU_CONFIG["SERVER_API"]["UPLOAD"] = "link/to/your/upload/route";
Deleting*
FU_CONFIG["SERVER_API"]["DELETE"] = "link/to/your/delete/route";
Edit*
FU_CONFIG["SERVER_API"]["EDIT"] = "link/to/your/edit/route";
Previewing*
FU_CONFIG["SERVER_API"]["PREVIEW"] = "link/to/your/uploaded/route";
*these values are not required for basic client operation.
Manual upload
If you want to controll start of the uploading manual you can do it by settled configuration constant to true like this:FU_CONFIG["MANUAL_UPLOAD_TRIGGER"] = true;
Files validation
My files uploader have initial validation functionality. Initially all validation rules are disabled (when value is set to 0 or to empty string or to empty array it's means that rule is disabled).Maximum files amount
Here you can define how many files you can send at once.FU_CONFIG["MAX_FILES"] = 5; // Now files limit is 5
Maximum single file size
If you want to limit single file size you can do it here:FU_CONFIG["MAX_FILE_SIZE"] = 1024; // Now size limit is 1KB.
FU_CONFIG["MIN_FILE_SIZE"] = 500; // Even minimum file size limit is available to set.
Maximum files size
If you want to set the total size limit of all files you can do it here:FU_CONFIG["MAX_FILES_SIZE"] = 10240; // Now size limit is 10KB.
Allowed extensions / unallowed extensions
Here you can define extensions allowed in files validation.FU_CONFIG["ALLOWED_EXTENSIONS"] = ["jpg", "png", "bmp", "gif"]; // Only files with extensions on this list will be allowed to upload.
FU_CONFIG["UNALLOWED_EXTENSIONS"] = ["png"]; // Now all extensions are available but png will be rejected.
Images limits
Even you can limit images.FU_CONFIG["IMAGE_MAX_WIDTH"] = 200; // Maximum images width is 200px.
FU_CONFIG["IMAGE_MIN_WIDTH"] = 50; // Minimum images width is 50px.
FU_CONFIG["IMAGE_MAX_HEIGHT"] = 300; // Maximum images height is 300px.
FU_CONFIG["IMAGE_MIN_HEIGHT"] = 50; // Minimum images height is 50px.
Client translation
I use array with messages to all client errors, alerts, etc. So this give you very simple way to translate "files uploader" client to language what you use in your web site.Example translation:
MESSAGES["CANCEL"] = "Cancel" // Default EN translation.
MESSAGES["CANCEL"] = "Anuluj"; // Translated to PL.
MESSAGES["CANCEL"] = "Stornieren"; // Translated to DE.
Multi language page
By default, this script supports translations for one language only, but you can easily change it.
Example to multi language pages:
var TRANSLATIONS = { // First create object with all transaltions.
"en": { // Default EN translations.
"TYPE_ERROR": "This file has wrong extension. Correct extensions: {EXTENSIONS}.",
"NO_FILES_ERROR": "No files to save."
},
"de": { // For a new language, create a new object with a key called the language shortcut.
"TYPE_ERROR": "Diese Datei hat die falsche Erweiterung. Richtige Erweiterung ist: {EXTENSIONS}.",
"NO_FILES_ERROR": "Keine zu speichernden Dateien."
},
"pl": { // For a new language, create a new object with a key called the language shortcut.
"TYPE_ERROR": "Ten plik ma złe rozszerzenie. Poprawne rozszerzenia to: {EXTENSIONS}.",
"NO_FILES_ERROR": "Brak plików do zapisu."
}
}
var userLanguage = document.querySelector("html").getAttribute("lang"); // Even you can get this value from cookie or something like this.
userLanguage = Object.keys(TRANSLATIONS).includes(userLanguage) ? userLanguage : "en";
var MESSAGES = TRANSLATIONS[userLanguage]; // Done. Now script will be use appropriate translation to each user.
Own requests headers
If you have server side csrf validation or something like this you can create own header with value required in reqeust.var FU_CONFIG = {
...
"REQUEST_HEADER": {
"X-CSRF-TOKEN": "csrf-private-token",
"TEST-HEADER-NAME": "TEST_HEADER_VALUE"
}
};
Now every request will be sended with these values.
Own alert function
Normally a script use ugly alert functionality from your web browser. When you have own script to alerting something in your web page you can use code below to change what function will display alerts. (Near 686 line)function fuAlert (message) {
myOwnAlertFunction(message);
}
Server
Responses
All responses sended by server should be in JSON format. All responses should have "status" value. without this, the client can not check the correctness of the response and status will be automaticly set at 6.Good response:
{
"status": 6
}
Bad response:
{
"statusCode": 2137
}
Explanation of file status
File status | Explanation |
---|---|
0 | The file is being prepared for upload. |
1 | File is ready to upload. |
2 | The file has not passed the validation. (Client side validation error) |
3 | File is uploading. |
4 | Server error while upload. (For example when server responde with 500 status code) |
5 | Error in server side validation. (The file has not passed the validation in your server validation) |
6 | File uploaded correctly. |
7 | File removed correctly. |
8 | Inside uploader error. |
9 | File edit correctly. |
10 | File is uploaded, but waiting to upload other files from current queue. |
Examples
Some simple server implementations in PHP for files uploader client.
IMPORTANT NOTE:
These are very simple examples, they can contain numerous security errors! They should be treated only as an overview.
Uploading
if (empty($_FILES["file"])) {
echo(json_encode([
"status" => 5,
"errorMsg" => "No file selected."
]));
return;
}
$path = pathinfo($_FILES["file"]["name"]);
$ext = $path['extension'];
$newFileName = bin2hex(random_bytes(15));
move_uploaded_file($_FILES['file']['tmp_name'], __dir__ . "/uploaded/" . $newFileName . "." . $ext);
$preparedPDO = $pdo->prepare("INSERT INTO files (name, path) VALUES (:name, :path)");
$preparedPDO->execute([
":name" => $newFileName,
":path" => $newFileName . "." . $ext
]);
echo(json_encode([
"status" => 6,
"newId" => $pdo->lastInsertId(),
"newName" => $newFileName . "." . $ext
]));
First I check if "file" post data exists. As you can se when $_FILES not containt "file" data I responde with status 5 and i sending message with information what happens wrong.
Every time when you validate file and there is a problem you must send status with 5 code (Error in server side validation) and error message in "errorMsg" variable.
if (empty($_FILES["file"])) {
echo(json_encode([
"status" => 5,
"errorMsg" => "No file selected."
]));
return;
}
Example error response:
{
"status": 5,
"errorMsg": "You'r file is to large! Maximum file size is 1KB."
}
Next I'm getting file name, file extension and I generate random bytes to create unique file name. (This is not good way)
$path = pathinfo($_FILES["file"]["name"]);
$ext = $path['extension'];
$newFileName = bin2hex(random_bytes(15));
Now I upload file with new name to "uploaded" directory.
move_uploaded_file($_FILES['file']['tmp_name'], __dir__ . "/uploaded/" . $newFileName . "." . $ext);
Here i save some information about file to Data Base because it will help me later.
$preparedPDO = $pdo->prepare("INSERT INTO files (name, path) VALUES (:name, :path)");
$preparedPDO->execute([
":name" => $newFileName,
":path" => $newFileName . "." . $ext
]);
And finally we can send response with information that all it's OK.
echo(json_encode([
"status" => 6,
"newId" => $pdo->lastInsertId(),
"newName" => $newFileName . "." . $ext
]));
Of course if you not save information to DB and not change file name you can send response in simplest form:
{
"status": 6
}
Deleting
if (empty($_POST["fileId"])) {
echo(json_encode([
"status" => 5,
"errorMsg" => "File ID you are searching for dose not exists."
]));
return;
}
$selectPDO = $pdo->prepare("SELECT id, path FROM files WHERE id = ?");
$selectPDO->bindValue(1, $_POST["fileId"]);
$selectPDO->execute();
$results = $selectPDO->fetchAll(PDO::FETCH_OBJ);
if (count($results) === 0) {
echo(json_encode([
"status" => 5,
"errorMsg" => "File ID you are searching for dose not exists."
]));
return;
}
$deletePDO = $pdo->prepare("DELETE FROM files WHERE id = ?");
$deletePDO->bindValue(1, $results[0]->id);
$deletePDO->execute();
unlink(__dir__ . "/uploaded/" . $results[0]->path);
echo(json_encode([
"status" => 7
]));
I think that i don't need to explain it. Just like when sending files, I check if the file's ID has been sent and if it exists in the database.
When file is removed correctly you must send response with status code 7
echo(json_encode([
"status" => 7
]));
File name edit
if (empty($_POST["newFileName"]) || empty($_POST["fileId"])) {
echo(json_encode([
"status" => 5,
"errorMsg" => "You must type new file name!"
]));
return;
}
$selectPDO = $pdo->prepare("SELECT id, path FROM files WHERE id = ?");
$selectPDO->bindValue(1, $_POST["fileId"]);
$selectPDO->execute();
$results = $selectPDO->fetchAll(PDO::FETCH_OBJ);
if (count($results) === 0) {
echo(json_encode([
"status" => 5,
"errorMsg" => "File ID you are searching for dose not exists."
]));
return;
}
$ext = pathinfo($results[0]->path)["extension"];
$newFileName = $_POST["newFileName"] . "." . $ext;
$uploadedPath = __dir__ . "/uploaded/";
rename($uploadedPath . $results[0]->path, $uploadedPath . $newFileName);
$editPDO = $pdo->prepare("UPDATE files SET name = ?, path = ? WHERE id = ?");
$editPDO->bindValue(1, $_POST["newFileName"]);
$editPDO->bindValue(2, $newFileName);
$editPDO->bindValue(3, $_POST["fileId"]);
$editPDO->execute();
echo(json_encode([
"status" => 9,
"newFileName" => $newFileName
]));
Some validation before i change file name:
if (empty($_POST["newFileName"]) || empty($_POST["fileId"])) {
echo(json_encode([
"status" => 5,
"errorMsg" => "You must type new file name!"
]));
return;
}
$selectPDO = $pdo->prepare("SELECT id, path FROM files WHERE id = ?");
$selectPDO->bindValue(1, $_POST["fileId"]);
$selectPDO->execute();
$results = $selectPDO->fetchAll(PDO::FETCH_OBJ);
if (count($results) === 0) {
echo(json_encode([
"status" => 5,
"errorMsg" => "File ID you are searching for dose not exists."
]));
return;
}
Next I'm getting new name and create new file path:
$ext = pathinfo($results[0]->path)["extension"];
$newFileName = $_POST["newFileName"] . "." . $ext;
$uploadedPath = __dir__ . "/uploaded/";
Here I change file name:
rename($uploadedPath . $results[0]->path, $uploadedPath . $newFileName);
Save it to DB:
$editPDO = $pdo->prepare("UPDATE files SET name = ?, path = ? WHERE id = ?");
$editPDO->bindValue(1, $_POST["newFileName"]);
$editPDO->bindValue(2, $newFileName);
$editPDO->bindValue(3, $_POST["fileId"]);
$editPDO->execute();
And finally send response with status code 9 and new file name:
echo(json_encode([
"status" => 9,
"newFileName" => $newFileName
]));
Client requests keys explanation
Files uploader script use some values in requests to working.Key | Data type | Sended by | Description |
---|---|---|---|
file | File | File uplading method | It is a file selected for upload by the client |
newFileName | string | Edit name method | It is a new name sended by user. |
fileId | number | Edit name / delete method | It is a new ID responded by the server. |
Server response keys explanation
Key | * | Data type | Sended after | Description |
---|---|---|---|---|
status | number | Everywhere | It is operation status. See Explanation of file status | |
errorMsg | * | string | Everywhere | Error message displayed in file status when something wrong happens in server validation. |
newId | * | number | Upload | It is a new ID generated by the server. It will be used in file deleting and in rename. |
newName | * | string | Upload | It is a new name generated by the server. |
newFileName | * | string | Edit | It is a new name given by user. |
Validator
My script have own validation method used to validate files before they are send to server. As this is a preliminary validation of the data, it is absolutely necessary to make sure that the files are correct on the server side.Own validation methods
Below I'm try to explain you how to create new validation method. In this example I create method to checking if file has the appropriate length of the name.
First what we must do is it create new constant in FU_CONFIG.
var FU_CONFIG = {
..., // Other configurations
"MAX_FILE_NAME_LENGTH": 5 // Your key name and key value.
}
Now you must add new error message to MESSAGES array.
MESSAGES = {
..., // Other messages
"MAX_FILE_NAME_LENGTH_ERROR": "This file has too long a name! The maximum length is {MAX_FILE_NAME_LENGTH}."
}
Next you must find fuValidator object. (Near 173 line) In this object you can see variable named this.validationMethods = {...}. In this variable we must add new key with your configuration name key and new validation method name. Like below:
this.validationMethods = {
"MAX_FILE_SIZE": "maxFileSize",
...,
"MAX_FILE_NAME_LENGTH": "maxFileNameLength" // This is new validator method name.
}
And finally below this.validation method you can create your new validator.
this.validation = function () {}
this.maxFileNameLength = function () {
if (this.file.name > FU_CONFIG["MAX_FILE_NAME_LENGTH"]) {
this.errors.push(MESSAGES["MAX_FILE_NAME_LENGTH_ERROR"].replace(
"{MAX_FILE_NAME_LENGTH}",
FU_CONFIG["MAX_FILE_NAME_LENGTH"]
));
}
}
... // Other validators
Now any new selected file can not have a name longer than 5 characters.
File object
This is the main object of the entire program. He is responsible for describing each file and has all the functions needed for processing files.Variables
Name | Data type | Description |
---|---|---|
fileData | File | developer.mozilla.org/en-US/docs/Web/API/File |
imgObj | undefined / img | developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement (Enabled only when file is detected as image) |
id | number | Unique ID given by FileUploader object to be recognized in files list. |
totalBytesSended | number | Using to checking upload progress. Its initialized when file is sending to server. |
request | undefined / XMLHttpRequest | developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest |
newFileId | number | New file ID given by server after request. It is used only to other request (edit/delete). Client still uses id. |
newFileName | string | New file name given by user after edit. |
status | number | File status. See file statuses. |
Methods
Name | Arguments | Return | Description |
---|---|---|---|
ajax | method, url, data | Promise | This is method to send flex request. |
showFileInList | - | Void | This prepare list element to display it in list. |
cancelUploadingActivate | - | Void | Activate cancel uploading button. |
remove | - | Void | Remove file from list and from files array. |
abortUploadingRequest | - | Void | When a file is transferred, this method stops sending it. |
validate | - | Void | Checking whether file is valid. |
changeToError | info | Void | Chenging displayed file to error status and show given message. |
changeToSuccess | - | Void | Chenging displayed file to success status. |
loadThumbnail | - | Promise | Checking whether file is image and displays the appropriate thumbnail. |
isImage | - | boolean | Checking whether file is image. |
upload | - | Void | Upload file to the server. |
changeFileName | newName | Void | Changing file name in file list. |
fileControlls | - | Void | Changing whether delete and edit route is set and display controll buttons. |
editNameActivate | - | Void | Activate edit button. |
editNameSave | - | Void | Send new file name to server. |
editNameCancel | - | Void | Cancel edit and show other controll buttons. |
delete | - | Void | Send delete request and if it response with success remove file from list. |
getShortName | fileName (optional) | string | Cute given name to LIMIT and return it without ext. |
getFullName | fileName (optional) | string | Return full file name without ext. |
getExt | - | string | Return file ext. |
selector | query (optional) | Element | Return html element of file list. |