单击外部以使用按钮隐藏容器

Click outside to hide container with button

本文关键字:隐藏 按钮 外部 单击      更新时间:2023-09-26

当用户单击或触摸外部时,我使用以下Angular指令来隐藏div元素。

https://github.com/TheSharpieOne/angular-off-click

它按预期工作,但当您在div外部单击切换div的按钮时,会触发"angular off click"回调以隐藏容器,但随后会调用该按钮附带的切换函数,重新打开div。

"关闭点击过滤器"通过添加异常来解决这个问题,这些异常在调用隐藏函数之前使用css选择器进行检查。然而,这被删除了,因为我们不希望在html标记中出现css类异常的额外膨胀。

所需的功能是当您点击容器外部时,切换按钮不会触发其处理程序

更新这只是触摸设备上的一个问题,默认情况下有300毫秒的延迟。因此,这意味着触发回调以隐藏容器,然后切换函数在300ms后运行,重新打开容器。在桌面上,点击鼠标,切换功能首先启动,然后回调

// Angular App Code
var app = angular.module('myApp', ['offClick']);
app.controller('myAppController', ['$scope', '$timeout', function($scope,$timeout) {
  $scope.showContainer = false;
  
  $scope.toggleContainer = function() {
    $timeout(function() {
      $scope.showContainer = !$scope.showContainer;
    }, 300);
    
  };
  
  $scope.hideContainer = function(scope, event, p) {
    $scope.showContainer = false;
    console.log('event: ', event);
    console.log('scope: ', scope);
    console.log(p);
  };
}]);
// Off Click Directive Code
angular.module('offClick', [])
    .directive('offClick', ['$rootScope', '$parse', function ($rootScope, $parse) {
    var id = 0;
    var listeners = {};
    // add variable to detect touch users moving..
    var touchMove = false;
    // Add event listeners to handle various events. Destop will ignore touch events
    document.addEventListener("touchmove", offClickEventHandler, true);
    document.addEventListener("touchend", offClickEventHandler, true);
    document.addEventListener('click', offClickEventHandler, true);
    function targetInFilter(target, elms) {
        if (!target || !elms) return false;
        var elmsLen = elms.length;
        for (var i = 0; i < elmsLen; ++i) {
            var currentElem = elms[i];
            var containsTarget = false;
            try {
                containsTarget = currentElem.contains(target);
            } catch (e) {
                // If the node is not an Element (e.g., an SVGElement) node.contains() throws Exception in IE,
                // see https://connect.microsoft.com/IE/feedback/details/780874/node-contains-is-incorrect
                // In this case we use compareDocumentPosition() instead.
                if (typeof currentElem.compareDocumentPosition !== 'undefined') {
                    containsTarget = currentElem === target || Boolean(currentElem.compareDocumentPosition(target) & 16);
                }
            }
            if (containsTarget) {
                return true;
            }
        }
        return false;
    }
    function offClickEventHandler(event) {
        // If event is a touchmove adjust touchMove state
        if( event.type === 'touchmove' ){
            touchMove = true;
            // And end function
            return false;
        }
        // This will always fire on the touchend after the touchmove runs...
        if( touchMove ){
            // Reset touchmove to false
            touchMove = false;
            // And end function
            return false;
        }
        var target = event.target || event.srcElement;
        angular.forEach(listeners, function (listener, i) {
            if (!(listener.elm.contains(target) || targetInFilter(target, listener.offClickFilter))) {
                $rootScope.$evalAsync(function () {
                    listener.cb(listener.scope, {
                        $event: event
                    });
                });
            }
        });
    }
    return {
        restrict: 'A',
        compile: function ($element, attr) {
            var fn = $parse(attr.offClick);
            return function (scope, element) {
                var elmId = id++;
                var offClickFilter;
                var removeWatcher;
                offClickFilter = document.querySelectorAll(scope.$eval(attr.offClickFilter));
                if (attr.offClickIf) {
                    removeWatcher = $rootScope.$watch(function () {
                        return $parse(attr.offClickIf)(scope);
                    }, function (newVal) {
                        if (newVal) {
                            on();
                        } else if (!newVal) {
                            off();
                        }
                    });
                } else {
                    on();
                }
                attr.$observe('offClickFilter', function (value) {
                    offClickFilter = document.querySelectorAll(scope.$eval(value));
                });
                scope.$on('$destroy', function () {
                    off();
                    if (removeWatcher) {
                        removeWatcher();
                    }
                    element = null;
                });
                function on() {
                    listeners[elmId] = {
                        elm: element[0],
                        cb: fn,
                        scope: scope,
                        offClickFilter: offClickFilter
                    };
                }
                function off() {
                    listeners[elmId] = null;
                    delete listeners[elmId];
                }
            };
        }
    };
}]);
/* Styles go here */
.container {
  background: blue;
  color: #fff;
  height: 300px;
  width: 300px;
}
<!DOCTYPE html>
<html>
  <head>
    <script src="https://code.angularjs.org/1.4.0/angular.js"></script>
    <link rel="stylesheet" href="style.css" />
    
    
  </head>
  <body data-ng-app="myApp">
    <h1>Hello Plunker!</h1>
    <div data-ng-controller="myAppController">
      
      <button data-ng-click="toggleContainer()">Toggle Container</button>
      
      <div class="container" data-ng-show="showContainer" data-off-click="hideContainer()" data-off-click-if="showContainer">
        This is the container
      </div>
    </div>
    
  </body>
</html>

http://jsbin.com/hibovu

问题是,当你点击按钮时,两个功能都会启动:

  1. 指令中的hideContainer
  2. 点击事件中的toggleContainer(再次显示div

解决方案

在评估hide回调之前添加event.stopPropagation();

你是怎么做到的?

  1. 将事件传递给函数data-off-click="hideContainer($event)"
  2. $scopehideContainer函数的定义中添加$event参数,如下所示:$scope.hideContainer = function($event)

完整代码:

// Angular App Code
var app = angular.module('myApp', ['offClick']);
app.controller('myAppController', ['$scope', '$timeout', function($scope,$timeout) {
  $scope.showContainer = false;
  $scope.toggleContainer = function() {
    $timeout(function() {
      $scope.showContainer = !$scope.showContainer;
    }, 300);
  };
  $scope.hideContainer = function($event) {
    $event.stopPropagation();
    $timeout(function(){
      $scope.showContainer = false;  
    });
  };
}]);
// Off Click Directive Code
angular.module('offClick', [])
.directive('offClick', ['$rootScope', '$parse', function ($rootScope, $parse) {
  var id = 0;
  var listeners = {};
  // add variable to detect touch users moving..
  var touchMove = false;
  // Add event listeners to handle various events. Destop will ignore touch events
  document.addEventListener("touchmove", offClickEventHandler, true);
  document.addEventListener("touchend", offClickEventHandler, true);
  document.addEventListener('click', offClickEventHandler, true);
  function targetInFilter(target, elms) {
    if (!target || !elms) return false;
    var elmsLen = elms.length;
    for (var i = 0; i < elmsLen; ++i) {
      var currentElem = elms[i];
      var containsTarget = false;
      try {
        containsTarget = currentElem.contains(target);
      } catch (e) {
        // If the node is not an Element (e.g., an SVGElement) node.contains() throws Exception in IE,
        // see https://connect.microsoft.com/IE/feedback/details/780874/node-contains-is-incorrect
        // In this case we use compareDocumentPosition() instead.
        if (typeof currentElem.compareDocumentPosition !== 'undefined') {
          containsTarget = currentElem === target || Boolean(currentElem.compareDocumentPosition(target) & 16);
        }
      }
      if (containsTarget) {
        return true;
      }
    }
    return false;
  }
  function offClickEventHandler(event) {
    // If event is a touchmove adjust touchMove state
    if( event.type === 'touchmove' ){
      touchMove = true;
      // And end function
      return false;
    }
    // This will always fire on the touchend after the touchmove runs...
    if( touchMove ){
      // Reset touchmove to false
      touchMove = false;
      // And end function
      return false;
    }
    var target = event.target || event.srcElement;
    angular.forEach(listeners, function (listener, i) {
      if (!(listener.elm.contains(target) || targetInFilter(target, listener.offClickFilter))) {
        //$rootScope.$evalAsync(function () {
        listener.cb(listener.scope, {
          $event: event
        });
        //});
      }
    });
  }
  return {
    restrict: 'A',
    compile: function ($element, attr) {
      var fn = $parse(attr.offClick);
      return function (scope, element) {
        var elmId = id++;
        var offClickFilter;
        var removeWatcher;
        offClickFilter = document.querySelectorAll(scope.$eval(attr.offClickFilter));
        if (attr.offClickIf) {
          removeWatcher = $rootScope.$watch(function () {
            return $parse(attr.offClickIf)(scope);
          }, function (newVal) {
            if (newVal) {
              on();
            } else if (!newVal) {
              off();
            }
          });
        } else {
          on();
        }
        attr.$observe('offClickFilter', function (value) {
          offClickFilter = document.querySelectorAll(scope.$eval(value));
        });
        scope.$on('$destroy', function () {
          off();
          if (removeWatcher) {
            removeWatcher();
          }
          element = null;
        });
        function on() {
          listeners[elmId] = {
            elm: element[0],
            cb: fn,
            scope: scope,
            offClickFilter: offClickFilter
          };
        }
        function off() {
          listeners[elmId] = null;
          delete listeners[elmId];
        }
      };
    }
  };
}]);
.container {
  background: blue;
  color: #fff;
  height: 300px;
  width: 300px;
}
<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
  </head>
  <body data-ng-app="myApp">
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
    <h1>Hello Plunker!</h1>
    <div data-ng-controller="myAppController">
      <button data-ng-click="toggleContainer()">Toggle Container</button>
      <div class="container" data-ng-show="showContainer" data-off-click="hideContainer($event)" data-off-click-if="showContainer">
        This is the container
      </div>
    </div>
  </body>
</html>

http://jsbin.com/hibovu/3/edit?html,css,js