Showing posts with label 2023-december. Show all posts
Showing posts with label 2023-december. Show all posts

Sunday, December 17, 2023

Using JavaScript to Upload Large Files in Chunks as Parts and Avoid Server Limits

We know that PHP config files has the limit to upload files to server, you know that uploading large files can be a real pain. You have to find the loaded php.ini file, edit the upload_max_filesize and post_max_size settings, and hope that you never have to change servers and do all of this over again.
We will do some trick to upload very very large file to server without manipulate any configuration, does not matter how small the limit is. We actually will send files from browser chunk by chunk. Chunk can be as small as our demand. Suppose if our server accept max 1mb request limit, we will send 1mb per request and many more.
This will include two parts (1) is to send files chunk by chunk from browser to server and the other part (2) is save file parts into single file. I will use PHP example at backend to save file into server.
So below is Javascript parts with description:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>File Upload Chunk</title>
</head>
<body>
    <div class="form">
        <input type="file" id="file"/>
        <button type="button" id="upload">Upload</button>
    </div>
    <div class="toast">
        <div id="toast"></div>
    </div>
    <script type="text/javascript">
        document.getElementById("upload").addEventListener("click", () => {
            let files = document.getElementById("file").files;
            if (files.length === 0) {
                alert("File required");
            }
            else {
                showToast("Uploading");
                doUploadFileChunk(files[0], 1);
            }
        });

        function doUploadFileChunk(file, chunk) {
            let chunkLimit = 1024 * 50; // Limit of file chunk send to server is 50 KB for test purpose

            // Blob from position
            let fromSlice = (chunk - 1) * chunkLimit;
            // Blob to position
            let nextSlice = fromSlice + chunkLimit - 1;
            // new FormData()
            let formData = new FormData();
            formData.append("file_number", chunk);
            formData.append("file_name", file.name);
            formData.append("file_part", file.slice(fromSlice, nextSlice + 1)); // Processing small part of file

            console.log(`Sending from ${fromSlice} to ${nextSlice} KB`);

            // Sending to server
            let xhr = new XMLHttpRequest();
            xhr.open("POST", "upload.php", true);
            xhr.setRequestHeader("Accept", "application/json");
            xhr.onreadystatechange = () => {
                if (xhr.readyState === 4) {
                    console.log("Status = " + xhr.status);
                    try {
                        let response = JSON.parse(xhr.responseText);
                        console.log(response);
                        if (response.uploaded) {
                            let percent = Math.floor((nextSlice / file.size) * 100);
                            if (nextSlice < file.size) {
                                showToast(`Uploading file - ${percent}% completed`);
                                setTimeout(() => {
                                    doUploadFileChunk(file, chunk + 1);
                                }, 200);
                            }
                            else {
                                showToast(`Upload completed, processing started`);
                            }
                        }
                        else {
                            alert("Failed to upload file");
                        }
                    }
                    catch (e) {
                        console.log("Complete = " + xhr.responseText);
                        console.log(e);
                        alert("Failed to upload file");
                    }
                }
            };
            xhr.send(formData);
        }

        function showToast(text) {
            document.getElementById("toast").innerHTML = text;
        }
    </script>
    <style type="text/css">
        div.form {
            padding: 20px;
        }
        div.toast {
            padding: 20px;
            background-color: green;
            color: white;
            font-size: 31px;
        }
    </style>
</body>
</html>
And respective PHP file is as below:
<?php
$output = ["uploaded" => true, "file" => $_FILES["file_part"]];

$uploadFile = sprintf("storage/%s", $_POST['file_name']);
if ($_POST["file_number"] == 1) {
    if (file_exists($uploadFile)) {
        unlink($uploadFile);
    }
}

$destination = fopen($uploadFile, "a+");
if (FALSE === $destination) die("Failed to open destination");

$handle = fopen($_FILES["file_part"]["tmp_name"], "rb");
if (FALSE === $handle) die("Failed to open blob");

$BUFFER_SIZE=1*1024*1024; // 1MB, bigger is faster
while( !feof($handle) ) {
    fwrite($destination, fread($handle, $BUFFER_SIZE) );
}
fclose($handle);
fclose($destination);

echo json_encode($output);

Wednesday, December 13, 2023

Auto Grow a Textarea with Javascript | How to create auto-resize textarea using JavaScript

The idea was to make a <textarea> more like a <div> so it expands in height as much as it needs to in order to contain the current value. It’s almost weird there isn’t a simple native solution for this
The trick is that you exactly replicate the content of the <textarea> in an element that can auto expand height, and match its sizing.

Instead, you exactly replicate the look, content, and position of the element in another element. You hide the replica visually (might as well leave the one that’s technically-functional visible).
You need to make sure the replicated element is exactly the same

Same font, same padding, same margin, same border… everything. It’s an identical copy, just visually hidden with visibility: hidden;. If it’s not exactly the same, everything won’t grow together exactly right.

We also need white-space: pre-wrap; on the replicated text because that is how textareas behave.
Example is as below:

HTML Part

<div class="grow-wrap">
    <textarea name="text" id="text"></textarea>
</div>

CSS Part

/* For grow textarea */
.grow-wrap {
    /* easy way to plop the elements on top of each other and have them both sized based on the tallest one's height */
    display: grid;
    width: 100%;
}

.grow-wrap::after {
    /* Note the weird space! Needed to preventy jumpy behavior */
    content: attr(data-replicated-value) " ";
    /* This is how textarea text behaves */
    white-space: pre-wrap;
    /* Hidden from view, clicks, and screen readers */
    visibility: hidden;
}

.grow-wrap>textarea {
    /* You could leave this, but after a user resizes, then it ruins the auto sizing */
    resize: none;
    /* Firefox shows scrollbar on growth, you can hide like this. */
    overflow: hidden;
}

.grow-wrap>textarea,
.grow-wrap::after {
    font: inherit;
    grid-area: 1 / 1 / 2 / 2;
    margin: 0 !important;
    padding-top: 10px;
    padding-bottom: 10px;
}

JavaScript Part

const growers:any = document.querySelectorAll(".grow-wrap");
growers.forEach((grower:any) => {
    const textarea = grower.querySelector("textarea");
    textarea.addEventListener("input", () => {
        grower.dataset.replicatedValue = textarea.value;
    });
});

Live example is as below: