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);
});
2
  • 1
    Did you consider using $rootScope.$broadcast("sfProgress",event)? docs.angularjs.org/api/ng/type/$rootScope.Scope Commented Dec 17, 2015 at 0:34
  • Another promise is not going to help you in this situation - promises are not designed for "progress" type scenarios. Commented Dec 17, 2015 at 2:34

2 Answers 2

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!');
    });
Sign up to request clarification or add additional context in comments.

12 Comments

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?
@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 and is discouraged because it's not idiomatic and can lead to uncaught errors.
@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.
@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?
@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!'); });
|
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

1 Comment

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.