angular.bind() for 'drop' event listener not behaving properly

11,778

Angular doesn't know when event handlers you have registered are triggered, so you need to notify Angular that you've changed scope variables. You do that with the $apply function.

$scope.$apply(function () {
    $scope.divClass = 'on-drag-enter';
});

When it comes to the drop handler you're correct--the statements after var droppedFiles = e.dataTransfer.files; are never run because of the runtime error that statement results in.

e is a jQuery event, whereas dataTransfer is a property on the actual DOM event. To fix the problem you need to access the original event through the originalEvent property.

var droppedFiles = e.originalEvent.dataTransfer.files;

Update

e.originalEvent is only needed if you have included jQuery before angular, but since you haven't done that in your plnkr, that solution was invalid, I apologize for that.

It seems like you need to opt in for drop support by preventDefault of the dragover event (Perhaps this can be done in other events as well--haven't tested.)

elem.bind('dragover', function (e) {
    e.stopPropagation();
    e.preventDefault();
}); 

Updated plnkr.

Share:
11,778
Scott
Author by

Scott

Updated on June 08, 2022

Comments

  • Scott
    Scott about 2 years

    Plunker demonstrating code

    I have a <div> element with the directive dragAndDrop set as an attribute:

    <div ng-class="divClass" drag-and-drop>Drop files here</div>
    

    (as a side, and less important note, ng-class is not behaving as expected, but that's a separate issue) What I would like is for users to drag and drop files from their desktop to the div. Upon doing so though, I use angular.bind() to detect the drop event. Of course once they drop, I call e.stopPropagation() and e.preventDefault(), but the page continues on with redirecting the page regardless. What am I doing wrong that would prevent the two aforementioned methods from executing?

    dragAndDrop directive:

    directive('dragAndDrop', function() {
      return {
        restrict: 'A',
        link: function($scope, elem, attr) {
          elem.bind('dragenter', function(e) {
            e.stopPropagation();
            e.preventDefault();
            // still can't get this one to behave:
            // https://stackoverflow.com/q/15419839/740318
            $scope.divClass = 'on-drag-enter';
          });
          elem.bind('dragleave', function(e) {
            e.stopPropagation();
            e.preventDefault();
            $scope.divClass = '';
          });
          elem.bind('drop', function(e) {
            var droppedFiles = e.dataTransfer.files;
            // It's as though the following two methods never occur
            e.stopPropagation();
            e.preventDefault();
    
            if (droppedFiles.length > 0) {
              for (var i=0,ii=droppedFiles.length;i<ii;i++) {
                $scope.files.push(droppedFiles[i]);
              }
            }
          });
        }
      };
    });
    
  • Scott
    Scott over 11 years
    Neither of those worked, for either issue. I've updated the plunker to reflect your recommendations. As for the $scope.$apply(), I presumed that was unnecessary given the angular.bind() method put on the element would keep scope, when using something such as .addEventListener() or elem.ondrop = function() would not. Am I mistaken?
  • Martin
    Martin over 11 years
    $scope.$apply (you missed the $) fixes the css class problems. (Gonna take a look at the other problem when I get home.)
  • Scott
    Scott over 11 years
    Doh! Good catch. Oddly enough, I seemingly recall trying that before based off previous advice from elsewhere. I must have either been making a mistake elsewhere or not trying it at all. Regardless, thank you. Looking forward to the ondrop solution.
  • Mark Rajcok
    Mark Rajcok over 11 years
    @Scott, when a bind() callback function runs, it runs "outside" of Angular, because bind() just sets up normal JavaScript event binding -- hence the need for us to manually call $apply(). It might be nice if Angular provided some kind of bindApply() or 'angularBind()` method that would automatically call $apply() for us after executing the callback function.
  • Scott
    Scott over 11 years
    So that begs the question, why even use angular.bind()? Formality?
  • Martin
    Martin over 11 years
    You're not using 'angular.bind' but their jqLite implementation (it's usually a good idea to include jQuery before Angular, so you get full jQuery support). bind (or on which is the 'new' syntax) is a good faqade around different browser implementations.
  • Scott
    Scott over 11 years
    dragover... That's a new one. Alas it does appear to solve the problem! Thanks for your assistance!
  • Alan
    Alan over 10 years
    You don't have to set the dropEffect, but you do need to stop the dragover event, otherwise the drop event won't fire.