Pure HTML5 file upload

Date: 09th Dec 2011 Author: admin 81 Comments
Posted in: AJAX, HTML5, JavaScript |Tags: , , , ,

Pure HTML5 file upload

Pure HTML5 file upload

Today we will be developing a great HTML5 file upload form with progress bar and preview (at client-side). We have already gave you jQuery based solution, but today’s application don’t require jQuery at all. All made in pure HTML5 Javascript. I’m going to use FileReader (html5) to implement live preview (without uploading to server), and, going to use XMLHttpRequest to send data to server.

Here are our demo and downloadable package:

Live Demo
download in package

Ok, download the sources and lets begin !


Step 1. HTML

At this page you can see out form for upload images

index.html


<html lang="en" >
    <head>
        <meta charset="utf-8" />
        <title>Pure HTML5 file upload | Script Tutorials</title>
        <link href="css/main.css" rel="stylesheet" type="text/css" />
        <script src="js/script.js"></script>
    </head>
    <body>
        <header>
            <h2>Pure HTML5 file upload</h2>
            <a href="http://script-tutorials.com/pure-html5-file-upload/" class="stuts">Back to original tutorial on <span>Script Tutorials</span></a>
        </header>
        <div class="container">
            <div class="contr"><h2>You can select the file (image) and click Upload button</h2></div>

            <div class="upload_form_cont">
                <form id="upload_form" enctype="multipart/form-data" method="post" action="upload.php">
                    <div>
                        <div><label for="image_file">Please select image file</label></div>
                        <div><input type="file" name="image_file" id="image_file" onchange="fileSelected();" /></div>
                    </div>
                    <div>
                        <input type="button" value="Upload" onclick="startUploading()" />
                    </div>
                    <div id="fileinfo">
                        <div id="filename"></div>
                        <div id="filesize"></div>
                        <div id="filetype"></div>
                        <div id="filedim"></div>
                    </div>
                    <div id="error">You should select valid image files only!</div>
                    <div id="error2">An error occurred while uploading the file</div>
                    <div id="abort">The upload has been canceled by the user or the browser dropped the connection</div>
                    <div id="warnsize">Your file is very big. We can't accept it. Please select more small file</div>

                    <div id="progress_info">
                        <div id="progress"></div>
                        <div id="progress_percent">&nbsp;</div>
                        <div class="clear_both"></div>
                        <div>
                            <div id="speed">&nbsp;</div>
                            <div id="remaining">&nbsp;</div>
                            <div id="b_transfered">&nbsp;</div>
                            <div class="clear_both"></div>
                        </div>
                        <div id="upload_response"></div>
                    </div>
                </form>

                <img id="preview" />
            </div>
        </div>
    </body>
</html>

Step 2. CSS

css/main.css

I have selected all necessary styles for our html5 upload form

css/main.css

.upload_form_cont {
    background: -moz-linear-gradient(#ffffff, #f2f2f2);
    background: -ms-linear-gradient(#ffffff, #f2f2f2);
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #f2f2f2));
    background: -webkit-linear-gradient(#ffffff, #f2f2f2);
    background: -o-linear-gradient(#ffffff, #f2f2f2);
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f2f2f2');
    -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f2f2f2')";
    background: linear-gradient(#ffffff, #f2f2f2);

    color:#000;
    overflow:hidden;
}
#upload_form {
    float:left;
    padding:20px;
    width:700px;
}
#preview {
    background-color:#fff;
    display:block;
    float:right;
    width:200px;
}
#upload_form > div {
    margin-bottom:10px;
}
#speed,#remaining {
    float:left;
    width:100px;
}
#b_transfered {
    float:right;
    text-align:right;
}
.clear_both {
    clear:both;
}
input {
    border-radius:10px;
    -moz-border-radius:10px;
    -ms-border-radius:10px;
    -o-border-radius:10px;
    -webkit-border-radius:10px;

    border:1px solid #ccc;
    font-size:14pt;
    padding:5px 10px;
}
input[type=button] {
    background: -moz-linear-gradient(#ffffff, #dfdfdf);
    background: -ms-linear-gradient(#ffffff, #dfdfdf);
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #dfdfdf));
    background: -webkit-linear-gradient(#ffffff, #dfdfdf);
    background: -o-linear-gradient(#ffffff, #dfdfdf);
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#dfdfdf');
    -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#dfdfdf')";
    background: linear-gradient(#ffffff, #dfdfdf);
}
#image_file {
    width:400px;
}
#progress_info {
    font-size:10pt;
}
#fileinfo,#error,#error2,#abort,#warnsize {
    color:#aaa;
    display:none;
    font-size:10pt;
    font-style:italic;
    margin-top:10px;
}
#progress {
    border:1px solid #ccc;
    display:none;
    float:left;
    height:14px;

    border-radius:10px;
    -moz-border-radius:10px;
    -ms-border-radius:10px;
    -o-border-radius:10px;
    -webkit-border-radius:10px;

    background: -moz-linear-gradient(#66cc00, #4b9500);
    background: -ms-linear-gradient(#66cc00, #4b9500);
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #66cc00), color-stop(100%, #4b9500));
    background: -webkit-linear-gradient(#66cc00, #4b9500);
    background: -o-linear-gradient(#66cc00, #4b9500);
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#66cc00', endColorstr='#4b9500');
    -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#66cc00', endColorstr='#4b9500')";
    background: linear-gradient(#66cc00, #4b9500);
}
#progress_percent {
    float:right;
}
#upload_response {
    margin-top: 10px;
    padding: 20px;
    overflow: hidden;
    display: none;
    border: 1px solid #ccc;

    border-radius:10px;
    -moz-border-radius:10px;
    -ms-border-radius:10px;
    -o-border-radius:10px;
    -webkit-border-radius:10px;

    box-shadow: 0 0 5px #ccc;
    background: -moz-linear-gradient(#bbb, #eee);
    background: -ms-linear-gradient(#bbb, #eee);
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #bbb), color-stop(100%, #eee));
    background: -webkit-linear-gradient(#bbb, #eee);
    background: -o-linear-gradient(#bbb, #eee);
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#bbb', endColorstr='#eee');
    -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#bbb', endColorstr='#eee')";
    background: linear-gradient(#bbb, #eee);
}

Step 3. HTML5 JS

js/script.js

// common variables
var iBytesUploaded = 0;
var iBytesTotal = 0;
var iPreviousBytesLoaded = 0;
var iMaxFilesize = 1048576; // 1MB
var oTimer = 0;
var sResultFileSize = '';

function secondsToTime(secs) { // we will use this function to convert seconds in normal time format
    var hr = Math.floor(secs / 3600);
    var min = Math.floor((secs - (hr * 3600))/60);
    var sec = Math.floor(secs - (hr * 3600) -  (min * 60));

    if (hr < 10) {hr = "0" + hr; }
    if (min < 10) {min = "0" + min;}
    if (sec < 10) {sec = "0" + sec;}
    if (hr) {hr = "00";}
    return hr + ':' + min + ':' + sec;
};

function bytesToSize(bytes) {
    var sizes = ['Bytes', 'KB', 'MB'];
    if (bytes == 0) return 'n/a';
    var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
    return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i];
};

function fileSelected() {

    // hide different warnings
    document.getElementById('upload_response').style.display = 'none';
    document.getElementById('error').style.display = 'none';
    document.getElementById('error2').style.display = 'none';
    document.getElementById('abort').style.display = 'none';
    document.getElementById('warnsize').style.display = 'none';

    // get selected file element
    var oFile = document.getElementById('image_file').files[0];

    // filter for image files
    var rFilter = /^(image\/bmp|image\/gif|image\/jpeg|image\/png|image\/tiff)$/i;
    if (! rFilter.test(oFile.type)) {
        document.getElementById('error').style.display = 'block';
        return;
    }

    // little test for filesize
    if (oFile.size > iMaxFilesize) {
        document.getElementById('warnsize').style.display = 'block';
        return;
    }

    // get preview element
    var oImage = document.getElementById('preview');

    // prepare HTML5 FileReader
    var oReader = new FileReader();
        oReader.onload = function(e){

        // e.target.result contains the DataURL which we will use as a source of the image
        oImage.src = e.target.result;

        oImage.onload = function () { // binding onload event

            // we are going to display some custom image information here
            sResultFileSize = bytesToSize(oFile.size);
            document.getElementById('fileinfo').style.display = 'block';
            document.getElementById('filename').innerHTML = 'Name: ' + oFile.name;
            document.getElementById('filesize').innerHTML = 'Size: ' + sResultFileSize;
            document.getElementById('filetype').innerHTML = 'Type: ' + oFile.type;
            document.getElementById('filedim').innerHTML = 'Dimension: ' + oImage.naturalWidth + ' x ' + oImage.naturalHeight;
        };
    };

    // read selected file as DataURL
    oReader.readAsDataURL(oFile);
}

function startUploading() {
    // cleanup all temp states
    iPreviousBytesLoaded = 0;
    document.getElementById('upload_response').style.display = 'none';
    document.getElementById('error').style.display = 'none';
    document.getElementById('error2').style.display = 'none';
    document.getElementById('abort').style.display = 'none';
    document.getElementById('warnsize').style.display = 'none';
    document.getElementById('progress_percent').innerHTML = '';
    var oProgress = document.getElementById('progress');
    oProgress.style.display = 'block';
    oProgress.style.width = '0px';

    // get form data for POSTing
    //var vFD = document.getElementById('upload_form').getFormData(); // for FF3
    var vFD = new FormData(document.getElementById('upload_form')); 

    // create XMLHttpRequest object, adding few event listeners, and POSTing our data
    var oXHR = new XMLHttpRequest();
    oXHR.upload.addEventListener('progress', uploadProgress, false);
    oXHR.addEventListener('load', uploadFinish, false);
    oXHR.addEventListener('error', uploadError, false);
    oXHR.addEventListener('abort', uploadAbort, false);
    oXHR.open('POST', 'upload.php');
    oXHR.send(vFD);

    // set inner timer
    oTimer = setInterval(doInnerUpdates, 300);
}

function doInnerUpdates() { // we will use this function to display upload speed
    var iCB = iBytesUploaded;
    var iDiff = iCB - iPreviousBytesLoaded;

    // if nothing new loaded - exit
    if (iDiff == 0)
        return;

    iPreviousBytesLoaded = iCB;
    iDiff = iDiff * 2;
    var iBytesRem = iBytesTotal - iPreviousBytesLoaded;
    var secondsRemaining = iBytesRem / iDiff;

    // update speed info
    var iSpeed = iDiff.toString() + 'B/s';
    if (iDiff > 1024 * 1024) {
        iSpeed = (Math.round(iDiff * 100/(1024*1024))/100).toString() + 'MB/s';
    } else if (iDiff > 1024) {
        iSpeed =  (Math.round(iDiff * 100/1024)/100).toString() + 'KB/s';
    }

    document.getElementById('speed').innerHTML = iSpeed;
    document.getElementById('remaining').innerHTML = '| ' + secondsToTime(secondsRemaining);
}

function uploadProgress(e) { // upload process in progress
    if (e.lengthComputable) {
        iBytesUploaded = e.loaded;
        iBytesTotal = e.total;
        var iPercentComplete = Math.round(e.loaded * 100 / e.total);
        var iBytesTransfered = bytesToSize(iBytesUploaded);

        document.getElementById('progress_percent').innerHTML = iPercentComplete.toString() + '%';
        document.getElementById('progress').style.width = (iPercentComplete * 4).toString() + 'px';
        document.getElementById('b_transfered').innerHTML = iBytesTransfered;
        if (iPercentComplete == 100) {
            var oUploadResponse = document.getElementById('upload_response');
            oUploadResponse.innerHTML = '<h1>Please wait...processing</h1>';
            oUploadResponse.style.display = 'block';
        }
    } else {
        document.getElementById('progress').innerHTML = 'unable to compute';
    }
}

function uploadFinish(e) { // upload successfully finished
    var oUploadResponse = document.getElementById('upload_response');
    oUploadResponse.innerHTML = e.target.responseText;
    oUploadResponse.style.display = 'block';

    document.getElementById('progress_percent').innerHTML = '100%';
    document.getElementById('progress').style.width = '400px';
    document.getElementById('filesize').innerHTML = sResultFileSize;
    document.getElementById('remaining').innerHTML = '| 00:00:00';

    clearInterval(oTimer);
}

function uploadError(e) { // upload error
    document.getElementById('error2').style.display = 'block';
    clearInterval(oTimer);
}  

function uploadAbort(e) { // upload abort
    document.getElementById('abort').style.display = 'block';
    clearInterval(oTimer);
}

Most of code is already commented. So I will hope that you will understand all this code. Anyway – how it working: when we select file – function ‘fileSelected’ is executing. We filter all unnecessary formats (allow to upload next formats: bmp, gif, jpg, png, tif), in case of huge file – we will draw warning message. Then, through FileReader::readAsDataURL we will draw live preview of selected file. Plus, we will display another information about image: its name, size, type, and dimensions. Process of uploading is a little complicated. But generally, we have to prepare XMLHttpRequest object, add event listeners to next events: progress, load, error and abort. And after – post form data (I have used FormData class) to our ‘upload.php’ receiver.

Step 4. PHP

upload.php

<?php

function bytesToSize1024($bytes, $precision = 2) {
    $unit = array('B','KB','MB');
    return @round($bytes / pow(1024, ($i = floor(log($bytes, 1024)))), $precision).' '.$unit[$i];
}

$sFileName = $_FILES['image_file']['name'];
$sFileType = $_FILES['image_file']['type'];
$sFileSize = bytesToSize1024($_FILES['image_file']['size'], 1);

echo <<<EOF
<p>Your file: {$sFileName} has been successfully received.</p>
<p>Type: {$sFileType}</p>
<p>Size: {$sFileSize}</p>
EOF;

As you can see – I’m not uploading file. But, ‘echo’ back all info about accepted file. This information will appear in our <div id="upload_response"></div> element.


Live Demo
download in package

Conclusion

Welcome back to read new awesome and unique articles about HTML5. Good luck!

Enjoyed this Post?

    Tweet
   
   

Stay connected with us:

If you enjoyed this article, feel free to share our tutorial with your friends.

81 Comments

    • pl4g4's Gravatar
    • when i run the demo in ie9. im getting the following error .

      SCRIPT5009: ‘FormData’ is undefined
      script.js, line 94 character 5

      var vFD = new FormData(document.getElementById(‘upload_form’));

      the upload gets stuck.

    • Philip Bolt's Gravatar
    • I think this is a great display and the demo works fine but when I try to run the downloaded code, I get an ‘upload error’ message.

    • Will's Gravatar
    • Will's Gravatar
    • If I try to upload a filetype that is not specified in the filter, the warning message pops up but clicking upload still allows a forbidden filetype to be uploaded successfully.

    • German's Gravatar
    • Great code but how do I get it to do the actual upload and dictate the path to where the file is uploaded. As per the tutorial you say it is only echoing the actual transfer but not transferring the file. How do I get it yo upload to a given director? Thanks.

    • Tux's Gravatar
    • Ian's Gravatar
    • Hi, thanks for the tutorial and file download. Can this be made to fall back somehow for IE to avoid the error message? Maybe conditional statement or javascript browser check? If so, what would you recommend?

      Thanks

    • Henry's Gravatar
    • It is compatible with Chrome?
      Because when I run the demo, in Chrome, don’t show the preview.

    • Dido's Gravatar
    • How to change rFilter to work with text files (txt, csv, doc)?

      var rFilter = /^(image\/bmp|image\/gif|image\/jpeg|image\/png|image\/tiff)$/i;

      Thanks in advance !!!

    • Alexandre's Gravatar
    • How do I configure the upload path ?
      Can’t find it in the scipt…

      Thank you for your help !

    • Mike's Gravatar
    • Has anybody figured out a work around for document.getElementById(‘image_file’).files[0], IE9 does not support this. Great uploader by the way.

    • Will's Gravatar
    • Since IE doesn’t support FormData or the addEventListener on the XMLHttpRequest object, clicking the upload button doesn’t do anything at all and doesn’t show an error message of any kind. What is your solution for people who use IE?

      Is there a way to check for IE and then write some event handlers to grab the form data? Would onreadystatechange work in IE instead (http://www.j2mesalsa.com/ajax/properties.php) or some other attach event code like http://rubayathasan.com/tutorial/ie8-addeventlistener-alternative-example/ ?

    • zotr4x's Gravatar
    • hi,
      first : thx !!
      and how can I do to make multiple file, please?
      cause :

      var oFile = document.getElementById(‘fichier_upload’).files[0];
      var oFile = document.getElementById(‘fichier_upload’).files[1];
      var oFile = document.getElementById(‘fichier_upload’).files[2];

      doesn’t work :s

    • Ernie's Gravatar
    • Nice tutorial…
      how do can I MySQL database to upload image for each user, when using username and password

    • Kobi Snir's Gravatar
    • Hi, i am trying to upload large files over 30MB and it’s seems that the file stop uploading after 12%…
      is there anything i can change to make it work?

    • Ernie's Gravatar
    • shobha's Gravatar
    • How to change rFilter to work with text files (xls, csv, xlxs)?

      var rFilter = /^(image\/bmp|image\/gif|image\/jpeg|image\/png|image\/tiff)$/i;

      Thanks

    • Tom's Gravatar
    • Anyway to get this to upload as soon as you drop the file ?
      Without the need to press the upload button ?

    • Junaid's Gravatar
    • How to add Captcha and other required fields like Name, Email, Message and Payment Option and then send all the details with attachment to the specified email address?

    • Junaid's Gravatar
    • peecesO's Gravatar
    • How do we cancel the upload if it’s taking too long? How would the event ‘abort’ be registered to the current ajax request?

    • catimos's Gravatar
    • ScottAgen's Gravatar
    • I am not complaining – this code looks amazing, but am I wrong for wondering WHY the script is incomplete? Only the most important part is left out, is this some sort of joke? After hours of studying the code, troubleshooting, and referencing php.net’s bare bone guides, I’m left with nothing but missing puzzle pieces. I may be one character or two off, but if the author made this beautiful uploader, I’m sure he could give us the remaining 5% that would finish the job – not just produce the pretty [and pretend] file uploaded messages.

    • ScottAgen's Gravatar
    • LOL – It happens every time! I was searching and trying to get the script, files and directory to work, and about 10 minutes after writing my post (asking for the missing code), I figured it out. But my question mark still stands – only several hours might have been saved had the script been complete. Thanks for providing the 95%

    • Giggio's Gravatar
    • Hi there thank you very much for the very nice tutorial.
      Just I cannot solve:
      If I try to upload a filetype that is not specified in the filter, NO warning message pops up and clicking upload allows a forbidden filetype to be uploaded successfully.
      In the same way, if a file is greater than “iMaxFilesize” I have no error at all and it allows uploads anyway.
      Any suggestions

      Thank you very much,
      Giggio :)

    • UAK's Gravatar
    • Hi,

      Looks great.

      However I am not a developer but more like front-end designer. Where does the uploaded file go? How to set the upload path?

      And any support of Multiple file upload?

      Thanks

    • monika's Gravatar
    • I want to add cancel button while uploading file .
      I have added this but having a problem file upload canceled only after 100% uploading.
      Can you tell me how to add cancel button.
      Regards,

    • avinash's Gravatar
    • where will the uploaded file will go
      where we can get the file
      can i know the dictonary name

    • Bin's Gravatar
    • Hardik Sondagar's Gravatar
    • Hardik SondagarJanuary 22, 2013 5:44 am

      Helps a lot. Life saving tutorial. :-)
      Simple & Clean , straightforward.
      Thank you so much..

    • pieter's Gravatar
    • In the HTML5 script sample you did not include the upload file.
      I have so far no success in getting the XMLHttpRequest object to function.
      What would be the file format for the uploaded file?
      Is below script adequate to get files?

      Anyone there to help me with this issue?
      tnx

    • caledo's Gravatar
    • Please replay in js/script.js l 102

      oXHR.open(‘POST’, ‘upload.php’); by
      oXHR.open(‘POST’, document.getElementById(“upload_form”).getAttribute(‘action’));

    • jagabandhu's Gravatar
    • Sammy's Gravatar
    • How to change rFilter to work with video and audio files (mp3, mp4, avi)?

      var rFilter = /^(image\/bmp|image\/gif|image\/jpeg|image\/png|image\/tiff)$/i;

      Thanks in advance !!!

    • Edik Manoukian's Gravatar
    • Edik ManoukianMarch 25, 2013 5:01 pm

      I have read your previous comments about where the uploaded files go, but I am still confused. can you give me an example of a directory that I can change for the files to go there? I tried changing the script to
      $sFileName = $_FILES[‘http://coolf2a.com/uploaded''name'
      $sFileType = $_FILES[‘http://coolf2a.com/uploaded''type'
      $sFileSize = bytesToSize1024($_FILES[‘http://coolf2a.com/uploaded''size', 1)
      I also created a uploaded folder in my server.
      Thanks

    • TJ's Gravatar
    • It would be most sensible and save over half the comments here to just complete the code with example path to save. It would probably take less writing on your part to edit the post than continually answer how simple it is. – simple when you know how. – otherwise great tut, thanks

    • Gennadiy's Gravatar
    • Thanks for nice solution, bat ja script dont have coopirate. Please mail me is it possible, like name, mail, autor, etc.

    • Joe's Gravatar
    • Hi,
      i uploaded the script to my webspace. When i upload a file it says “successfully uploaded file..:” but i cant find it in the folder? Where will the file be uploaded?

    • Joe's Gravatar
    • I modified the uploads.php like you mentioned, but it does not work:

      <?php

      function bytesToSize1024($bytes, $precision = 2) {
      $unit = array('B','KB','MB');
      return @round($bytes / pow(1024, ($i = floor(log($bytes, 1024)))), $precision).' '.$unit[$i];
      }

      $sFileName = $_FILES['image_file']['name'];
      $sFileType = $_FILES['image_file']['type'];
      $sFileSize = bytesToSize1024($_FILES['image_file']['size'], 1);

      echo <<<EOF
      Your file: {$sFileName} wurde hochgeladen JONGE!
      Type: {$sFileType}
      Size: {$sFileSize}
      EOF;

      /////////////////////// CONFIGURATION OF FILE FOLDER
      $uploads_dir = ‘/uploads’;
      foreach ($_FILES['image_file']['type'] as $key => $error) {
      if ($error == UPLOAD_ERR_OK) {
      $tmp_name = $_FILES["pictures"]["tmp_name"][$key];
      $name = $_FILES["pictures"]["name"][$key];
      move_uploaded_file($tmp_name, “$uploads_dir/$name”);
      }
      }
      ?>

    • remus's Gravatar
    • Hi everyone,
      i wonder if you could help me make the uploader to display the picture in it’s page after uploading is finished ,i must say i’m not very experienced.Thanx.

      $(‘#submit_form’)click(function(){
      $.ajax({
      type: ‘POST’,
      url: path/phpfile.php,
      data: image_input_name
      });

      //after submitting, get the url of the image form the server

      $(‘#div_to_display_image’).html(“”);

      });

    • Vinc's Gravatar
    • To help many of you, here is what I simply needed to put in the PHP file for the upload to work:

      move_uploaded_file($_FILES['image_file']['tmp_name'], $_FILES["image_file"]["name"]);

      Simply put this new line before the line saying ECHO <<EOF.
      It will let the file to upload itself in the same directory as the directory where your htm and php files are, if same. And you also need to ensure that your directory is 777 regarding the permissions.
      Also check that your PHP.ini says ON to file uploads.

    • nami's Gravatar
    • Hey admin nice work

      If i want to change the color of the image id=preview how ?

    • Israel's Gravatar
    • Great article! This worked well in desktop’s devices, but do not worked in mobile’s device. Do you know how can i make this upload’s system work with mobile (android and/or ios)?

Leave a Reply to Dido Cancel reply

Your email address will not be published. Required fields are marked *

*

CAPTCHA Image
Refresh Image

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>