0

I'm trying to add a multiple image up-loader which returns a JavaScript array that is populated by the user selecting images. I will be using this image up-loader with other input form elements which will all submit together, in this case i'm not using AJAX. The maximum file upload size is 2MB. The JavaScript array contains the base 64 encoded image with all the details including size, type etc. I have used $('#j_son').val(JSON.stringify(AttachmentArray)); outside the array to populate the hidden input field everytime the array is populated (input unhidden to show JSON string). Lightweight Multiple File Upload with Thumbnail Preview using HTML5 and Client Side Scripts

enter image description here

On submit I will use PHP to decode the new JSON string and and put the multiple images in a folder called uploads.

The problem I am facing is that selecting image attachments larger than 200 KB seem to slow the output of images inside the div container and the JSON string inside the hidden input field and anything to large will cause "aw snap" error inside chrome and crash the browser, I don't know where I'm going wrong. I also have a click event that when the user clicks X remove button and the hidden input field is repopulated with the updated JSON array, this to is really slow and crashes if the files are to large. The PHP side of things has no problem decoding the JSON string it seems to either fall on the JavaScript code or the extra functionality I have added at the bottom of the script. Is there a way to stop this from happening? I have added the full code including the PHP if anybody wants to test it.

<!DOCTYPE html> 
<html>
<head>
<title></title>
<meta charset="utf-8" />

<style>

    /*Copied from bootstrap to handle input file multiple*/
    .btn {
        display: inline-block;
        padding: 6px 12px;
        margin-bottom: 0;
        font-size: 14px;
        font-weight: normal;
        line-height: 1.42857143;
        text-align: center;
        white-space: nowrap;
        vertical-align: middle;
        cursor: pointer;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
        background-image: none;
        border: 1px solid transparent;
        border-radius: 4px;
    }
    /*Also */
    .btn-success {
        border: 1px solid #c5dbec;
        background: #D0E5F5;
        font-weight: bold;
        color: #2e6e9e;
    }
    /* This is copied from https://github.com/blueimp/jQuery-File- 
   Upload/blob/master/css/jquery.fileupload.css */
    .fileinput-button {
        position: relative;
        overflow: hidden;
    }

        .fileinput-button input {
            position: absolute;
            top: 0;
            right: 0;
            margin: 0;
            opacity: 0;
            -ms-filter: 'alpha(opacity=0)';
            font-size: 200px;
            direction: ltr;
            cursor: pointer;
        }

    .thumb {
        height: 80px;
        width: 100px;
        border: 1px solid #000;
    }

    ul.thumb-Images li {
        width: 120px;
        float: left;
        display: inline-block;
        vertical-align: top;
        height: 120px;
    }

    .img-wrap {
        position: relative;
        display: inline-block;
        font-size: 0;
    }

        .img-wrap .close {
            position: absolute;
            top: 2px;
            right: 2px;
            z-index: 100;
            background-color: #D0E5F5;
            padding: 5px 2px 2px;
            color: #000;
            font-weight: bolder;
            cursor: pointer;
            opacity: .5;
            font-size: 23px;
            line-height: 10px;
            border-radius: 50%;
        }

        .img-wrap:hover .close {
            opacity: 1;
            background-color: #ff0000;
        }

    .FileNameCaptionStyle {
        font-size: 12px;
    }
    </style>

    <script type="text/javascript" src="scripts/jquery-1.10.2.js"></script>
    <script type="text/javascript">

    //I added event handler for the file upload control to access the files 
    properties.
    document.addEventListener("DOMContentLoaded", init, false);

    //To save an array of attachments 
    var AttachmentArray = [];

    $('#j_son').val(JSON.stringify(AttachmentArray));

    //counter for attachment array
    var arrCounter = 0;

    //to make sure the error message for number of files will be shown only 
    one time.
    var filesCounterAlertStatus = false;

    //un ordered list to keep attachments thumbnails
    var ul = document.createElement('ul');
    ul.className = ("thumb-Images");
    ul.id = "imgList";

    function init() {
        //add javascript handlers for the file upload event
        document.querySelector('#files').addEventListener('change', 
    handleFileSelect, false);
    }

    //the handler for file upload event
    function handleFileSelect(e) {
        //to make sure the user select file/files
        if (!e.target.files) return;

        //To obtaine a File reference
        var files = e.target.files;

        // Loop through the FileList and then to render image files as 
        thumbnails.
        for (var i = 0, f; f = files[i]; i++) {

            //instantiate a FileReader object to read its contents into 
            memory
            var fileReader = new FileReader();

            // Closure to capture the file information and apply validation.
            fileReader.onload = (function (readerEvt) {
                return function (e) {

                    //Apply the validation rules for attachments upload
                    ApplyFileValidationRules(readerEvt)

                    //Render attachments thumbnails.
                    RenderThumbnail(e, readerEvt);

                    //Fill the array of attachment
                    FillAttachmentArray(e, readerEvt)
                };
            })(f);

            // Read in the image file as a data URL.
            // readAsDataURL: The result property will contain the 
            //file/blob's data encoded as a data URL.
            // More info about Data URI scheme 
            //https://en.wikipedia.org/wiki/Data_URI_scheme
            fileReader.readAsDataURL(f);
        }
        document.getElementById('files').addEventListener('change', 
    handleFileSelect, false);
    }

    //To remove attachment once user click on x button
    jQuery(function ($) {
        $('div').on('click', '.img-wrap .close', function () {
            var id = $(this).closest('.img-wrap').find('img').data('id');

            //to remove the deleted item from array
            var elementPos = AttachmentArray.map(function (x) { return 
            x.FileName; }).indexOf(id);
            if (elementPos !== -1) {
                AttachmentArray.splice(elementPos, 1);
            }

            //to remove image tag
            $(this).parent().find('img').not().remove();

            //to remove div tag that contain the image
            $(this).parent().find('div').not().remove();

            //to remove div tag that contain caption name
            $(this).parent().parent().find('div').not().remove();

            //to remove li tag
            var lis = document.querySelectorAll('#imgList li');
            for (var i = 0; li = lis[i]; i++) {
                if (li.innerHTML == "") {
                    li.parentNode.removeChild(li);
                }
            }

        });
    }
    )

    //Apply the validation rules for attachments upload
    function ApplyFileValidationRules(readerEvt)
    {
        //To check file type according to upload conditions
        if (CheckFileType(readerEvt.type) == false) {
            alert("The file (" + readerEvt.name + ") does not match the 
            upload conditions, You can only upload jpg/png/gif files");
            e.preventDefault();
            return;
        }

        //To check file Size according to upload conditions
        if (CheckFileSize(readerEvt.size) == false) {
            alert("The file (" + readerEvt.name + ") does not match the 
            upload conditions, The maximum file size for uploads should not 
            exceed 300 KB");
            e.preventDefault();
            return;
        }

        //To check files count according to upload conditions
        if (CheckFilesCount(AttachmentArray) == false) {
            if (!filesCounterAlertStatus) {
                filesCounterAlertStatus = true;
                alert("You have added more than 10 files. According to 
                upload conditions you can upload 10 files maximum");
            }
            e.preventDefault();
            return;
        }
    }

    //To check file type according to upload conditions
    function CheckFileType(fileType) {
        if (fileType == "image/jpeg") {
            return true;
        }
        else if (fileType == "image/png") {
            return true;
        }
        else if (fileType == "image/gif") {
            return true;
        }
         else if (fileType == "image/jpg") {
            return true;
        }
        else {
            return false;
        }
        return true;
    }

    //To check file Size according to upload conditions
    function CheckFileSize(fileSize) {
        if (fileSize < 2000000) {
            return true;
        }
        else {
            return false;
        }
        return true;
    }

    //To check files count according to upload conditions
    function CheckFilesCount(AttachmentArray) {
        //Since AttachmentArray.length return the next available index in 
        //the array, 
        //I have used the loop to get the real length
        var len = 0;
        for (var i = 0; i < AttachmentArray.length; i++) {
            if (AttachmentArray[i] !== undefined) {
                len++;
            }
        }
        //To check the length does not exceed 10 files maximum
        if (len > 9) {
            return false;
        }
        else
        {
            return true;
        }
    }

    //Render attachments thumbnails.
    function RenderThumbnail(e, readerEvt)
    {
        var li = document.createElement('li');
        ul.appendChild(li);
        li.innerHTML = ['<div class="img-wrap"> <span class="close">&times; 
        </span>' +
            '<img class="thumb" src="', e.target.result, '" title="', 
            escape(readerEvt.name), '" data-id="',
            readerEvt.name, '"/>' + '</div>'].join('');

        var div = document.createElement('div');
        div.className = "FileNameCaptionStyle";
        li.appendChild(div);
        div.innerHTML = [readerEvt.name].join('');
        document.getElementById('Filelist').insertBefore(ul, null);
    }

    //Fill the array of attachment
    function FillAttachmentArray(e, readerEvt)
    {
        AttachmentArray[arrCounter] =
        {
            AttachmentType: 1,
            ObjectType: 1,
            FileName: readerEvt.name,
            FileDescription: "Attachment",
            NoteText: "",
            MimeType: readerEvt.type,
            Content: e.target.result.split("base64,")[1],
            FileSizeInBytes: readerEvt.size,
        };
        arrCounter = arrCounter + 1;

        //THIS IS THE PART I ADDED TO POPULATE THE HIDDEN INPUT FIELD
        $('#j_son').val(JSON.stringify(AttachmentArray));
    }

    //THIS IS TO UPDATE THE INPUT FIELD WHEN A FILE IS REMOVED
    $(document).on('click', '.close', function(){
        var myString = JSON.stringify(AttachmentArray);
        $('#j_son').val(myString); 
    });


</script>

</head>
<body>
<div>
    <label style="font-size: 14px;">
        <span style='color:navy;font-weight:bold'>Attachment Instructions : 
</span>
    </label>

    <ul>
        <li>
            Allowed only files with extension (jpg, png, gif)
        </li>
        <li>
            Maximum number of allowed files 10 with 2 MB for each
        </li>
        <li>
            you can select files from different folders
        </li>
    </ul>
     <form method="POST" action="" enctype="multipart/form-data">
    <span class="btn btn-success fileinput-button">
        <span>Select Attachment</span>
        <input type="file" name="files[]" id="files" multiple 
        accept="image/jpeg, image/jpg image/png, image/gif,"><br />
    </span>

        <!--input field to be populated by the array-->
        <input type="text" name="j_son"  id="j_son" style="width: 500px;"> 

        <!--Submit and post to get decoded JSON string-->
        <button type="submit" id="image_post" name="post_it">Submit</button>

    </form>
    <output id="Filelist"></output>

</div>


<?php 

if(isset($_POST['post_it']))
{
    //other input fields

    $file = $_POST['j_son'];

    $tempData = html_entity_decode($file);
    $cleanData = json_decode($tempData, true);

    foreach($cleanData as $p)
    {
        echo $p['Content']."</br>";

        //insert code to uploads folder
    }
}
?>
</body>
</html>
Justin
  • 29
  • 8

1 Answers1

0

-- edit --

This may be because of a known issue in chrome. Try using a blob as recommended in this post

How can javascript upload a blob?

function uploadAudio( blob ) {
  var reader = new FileReader();
  reader.onload = function(event){
    var fd = {};
    fd["fname"] = "test.wav";
    fd["data"] = event.target.result;
    $.ajax({
      type: 'POST',
      url: 'upload.php',
      data: fd,
      dataType: 'text'
    }).done(function(data) {
        console.log(data);
    });
  };
  reader.readAsDataURL(blob);
}

-- /edit --

ok it seems like you are adding an onChange event listener multiple times to the "files" id. Once in the init and once every time the handleFileSelect function is called. This could for sure be your slowdown problem.

Also, if you are going to have a file upload size that maxes out at 2MB you should set this in your PHP file using upload_max_filesize and also set post_max_size.

ini_set('upload_max_filesize', '2M');
ini_set('post_max_size', '2M');

from php.net:

upload_max_filesize

The maximum size of an uploaded file.

post_max_size

Sets max size of post data allowed. This setting also affects file upload. To upload large files, this value must be larger than upload_max_filesize. Generally speaking, memory_limit should be larger than post_max_size. When an integer is used, the value is measured in bytes.

Also if your upload ends up timing out you might also want to extend the execution time by using max_input_time or max_execution time though I think that max_input_time should be enough.

ini_set('max_input_time', 300); ini_set('max_execution_time', 300);

max_input_time

This sets the maximum time in seconds a script is allowed to parse input data, like POST and GET. Timing begins at the moment PHP is invoked at the server and ends when execution begins. The default setting is -1, which means that max_execution_time is used instead. Set to 0 to allow unlimited time.

max_execution_time

This sets the maximum time in seconds a script is allowed to run before it is terminated by the parser. This helps prevent poorly written scripts from tying up the server. The default setting is 30. When running PHP from the command line the default setting is 0.

This needs to be added at the top of your PHP file before other output.

pg316
  • 1,380
  • 1
  • 8
  • 7
  • or added to the php.ini file :) – HermanTheGermanHesse Oct 15 '18 at 16:16
  • Hi Robert thanks for looking over this code, the PHP is not executed until the hidden field has the desired images, but this is where the problem is. If i select a small image from the file input it outputs the image preview after a second or two which is ok, then if i select a larger file it hangs and if i choose all 10 files allowed it crashes. Is there any chance you could show me in the JavaScript on what you would do differently in relation to the multiple on-change events you mentioned. Im after the crazy long base64 string from these images. – Justin Oct 15 '18 at 17:54
  • ok I see what you are saying. Actually I don't see anything wrong with your code. I wonder if this may be related to how processor intensive it is to join large sets of data. In your "RenderThumbnail" function can you try returning false at the very beginning to see if things change? Also I think I understand why you are resetting the onChange function, but can you also try commenting this out in your "handleFileSelect" function – pg316 Oct 15 '18 at 18:49
  • Actually, I read a little more about this and it seems like this may be a known bug in chrome. https://bugs.chromium.org/p/chromium/issues/detail?id=69227. Check out this SO post about the issue that says you may need to use a blob: https://stackoverflow.com/questions/16761927/aw-snap-when-data-uri-is-too-large – pg316 Oct 15 '18 at 19:31
  • Tried those code edits and nothing improved, i tried in Firefox and a big improvement. I will look at those links now and try and find a work around, thanks Robert for taking time to look over things. – Justin Oct 15 '18 at 19:48