Click everywhere but here event

ghz 1years ago ⋅ 783 views

Question

Was wondering how I would implement a 'Click everywhere but on this element' event.

I have something that you can compare to a list of files in a file explorer. You can select certain elements but if you click outside the element controller it needs to deselect everything.

enter image description here

Added a screenshot to make it more clear. So what I want to do is that if I click anywhere but on the language elements it should fire an event.

Update

To clarify I am not asking how I can do this with jQuery.


Answer

EDIT: There were a couple of problems in this old, old answer.

*Also: Marking Community Wiki (no points for me) because errors

  1. N calls for N uses of the directive. This probably isn't desirable for uses within the same scope with matching expressions.

  2. NOTHING WAS TEARING DOWN THE EVENT HANDLERS!!!! BAD! BAD! BAD!

So, I'm updating this answer. Hopefully it didn't cause anyone too much trouble.

Updated answer

Here's a new plunker with those issues fixed ... there are likely other things that individual application developers will run into. This is just an example of how to handle this problem.

app.factory('clickAnywhereButHereService', function($document){
  var tracker = [];
  
  return function($scope, expr) {
    var i, t, len;
    for(i = 0, len = tracker.length; i < len; i++) {
      t = tracker[i];
      if(t.expr === expr && t.scope === $scope) {
        return t;    
      }
    }
    var handler = function() {
      $scope.$apply(expr);
    };
    
    $document.on('click', handler);
    
    // IMPORTANT! Tear down this event handler when the scope is destroyed.
    $scope.$on('$destroy', function(){
      $document.off('click', handler);
    });
    
    t = { scope: $scope, expr: expr };
    tracker.push(t);
    return t;
  };
});

app.directive('clickAnywhereButHere', function($document, clickAnywhereButHereService){
  return {
    restrict: 'A',
    link: function(scope, elem, attr, ctrl) {
      var handler = function(e) {
        e.stopPropagation();
      };
      elem.on('click', handler);
      
      scope.$on('$destroy', function(){
        elem.off('click', handler);
      });
      
      clickAnywhereButHereService(scope, attr.clickAnywhereButHere);
    }
  };
});

Original answer (with fixes for teardown of event handlers)

You were close with the one answer you've found, but I've put together a plunk for you to show you what it was missing.

app.directive('clickAnywhereButHere', function($document){
  return {
    restrict: 'A',
    link: function(scope, elem, attr, ctrl) {
      var elemClickHandler = function(e) {
        e.stopPropagation();
      };
      
      var docClickHandler = function() {
        scope.$apply(attr.clickAnywhereButHere);
      };
      
      elem.on('click', elemClickHandler);
      $document.on('click', docClickHandler);
      
      // teardown the event handlers when the scope is destroyed.
      scope.$on('$destroy', function() {
        elem.off('click', elemClickHandler);
        $document.off('click', docClickHandler);
      });
    }
  }
})

HTML

<a click-anywhere-but-here="clickedSomewhereElse()" 
  ng-click="clickedHere()">Don't Click Me!</a>