2

I am experiencing difficulties with uploading files via ajax without sing html form. My case is like this:

  1. I have a textarea
  2. I would like to zip the content of that textarea and upload it to the server via AJAX (right now I am using JSZip)
  3. For testing purpose, I try to create a dummy zip file to send like this (not getting textarea content):

    var zip = new JSZip();
    zip.file("hello1.txt", "Hello First World\n");<br/>
    zip.file("hello2.txt", "Hello Second World\n");<br/>
    var content = zip.generate();
    
  4. Then I use Jquery ajax method to send, like this:

    $.post("the_url",
        {
            GradeRequest : {
                submitter_id : "foobar",
                evaluationset_id : 9,
                mode : 1,
                source_file : "a.cpp",
                file: content
            }
        }
    );
    

But the file is not received on the server. I have read question in How can I upload files asynchronously?, but all solutions are using html form. Is there any solution which does not involve any html form ?

Thanks in advance.

In the server side, I'm using Yii PHP Framework:

$model = $this->model;
    if ($model !== null && isset($_POST['GradeRequest'])) {
        $model->setAttributes($_POST['GradeRequest']);

        if ($model->validate()) {
            if (isset($_FILES['GradeRequest']['tmp_name']['file']) && $_FILES['GradeRequest']['tmp_name']['file'] !== "") {
                $model->file = file_get_contents($_FILES['GradeRequest']['tmp_name']['file']);
                $model->source_file = $_FILES['GradeRequest']['name']['file'];
            }

            $this->setReply(true, "Ok");
            $model->client_id = $this->clientId;
            $model->request_id = $this->requestRecord->id;
            $model->save();
        } else {
            $this->setReply(false, $model->getErrors());
        }
    }

As a proof of concept, I have created a form version like this and it works (the file got uploaded to the server):

<form enctype="multipart/form-data" method="post" action="[the_url]>
EvaluationSet id: <input type="text" name="GradeRequest[evaluationset_id]" /><br />
<input type="hidden" name="GradeRequest[mode]" value="0" />
<input type="hidden" name="GradeRequest[submitter_id]" value="Someone" />

Source file : <input type="text" name="GradeRequest[source_file]" /><br />
File : <input type="file" name="GradeRequest[file]" /><br />

<input type="submit" />

Community
  • 1
  • 1
HoverPhoenix
  • 177
  • 1
  • 4
  • 13

2 Answers2

3

There the FormData object that can help you (it's part of the so called xmlHttpRequest version 2): this is a compatibility chart, with some linked references and examples.

With that, you can add key/value pairs to the POST form, including File objects, and send it all via the common send method of xmlHttpRequest.

File objects can be easily retrieved using a <input type="file"> element, or even using drag & drop from your desktop.

If you want to upload some file content as a file, you'll have to create a Blob. This feature is still experimental but supported by Chrome and Firefox (at least... and Safari I guess?):

var builder = new BlobBuilder();
builder.append(content);
var blob = builder.getBlob("application/zip");

Keep in mind that at this moment you'll have to use MozBlobBuilder in Firefox and WebKitBlobBuilder in Chrome instead.

In some tutorials I've seen that the string is actually converted into a Uint8Array first. Maybe that's based on a older reference, because the append method of BlobBuilder should accept plain strings too. Never tried it though.

If your content is a Base64 encoded string, you'll have to convert it using atob (should be supported by every browser that also supports Blob and FormData).

Edit: BlobBuilder is now deprecated due to the new draft of the Blob constructor. So everything you'll have to do to get the Blob is:

var blob = new Blob([content], "application/zip");

The rest is quite simple:

var form = new FormData();
form.append("file", blob);

The problem here is that the file name on the server side is unpredictable and depends on the user agent. I've seen some uses of append with a third parameter specifying the file name, but I guess it's a good idea to send the actual file name to a separate key/value pair in the FormData object.

MaxArt
  • 22,200
  • 10
  • 82
  • 81
  • for the [compatibility chart](http://caniuse.com/#feat=xhr2), I don't really understand about it. Could you please explain more ? Yes, I am aware with the easy usage of , but the problem is: I wanted to send file from the text entered by the user, not from existing file in computers. – HoverPhoenix Jun 17 '12 at 09:50
  • You'll still need `FormData`, but you can begin explaining how JSZip works. Does it create a file? And where? Locally? On a server? – MaxArt Jun 17 '12 at 09:54
  • In the [JSZip](http://stuartk.com/jszip/) example, they use: `var content = zip.generate(); location.href="data:application/zip;base64,"+content;` I think the 'created' file is in the content variable. There is no real file created in the client computer. – HoverPhoenix Jun 17 '12 at 09:57
  • So all you have is a base64 string. This is a little bit complex, since if you *have* to send a file, and not a string, you'll have to create one first. The only browser that allows you to do that at the moment, supporting a decently complete FileAPI library, is Google Chrome. – MaxArt Jun 17 '12 at 10:05
  • oouucchh....so it's currently not possible to fake the file-creation and send it via XHR. Do you think I should send the textarea content as a plain string ? Would that be a good workaround ? – HoverPhoenix Jun 17 '12 at 10:18
  • I have tried the form data [here](https://developer.mozilla.org/en/DOM/XMLHttpRequest/FormData/Using_FormData_Objects), I sent a text file and it successfully uploaded to the server. Thanks a lot for the insight !! But then, I tried again to send the base64 string from JSZip, it is uploaded, but the file is corrupted in the server. Is this what you mean on the previous comment ? Or do you have any other workaround for this ? – HoverPhoenix Jun 17 '12 at 11:12
  • To be honest, I've forgot about the `Blob` way. So you can create a BlobBuilder object, append your data (after converting it from base64 using `atob`) and retrieving the `Blob` object using `builder.getBlob("application/zip")`. Then append the `Blob` to the `FormData` object. You're done. I'll edit the answer to respond to your actual demand. – MaxArt Jun 17 '12 at 11:34
1

I finally get it through !! Many thanks for MaxArt.

Basically, it is what MaxArt has said with Uint8Array conversion. I got the conversion reference from Eric Bidelman here.

Here's how I've done it:

var zip = new JSZip();
zip.file("hello1.txt", "Hello First World\n");
zip.file("hello2.txt", "Hello Second World\n");
var content = zip.generate(); //Generate dummy zip file (adjust to your need)

var oBlob = new (window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder)(); //Instantiate blob builder

var raw = atob(content);    //decode the base64 string
var rawLength = raw.length;
var uInt8Array = new Uint8Array(rawLength);
for (var i = 0; i < rawLength; ++i) { //convert to uInt8Array
    uInt8Array[i] = raw.charCodeAt(i);
}

oBlob.append(uInt8Array.buffer); //append it to blobbuilder
oMyForm.append("GradeRequest[file]", oBlob.getBlob("application/zip")); //because you create a zip file, so get the zip type

//send it
var oReq = new XMLHttpRequest();
oReq.open("POST", "{{the_url}}");
oReq.send(oMyForm);
Community
  • 1
  • 1
HoverPhoenix
  • 177
  • 1
  • 4
  • 13