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": "" } }; 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.
*Optional keys. Other keys are required in every response.

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.