11

I need to upload a part of a file (only the first MB). I've created a PHP script which uploads the whole file. The data (formData Object) is passed by a ajax call.

My idea would be now to split the file with javascript (jquery). Is there any solution for my request?

Current code:

function start(a){
    //var fSize = $('#fileUpload')[0].files[0].size / 1024;
    var formData = new FormData();    
    formData.append( 'fileUpload', $('#fileUpload')[0].files[0] );
    //AJAX
    $.ajax({
        url: 'script.php',
        type: 'POST',
        data: formData,
        processData: false,
        contentType: false,
        success: function(msg){
            alert("Win: " + msg);
        },
        error: function(bla, msg){
            alert("Fail: " + msg);
        }
    });
}
bfavaretto
  • 71,580
  • 16
  • 111
  • 150
Sylnois
  • 1,589
  • 6
  • 23
  • 47
  • 3
    You don't have any access to the content file in JavaScript. There is no way to split it. And BTW there is no way to post a file with AJAX either. – RoToRa Aug 21 '12 at 13:02
  • @RoToRa Why don't you post it as an answer? –  Aug 21 '12 at 13:14
  • 2
    @RoToRa Well, actually there's a way - with new [FileReader API](https://developer.mozilla.org/en-US/docs/DOM/FileReader). But somehow I think it's not relevant here. ) – raina77ow Aug 21 '12 at 13:14
  • @user1515190 I suppose [this article](https://developer.mozilla.org/en-US/docs/Using_files_from_web_applications#Example.3A_Uploading_a_user-selected_file) might be helpful for you. But take note that tools used in it are not universally supported... yet. – raina77ow Aug 21 '12 at 13:21
  • @RoToRa ehm, this script above works. – Sylnois Aug 21 '12 at 13:46

1 Answers1

21

Since you're using FormData, which is a fairly new technology, I'll show you something with new technologies as well.

First, read the file with a FileReader object:

var fr = new FileReader(), buf, file = $('#fileUpload')[0].files[0];
fr.onload = function(e) {
    buf = new Uint8Array(e.target.result);
};
fr.readAsArrayBuffer(file);

Then you can create a Blob for each splitted part (1e6 bytes long each):

for (var i = 0, blobs = []; i < buf.length; i += 1e6)
    blobs.push(new Blob([buf.subarray(i, i + 1e6)]));

Finally, you can add all your Blobs to your FormData object:

var formData = new FormData();
for (var i = 0; i < blobs.length; i++)
    formData.append("slice" + i, blobs[i], file.name + ".part" + i);

You should be ok. I haven't tested it, though.

I don't know anything about the performance either. You can use fr.readAsBinaryString too, thus making e.target.result a string. This way, you can create the Blobs using a simple substring/slice/substr/whatever, but I fear there could be some problems with Unicode characters and whatnot. Plus, maybe it's slower.

Putting everything in a more coherent snippet:

$('#fileUpload').change(function() {
    // If no file is selected, there's nothing to do
    if (!this.files.length) return;

    var fr = new FileReader(), file = this.files[0];
    fr.onload = function(e) {
        splitAndSendFile(new Uint8Array(e.target.result), file);
    };
    fr.readAsArrayBuffer(file);
};

function splitAndSendFile(dataArray, file) {
    var i = 0, formData, blob;
    for (; i < dataArray.length; i += 1e6) {
        blob = new Blob([dataArray.subarray(i, i + 1e6)]);
        formData = new FormData();
        formData.append("fileUpload", blob, file.name + ".part" + (i / 1e6));
        $.ajax({
            url: 'script.php',
            type: 'POST',
            data: formData,
            processData: false,
            contentType: false,
            success: function(msg){
                alert("Win: " + msg);
            },
            error: function(bla, msg){
                alert("Fail: " + msg);
            }
        });
    }
}

Note: FormData.append takes a third optional parameter, which should be the name of the file in case of File or Blob values. If not specified, Blobs may get unpredictable random file names.

Probably that parameter isn't standard, and it's not mentioned in the MDN artice, but I used it in the snippet above nonetheless. Anyway, if you know what you're doing, you can have several options to specify the file name. For example, with formData.append("filename", file.name) or sending a custom header in the request.

MaxArt
  • 22,200
  • 10
  • 82
  • 81
  • Thanks for your reply. I don't want to send the whole array, just part for part(so i can control the progressbar), but somehow it does not work(link to my script at the end). My PHP-Script is a simple command(move_uploaded_file($_FILES["fileUpload"]["tmp_name"], $uploadPath . $_FILES["fileUpload"]["name"]);). I want to fit the parts together serverside. http://193.109.52.60/dario/f/ – Sylnois Aug 21 '12 at 14:28
  • @user1515190 It doesn't work because of a syntax error in the `blob.push(...` line. Missed a closing parenthesis, my bad. You can try it again. – MaxArt Aug 21 '12 at 15:50
  • still not working :/ PS: Really thanks for your help! I try this for weeks(not full-time of course :D) – Sylnois Aug 21 '12 at 16:05
  • @user1515190 Ah, I get it. Now the problem is in _your_ code. I didn't put everything in a complete form, but it can't be done in a synchronous way. I'll give you a hint about it, in the answer. – MaxArt Aug 21 '12 at 19:39
  • Something still not right. I've tried to debug my code with alerts. Alert 6 does not appaer(after this code section: blob = new Blob([dataArray.subarray(i, i + 1e6)]); – Sylnois Aug 22 '12 at 09:07
  • @user1515190 It works perfectly for me, "bla6" is alerted and then I get a "Win" alert with the size of the sent chunk. I'm using Chrome 22 dev. Why don't you debug with the developer tools of your browser instead of the old fashioned alerts? Which one are you using? – MaxArt Aug 22 '12 at 10:01
  • i can see your files(AEVLX.gif.part0 - part2). I'm using Chrome 18.0.1025.162. When i try to upload a ZIP-file, nothing happens. Only the first four alerts appear. – Sylnois Aug 22 '12 at 11:05
  • i updated my google chrome to 21 and now it works.. but whats with the people with older browser? – Sylnois Aug 22 '12 at 11:14
  • large files still not working :( browser crashes after trying to upload a 25 MB big RAR-file – Sylnois Aug 22 '12 at 11:18
  • @user1515190 That's probably because of a memory overflow of some sort. I don't think file splitting is a task for browsers, anyway: that's the major problem. People with older browsers... well, should upgrade before using your app. Chrome, Firefox and Opera usually upgrades automatically. IE is out of question in this case, so you shouldn't worry about it. It's not a tragedy to ask your users to upgrade to browsers with a good HTML5 support. – MaxArt Aug 22 '12 at 14:12
  • It is, cause in many companies is the ie(an older version) default and the employees can't just upgrade the software, but many THANKS four your help, really! A last question: i've done this partions that i can show up an progressbar. Do you know another way to show up an progressbar with JQuery? without flash or any other extensions :) – Sylnois Aug 22 '12 at 14:45
  • @user1515190 IE, even IE9, doesn't support `FormData`, nor XHR2 requests, so you're out of luck with this bunch of code. If your aim was to show a progress bar, then you could just rely on the `onprogress` event of the `upload` member of the XHR object, getting a much better result than all the pain we got through. As for the progress bar, I'd suggest to use some common jQuery plugins, for example the progress bar of [jQuery UI](http://jqueryui.com/demos/progressbar/), which is like an "official" widget library for jQuery. (BTW, can you accept my answer if it helped you?) – MaxArt Aug 22 '12 at 16:52
  • what is FormData and FileReader class? – Thomas Nov 30 '15 at 10:50
  • @Thomas They're API that are part of the XHR2 standard interface. They're available in IE10+ and evergreen browsers. – MaxArt Nov 30 '15 at 14:28
  • Thank you, used your code as base to do the same with angular js. https://github.com/kurtzace/angular-multipart-smaller-chunks – Karan Bhandari Aug 30 '17 at 23:36