Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJs dynamic download from response

I have written a directive based on Scott's answer. You'd use it like so:

<button class="btn btn-success"
        download-response="getData()"
        download-success="getDataSuccess()"
        download-error="getDataError()"
        download-name="{{name}}.pdf"
        download-backup-url="/Backup/File.pdf">
    Save
</button>

Issue: the code below throws an error TypeError: Invalid calling object in IE11 on the first method (line: saveBlob(blob, filename);). Even though it falls back to other methods of downloading, it is my understanding that the saveMethod1 should work in IE11.

Here is the code:

'use strict';

// directive allows to provide a function to be executed to get data to be downloaded
// attributes:
// download-response - Required. Function to get data. It must return a promise. It must be declared on the $scope.
// download-success - Optional. Function to be executed if download-response function was successfully resolved. It must be declared on the $scope.
// download-error - Optional. Function to be executed if download-response function return a promise rejection. It must be declared on the $scope.
// download-mime - Optional. provide a mime type of data being downloaded. Defaulted to "application/octet-stream"
// download-name - Optional. name of the file to download. Defaulted to "download.bin"
// download-backup-url - in case browser does not support dynamic download, this url will be called to get the file
angular.module('app.directives')
    .directive('downloadResponse', [ '$parse', '$timeout',
        function ($parse, $timeout) {

            function saveMethod1(data, filename, contentType) {
                // Support for saveBlob method (Currently only implemented in Internet Explorer as msSaveBlob, other extension in case of future adoption)
                var saveBlob = navigator.msSaveBlob || navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob;

                if (saveBlob) {
                    // Save blob is supported, so get the blob as it's contentType and call save.
                    var blob = new Blob([data], { type: contentType });
                    saveBlob(blob, filename);
                    //console.log("SaveBlob Success");
                } else {
                    throw 'saveBlob is not supported. Falling back to the next method';
                }
            }

            function saveMethod2(data, filename, contentType, octetStreamMime) {
                // Get the blob url creator
                var urlCreator = window.URL || window.webkitURL || window.mozURL || window.msURL;
                if (urlCreator) {
                    // Try to use a download link
                    var link = document.createElement("a");
                    var url;
                    if ("download" in link) {
                        // Prepare a blob URL
                        var blob = new Blob([data], { type: contentType });
                        url = urlCreator.createObjectURL(blob);
                        link.setAttribute("href", url);

                        // Set the download attribute (Supported in Chrome 14+ / Firefox 20+)
                        link.setAttribute("download", filename);

                        // Simulate clicking the download link
                        var event = document.createEvent('MouseEvents');
                        event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
                        link.dispatchEvent(event);

                        //console.log("Download link Success");
                    } else {
                        // Prepare a blob URL
                        // Use application/octet-stream when using window.location to force download
                        var blob = new Blob([data], { type: octetStreamMime });
                        url = urlCreator.createObjectURL(blob);
                        window.location = url;

                        //console.log("window.location Success");
                    }
                } else {
                    throw 'UrlCreator not supported. Falling back to the next method';
                }
            }

            function saveMethod3(attrs) {
                if (attrs.downloadBackupUrl && attrs.downloadBackupUrl != '') {
                    console.log('opening ' + attrs.downloadBackupUrl);
                    window.open('http://' + document.domain + attrs.downloadBackupUrl, '_blank');
                } else {
                    throw 'Could not download a file using any of the available methods. Also you did not provide a backup download link. No more bullets left...';
                }
            }

            return {
                restrict: 'A',
                scope: false,
                link:function (scope, elm, attrs) {
                    var getDataHandler = $parse(attrs.downloadResponse);

                    elm.on('click', function() {
                        var promise = getDataHandler(scope);
                        promise.then(
                            function (data) {
                                if (attrs.downloadSuccess && attrs.downloadSuccess != '') {
                                    var successHandler = $parse(attrs.downloadSuccess);
                                    successHandler(scope);
                                }

                                var octetStreamMime = "application/octet-stream";

                                var filename = attrs.downloadName || "download.bin";
                                var contentType = attrs.downloadMime || octetStreamMime;


                                try {
                                    saveMethod1(data, filename, contentType);
                                    return;
                                } catch (e) {
                                    console.log(e);
                                    try {
                                        saveMethod2(data, filename, contentType, octetStreamMime);
                                        return;
                                    } catch (e) {
                                        console.log(e);
                                        try {
                                            saveMethod3(attrs);
                                            return;
                                        } catch (e) {
                                            throw e;
                                        } 
                                        throw e;
                                    } 
                                    throw e;
                                }
                            },
                            function(data) {
                                if (attrs.downloadError && attrs.downloadError != '') {
                                    var errorHandler = $parse(attrs.downloadError);
                                    errorHandler(scope);
                                }
                            }
                        );
                    });

                }
            };
        }
    ]);

Any help is greatly appreciated!

like image 868
Dmitry Efimenko Avatar asked Sep 26 '14 22:09

Dmitry Efimenko


1 Answers

Problem:

I have resolved this issue. It appears that Internet Explorer 11 does not like aliases to the msSaveBlob function, demonstrated by the simplest examples:

// Succeeds
navigator.msSaveBlob(new Blob(["Hello World"]), "test.txt");

// Fails with "Invalid calling object"
var saveBlob = navigator.msSaveBlob;
saveBlob(new Blob(["Hello World"]), "test.txt");

So essentially the generic alias created to encapsulate saveBlob functionality, which should be permissible, prevents msSaveBlob from working as expected:

var saveBlob = navigator.msSaveBlob || navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob;

Workaround:

So the work around is to test for msSaveBlob separately.

if(navigator.msSaveBlob)
    navigator.msSaveBlob(blob, filename);
else {
    // Try using other saveBlob implementations, if available
    var saveBlob = navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob;
    if(saveBlob === undefined) throw "Not supported";
    saveBlob(blob, filename);
}

tl;dr

So the msSaveBlob method works, as long as you don't alias the function. Perhaps it's a security precaution - but then perhaps a security exception would have been more appropriate, though I think it is most likely a flaw, considering the source. :)

like image 135
Scott Avatar answered Oct 20 '22 02:10

Scott