Monday, April 08, 2013

Upload Multiple Images in WinJS

This post shows a WinJS/HTML solution to upload multiple images in Windows 8. The same method can apply to other binary files, not limited to images.

Requirement : use HTML POST to upload multiple images to a server at one shot with some form values

Solution : use FormData to build the raw data and use WinJS.xhr to post the data

Code :

    /**
     * Select multiple images and upload to the server
     * @param string url: Server URL
     * @param array formFields: other form values to send
     * @return a Promise object
     */ 
    function uploadMultipleImages(url, formFields) {
        return new WinJS.Promise(function (c, e, p) {
            var dataToSend = new FormData();
            var filePicker = new Windows.Storage.Pickers.FileOpenPicker();
            filePicker.fileTypeFilter.replaceAll([".jpg", ".jpeg"]);
            filePicker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.picturesLibrary;

            filePicker.pickMultipleFilesAsync().then(function (files) {
                if (files.length === 0) {
                    console.log("No file selected");
                    return;
                }
                if (formFields && formFields.length > 0) {
                    formFields.forEach(function (formField) {
                        for (var key in formField) {
                            dataToSend.append(key, formField[key]); // Add field value
                        }
                    });
                }

                var imgPromises = []; // Promises for all images access 
                files.forEach(function (file, index) {
                    imgPromises.push(file.openAsync(Windows.Storage.FileAccessMode.read));
                });

                WinJS.Promise.join(imgPromises).then(function (data) { // Wait for all promises 
                    for (var i = 0; i < data.length; i++) {
                        // data[i] is the file stream
                        var blob = MSApp.createBlobFromRandomAccessStream("image/jpg", data[i]);
                        dataToSend.append(files[i].name, blob, files[i].name);
                    }

                    var options = { url: url, type: "POST", data: dataToSend };
                    WinJS.xhr(options).then(function (response) {
                        c(response);
                    },
                    function (xhrError) {
                        e(xhrError);
                    });
                },
                function (joinError) {
                    e(joinError);
                });
            });
        });
    }

Usage example :

        var url = "http://testServer/FileUpload/OCR/";
        var formValues = [];
        formValues.push({ Token: "1234567890" });
        uploadMultipleImages(url, formValues).then(function (response) {
            var result = response.responseText;
            //DO STUFF
        });

Note : It's important to include the third parameter when appending blob data:

    var blob = MSApp.createBlobFromRandomAccessStream("image/jpg", data[i]);
    dataToSend.append(files[i].name, blob, files[i].name);
This will result in following HTTP POST:
-----------------------------multipart-form-data-boundary
Content-Disposition: form-data; name="File1.JPG"; filename="File1.JPG"
Content-Type: image/jpg;

--image binary--
If the third parameter is skipped: formData.append(objName, objValue); then the filename property will be set to "blob" as below which is not accepted in many server implementations:
-----------------------------multipart-form-data-boundary
Content-Disposition: form-data; name="File1.JPG"; filename="blob"
Content-Type: image/jpg;

--image binary--