8
votes

I have a site that allows a person to import some data. They click a button, and the file select opens and they select a file. When they select a file I open a dialog that they can't close that tells them their data is being imported. Once I get the call back from the api call to import the file, I then close that dialog and open a new one that gives the status of the import.

On Chrome the "please wait while importing" dialog closes as expected. On IE it doesn't. If you use IE 11 it should happen in the following fiddle:

http://jsfiddle.net/og6qsxdw/

You see a ghost like outline of the dialog go up and fade away like its trying to close but the dialog and overlay still remain.

<div ng-controller="MyCtrl">

  <input type="file" ng-simple-upload web-api-url="http://www.fakeresponse.com/api/?sleep=5" select-fn="importFileSelected" callback-fn="importDataComplete" />

  <script type="text/ng-template" id="templateId">

    <div>
      Getting Data
    </div>
  </script>

</div>

JavaScript/AngularJS code:

var myApp = angular.module('myApp', ['ngDialog', 'ngSimpleUpload']);

function MyCtrl($scope, $http, ngDialog) {

  $scope.importDataComplete = function() {
    $scope.dlg.close();
  }

  $scope.importFileSelected = function() {
    $scope.dlg = ngDialog.open({
      template: 'templateId',
      className: 'ngdialog-theme-default',
      closeByDocument: false,
      showClose: false
    });
  }
}


angular.module('ngSimpleUpload', [])
  .directive('ngSimpleUpload', [function() {
    return {
      scope: {
        webApiUrl: '@',
        callbackFn: '=',
        selectFn: '=',
        buttonId: '@'
      },
      link: function(scope, element, attrs) {
        // if button id value exists
        if (scope.buttonId) {
          $('#' + scope.buttonId).on('click', function() {
            // retrieves files from file input
            var files = element[0].files;
            // will not fire until file(s) are selected
            if (files.length == 0) {
              console.log('No files detected.');
              return false;
            }

            Upload(files);
          });
        } else {
          // original code, trigger upload on change
          element.on('change', function(evt) {
            var files = evt.__files_ || (evt.target && evt.target.files);

            Upload(files);

            // removes file(s) from input
            $(this).val('');
          });
        }

        function Upload(files) {
          var fd = new FormData();
          angular.forEach(files, function(v, k) {
            fd.append('file', files[k]);
          });

          // this tell us the user clicked open instead of cancel so we can start our overlay
          scope.selectFn();

          return $.ajax({
            type: 'GET',
            url: scope.webApiUrl,
            async: true,
            cache: false,
            contentType: false,
            processData: false
          }).done(function(d) {
            // callback function in the controller
            scope.callbackFn(d);
          }).fail(function(x) {
            console.log(x);
          });
        }
      }
    }
  }]);
1

1 Answers

7
votes

Alright, so here's the deal. In IE, when you open the dialog, two instances are instantiated. When the upload completes, you have a reference to close the most recent dialog, but one existed milliseconds before as well.

I had originally thought at quick glance that this was just an ugly IE bug, and you had to "keep track" of the instances, however, I failed to take note of jQuery's involvment in your link function. Thusly, my initial solution was a hack/workaround, but better can be done.

It seems that the mixture of the two libraries is the culprit, where Angular and jQuery are not communicating properly. I've inserted a reference below to a ticket that discusses jQuery events with Angular.

jQuery and AngularJS: Bind Events to Changing DOM

Solution

My suggestion, as always in these cases, is not to leverage jQuery on top of Angular. It adds an additional layer of confusion, and requires you to be prudent about maintaining proper coupling between the two (in circumstances such as this).

I have provided a solution, where I clean up your link function. It uses a lot of your existing code, but with the absence of the jQuery bits. It seems to work just fine for me in both Chrome and IE now.

http://plnkr.co/edit/6Z4Rzg1Zm3w5rYyqQqmg?p=preview

link: function(scope, element, attrs) {

        console.warn("Element is", element);

        // original code, trigger upload on change
        element.on('change', function(evt) {
          var files = evt.__files_ || (evt.target && evt.target.files);
          Upload(files);
        });

        function Upload(files) {
          var fd = new FormData();
          angular.forEach(files, function(v, k) {
            fd.append('file', files[k]);
            console.log("File loaded");
          });

          // this tell us the user clicked open instead of cancel so we can start our overlay
          scope.selectFn();


          $http({
            url: scope.webApiUrl,
            method: "GET",
            cache: true
          }).success(function(d, status, headers, config) {
            scope.callbackFn(d);
          }).error(function(data, status, headers, config) {
            console.warn("Request failed...");
          });

        }
      }