0

So I am having some struggle with the code below:

app.factory('sfAttachment', ['$http', '$q', '$window', '$rootScope', function($http, $q, $window, $rootScope) {

  var attachment = {};
  //Save to server function for attachments
  attachment.save = function(base64value, document, index) {

    /*Stripping the file type text in front of the base64 
      string, without this the file would show as corrupted */
    var position = base64value.indexOf("base64,");
    var matchWord = "base64,";
    var base64valueClean = base64value.slice(position + matchWord.length, base64value.length);

    //Setting payload to be saved in SF database.
    var data = {
      "Body": base64valueClean,
      "ContentType": document.attachmentContentType,
      "ParentId": document.id,
      "Name": document.fileName
    };

    /*Get the {!URLFOR('/services/data/v26.0/sobjects/Attachment/')} value
      cannot be processed on static ressource, hence the link to the window
      global variable.*/
    var url = $window.__url;
    var method = 'POST';

    var request = {

      url: url,
      method: method,
      data: data,
      headers: {
            __XHR__: function() {
                return function(xhr) {
                    xhr.upload.addEventListener("progress", function(event) {

                      $rootScope.text = event.loaded/event.total;
                      $rootScope.$apply();
                        console.log("uploaded " + ((event.loaded/event.total) * 100) + "%");

                    });
                };
            }
        }
    };

    console.log(request);

    //Promise type approach to Http request, allows easy handle of succes and failure
    // Very useful for asynchronous calls.
    var deferred = $q.defer();

    //Performing http request to Server
    $http(request).then(function(response) {

      deferred.resolve(response);
      console.log('File UPLOADED to SF!');

    }, function(event) {

      //Need to Improve error handling!!!
      deferred.reject('The attachment could not be saved:' + event);

    });


    return deferred.promise;
  }

This service purpose is to load an Attachment into Salesforce and it works great, but then I added a piece of code

headers: {
    __XHR__: function() {
        return function(xhr) {
          xhr.upload.addEventListener("progress", function(event) {

            $rootScope.text = event.loaded / event.total;
            $rootScope.$apply();
            console.log("uploaded " + ((event.loaded / event.total) * 100) + "%");

          });
        };

to track the progress of the upload and it successfully output to the console the percentage, what I am trying to achieve is pass the progress percentage to the controller calling this service, and I am struggling a bit with that considering I already have a promise in place, not really sure how to properly grab the text, here my attempt is with $rootscope.text and setting up a watch in my controller and it works but is there a more elegant/proper way of doing it?

$rootScope.$watch('text', function(newValue, oldValue, scope) {
  console.log($rootScope.text);
});
JLRishe
  • 99,490
  • 19
  • 131
  • 169
Tekill
  • 1,171
  • 1
  • 14
  • 30
  • 1
    Did you consider using `$rootScope.$broadcast("sfProgress",event)`? https://docs.angularjs.org/api/ng/type/$rootScope.Scope – georgeawg Dec 17 '15 at 00:34
  • Another promise is not going to help you in this situation - promises are not designed for "progress" type scenarios. – Steve Campbell Dec 17 '15 at 02:34

2 Answers2

1

Looks like the $broadcast function might serve you well here. Check out this post for a well explained answer: $on and $broadcast in angular

You can find the documentation for $broadcast and $on here

Community
  • 1
  • 1
Dan Keiger
  • 671
  • 5
  • 12
  • Thought about this and yes I could use that but I was hoping for something in the likes of @JLRishe's response. Thank you though for the suggestion – Tekill Dec 17 '15 at 12:27
1

Angular's $q promises do provide a facility for providing progress updates. You should be able to construct such a promise like this:

app.factory('sfAttachment', [
    '$http', '$q', '$window', '$rootScope', function ($http, $q, $window, $rootScope) {

        var attachment = {};
        //Save to server function for attachments
        attachment.save = function (base64value, document, index) {

            /*Stripping the file type text in front of the base64 
              string, without this the file would show as corrupted */
            var position = base64value.indexOf("base64,");
            var matchWord = "base64,";
            var base64valueClean = base64value.slice(position + matchWord.length, base64value.length);

            //Setting payload to be saved in SF database.
            var data = {
                "Body": base64valueClean,
                "ContentType": document.attachmentContentType,
                "ParentId": document.id,
                "Name": document.fileName
            };

            /*Get the {!URLFOR('/services/data/v26.0/sobjects/Attachment/')} value
              cannot be processed on static ressource, hence the link to the window
              global variable.*/
            var url = $window.__url;
            var method = 'POST';

            var deferred = $q.defer();

            var request = {
                url: url,
                method: method,
                data: data,
                headers: {
                    __XHR__: function () {
                        return function (xhr) {
                            xhr.upload.addEventListener("progress", function (event) {
                                var pct = event.loaded / event.total;
                                // notify here
                                deferred.notify(pct);
                                console.log("uploaded " + (pct * 100) + "%");
                            });
                        };
                    }
                }
            };

            $http(request).then(function (result) {
                deferred.resolve(result);
            }, function (error) {
                deferred.reject(error);
            });

            return deferred.promise;
        };

        return attachment;
    }
]);

And then you can consume it like this:

sfAttachment.save(value, document, index)
    .then(function (result) {
        console.log('finished downloading');
    },
    null,
    function (pct) {
        $scope.downloadPct = pct;
    })
    .catch(function (error) {
        console.log('oh noes!');
    });

To chain two file uploads:

sfAttachment.save(file1, document, index)
    .then(function (result) {
        return sfAttachment.save(file2, document, index);
    }, null, function (pct) {
        $scope.downloadPct = pct;
    })
    .then(null, null, function (pct) {
        $scope.downloadPct2 = pct;
    })
    .catch(function (error) {
        console.log('oh noes!');
    });
JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • This is really useful, thank you @JLRishe why the use of catch in the second snippet? why not handle the error where your null is instead? what is the reasoning behind having null here? – Tekill Dec 17 '15 at 12:25
  • Also is the `$q.defer` not necessary and if so why? – Tekill Dec 17 '15 at 12:31
  • 1
    @Yourinium The approach I used allows you to catch any errors that happen in the `.then()` handler. The use of `.then(success, fail)` is known as the [`.then(success, fail)` antipattern](https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns#the-thensuccess-fail-anti-pattern) and is discouraged because it's not idiomatic and can lead to uncaught errors. – JLRishe Dec 17 '15 at 12:33
  • 1
    @Yourinium Using deferreds is a mostly outdated way of creating promises and is problematic because you can wind up with errors being thrown outside of the promise chain and this makes them harder to catch. Creating promises with a promise constructor `$q(function)` is more robust and is the better approach in almost all cases. – JLRishe Dec 17 '15 at 12:37
  • Thank you that clears it up for me! All this was very useful. – Tekill Dec 17 '15 at 15:39
  • if I have nested promises in my controller can I just put the catch at the end?like so: `sfAttachment.save.then(function (result) { sfAttachment.bind.then(function (result) { console.log('finished downloading'); }, null, function (pct) { $scope.downloadPct2 = pct; }); }, null, function (pct) { $scope.downloadPct = pct; }) .catch(function (error) { console.log('oh noes!'); });` – Tekill Dec 17 '15 at 15:53
  • Also when I ran the code I got the following error `progress is not a function` suggestions? – Tekill Dec 17 '15 at 15:57
  • I can't get the progress pct to be returned into the controller... @JLRishe – Tekill Dec 17 '15 at 16:18
  • 1
    @Yourinium Sorry, I missed the part in the documentation where it said that only deferreds support progress notification. I've switched my code to using a deferred. Regarding the code example you posted here in the comments, I don't entirely understand it. What is `sfAttachment.bind`? – JLRishe Dec 17 '15 at 16:38
  • yup now it works, I guess my other question was pertaining to chaining promises do I put a catch after each promise or just at the end of all of them...I seem to recall the doc to say at the end but I wanted to check. – Tekill Dec 17 '15 at 16:54
  • 1
    @Yourinium Yeah, you would typically want to put the catch at the end of the chain. Something like: `sfAttachment.save(firstFile).then(function (result) { return sfAttachment.save(secondFile); }, null, function (pct) { $scope.downloadPct = pct; }); }).then(null, null, function (pct) { $scope.downloadPct2 = pct; }).catch(function (error) { console.log('oh noes!'); });` – JLRishe Dec 17 '15 at 16:59
  • Sweet Thanks that's what I thought!! – Tekill Dec 17 '15 at 17:01