AngularJS下拉指令单击外部时隐藏

AngularJS下拉指令单击外部时隐藏,angularjs,Angularjs,我正在尝试创建一个带有复选框和过滤器选项的多选下拉列表。我试图通过单击“外部”将列表隐藏起来,但无法找到隐藏的方式。谢谢你的帮助 我是通过监听一个全局点击事件来实现的,比如: .directive('globalEvents', ['News', function(News) { // Used for global events return function(scope, element) { // Listens for a mouse click

我正在尝试创建一个带有复选框和过滤器选项的多选下拉列表。我试图通过单击“外部”将列表隐藏起来,但无法找到隐藏的方式。谢谢你的帮助


我是通过监听一个全局点击事件来实现的,比如:

.directive('globalEvents', ['News', function(News) {
    // Used for global events
    return function(scope, element) {
        // Listens for a mouse click
        // Need to close drop down menus
        element.bind('click', function(e) {
            News.setClick(e.target);
        });
    }
}])
然后,事件本身通过新闻服务进行广播

angular.factory('News', ['$rootScope', function($rootScope) {
    var news = {};
    news.setClick = function( target ) {
        this.clickTarget = target;
        $rootScope.$broadcast('click');
    };
}]);
然后,您可以在任何需要的地方收听广播。以下是一个示例指令:

.directive('dropdown', ['News', function(News) {
  // Drop down menu für the logo button
  return {
    restrict: 'E',
    scope: {},
    link: function(scope, element) {
      var opened = true;
      // Toggles the visibility of the drop down menu
      scope.toggle = function() {
        element.removeClass(opened ? 'closed' : 'opened');
        element.addClass(opened ? 'opened' : 'closed');
      };
      // Listens for the global click event broad-casted by the News service
      scope.$on('click', function() {
        if (element.find(News.clickTarget.tagName)[0] !== News.clickTarget) {
          scope.toggle(false);
        }
      });
      // Init
      scope.toggle();
    }
  }
}])
const ClickModule = angular
.module('clickOutside', [])
.directive('clickOutside', ['$document', function ($document) {
    return {
        restrict: 'A',
        scope: {
            clickOutside: '&'
        },
        link: function (scope, el, attr) {
            const handler = function (e) {
                if (el !== e.target && !el[0].contains(e.target)) {
                    scope.$apply(function () {
                        console.log("hiiii");
                        //  whatever expression you assign to the click-outside attribute gets executed here
                        //  good for closing dropdowns etc
                        scope.$eval(scope.clickOutside);
                    });
                }
            }

            $document.on('click', handler);

            scope.$on('$destroy', function() {
                $document.off('click', handler);
            });
        }
    }
}]);
我希望有帮助

好的,我必须调用$apply(),因为事件发生在angular world之外(根据文档)

注意,您的解决方案(问题中提供的Plunker)在打开第二个弹出窗口(在具有多个选择的页面上)时不会关闭其他框的弹出窗口

通过点击一个框打开一个新的弹出窗口,点击事件将始终停止。事件将永远不会到达任何其他打开的弹出窗口(以关闭它们)

我通过删除
事件.stopPropagation()解决了这个问题行并匹配弹出窗口的所有子元素

只有当events元素与弹出窗口的任何子元素不匹配时,弹出窗口才会关闭

我将指令代码更改为以下内容:

select.html(指令代码)

我用叉子叉了你的叉子并应用了更改:

bower install angular-click-outside --save
npm install @iamadamjowett/angular-click-outside
yarn add @iamadamjowett/angular-click-outside
angular.module('myApp', ['angular-click-outside'])

//in your html
<div class="menu" click-outside="closeThis">
...
</div>

//And then in your controller
$scope.closeThis = function () {
    console.log('closing');
}

屏幕截图:

bower install angular-click-outside --save
npm install @iamadamjowett/angular-click-outside
yarn add @iamadamjowett/angular-click-outside
angular.module('myApp', ['angular-click-outside'])

//in your html
<div class="menu" click-outside="closeThis">
...
</div>

//And then in your controller
$scope.closeThis = function () {
    console.log('closing');
}

我对提供的答案并不完全满意,所以我自己做了答案。改进:

  • 更具防御性的范围更新。将检查应用/摘要是否已在进行中
  • 当用户按下退出键时,div也将关闭
  • div关闭时,窗口事件解除绑定(防止泄漏)
  • 销毁作用域时,窗口事件将解除绑定(防止泄漏)

    函数链接(范围、$element、属性、$window){

    }

我将
$window
直接输入到link函数中。但是,您不需要完全这样做就可以获得
$window

function directive($window) {
    return {
        restrict: 'AE',
        link: function(scope, $element, attributes) {
            link.call(null, scope, $element, attributes, $window);
        }
    };
}

这是一篇老文章,但为了帮助这里的任何人,这是一个点击外部的工作示例,它不依赖任何东西,只依赖角度

module('clickOutside', []).directive('clickOutside', function ($document) {

        return {
           restrict: 'A',
           scope: {
               clickOutside: '&'
           },
           link: function (scope, el, attr) {

               $document.on('click', function (e) {
                   if (el !== e.target && !el[0].contains(e.target)) {
                        scope.$apply(function () {
                            scope.$eval(scope.clickOutside);
                        });
                    }
               });
           }
        }

    });

有一个很酷的指令叫做
angular click out
。您可以在项目中使用它。它使用起来非常简单:

使用

安装:

bower install angular-click-outside --save
npm install @iamadamjowett/angular-click-outside
yarn add @iamadamjowett/angular-click-outside
angular.module('myApp', ['angular-click-outside'])

//in your html
<div class="menu" click-outside="closeThis">
...
</div>

//And then in your controller
$scope.closeThis = function () {
    console.log('closing');
}
用法:

bower install angular-click-outside --save
npm install @iamadamjowett/angular-click-outside
yarn add @iamadamjowett/angular-click-outside
angular.module('myApp', ['angular-click-outside'])

//in your html
<div class="menu" click-outside="closeThis">
...
</div>

//And then in your controller
$scope.closeThis = function () {
    console.log('closing');
}
angular.module('myApp',['angular-click-out']))
//在html中
...
//然后在你的控制器里
$scope.closeThis=函数(){
console.log(“关闭”);
}

Danny F发布的答案非常棒,几乎完整,但是ịnh的评论是正确的,因此下面是我修改的指令,用于删除指令$destroy事件的侦听器:

.directive('dropdown', ['News', function(News) {
  // Drop down menu für the logo button
  return {
    restrict: 'E',
    scope: {},
    link: function(scope, element) {
      var opened = true;
      // Toggles the visibility of the drop down menu
      scope.toggle = function() {
        element.removeClass(opened ? 'closed' : 'opened');
        element.addClass(opened ? 'opened' : 'closed');
      };
      // Listens for the global click event broad-casted by the News service
      scope.$on('click', function() {
        if (element.find(News.clickTarget.tagName)[0] !== News.clickTarget) {
          scope.toggle(false);
        }
      });
      // Init
      scope.toggle();
    }
  }
}])
const ClickModule = angular
.module('clickOutside', [])
.directive('clickOutside', ['$document', function ($document) {
    return {
        restrict: 'A',
        scope: {
            clickOutside: '&'
        },
        link: function (scope, el, attr) {
            const handler = function (e) {
                if (el !== e.target && !el[0].contains(e.target)) {
                    scope.$apply(function () {
                        console.log("hiiii");
                        //  whatever expression you assign to the click-outside attribute gets executed here
                        //  good for closing dropdowns etc
                        scope.$eval(scope.clickOutside);
                    });
                }
            }

            $document.on('click', handler);

            scope.$on('$destroy', function() {
                $document.off('click', handler);
            });
        }
    }
}]);

如果在handler方法中放入日志,那么当从DOM中删除元素时,仍然会看到它被触发。加上我的零钱就足够把它拿走了。不想抢走任何人的风头,但这是一个优雅解决方案的修复方案。

我在中发现了一些实现问题

例如,如果单击的元素从DOM中删除,上面的指令将触发逻辑。 这对我不起作用,因为我在一个模态中有一些逻辑,在单击之后,用ng if删除了元素

我重写了他的实现。没有经过战斗测试,但似乎工作得更好(至少在我的场景中)

angular
.module('sbs.directives')
.directive('clickOutside',['$document','$parse','$timeout',clickOutside]);
const MAX_递归=400;
函数clickout($document、$parse、$timeout){
返回{
限制:“A”,
链接:函数($scope、elem、attr){
//推迟链接到下一个摘要以允许生成唯一id
$timeout(()=>{
函数RunLogicIfClickedElementSoutSide(e){
//检查我们的元素是否已隐藏,如果已隐藏则中止
if(angular.element(elem.hasClass('ng-hide')){
返回;
}
//如果没有点击目标,就没有意义
如果(!e | |!e.target){
返回;
}
单击edelementsoutsidedirectiveroot=false;
让hasParent=true;
设递归=0;
让compareNode=elem[0].parentNode;
当(
!单击eElementsOutsideDirectiveRoot&&
父母&&
递归<最大递归
) {
如果(例如,目标===比较节点){
单击eElementSoutSideDirectiveRoot=true;
}
compareNode=compareNode.parentNode;
hasParent=布尔值(compareNode);
递归+++;//以防万一,避免永久循环
}
如果(单击eElementsOutsideDirectiveRoot){
$timeout(函数(){
const fn=$parse(attr['clickout']);
fn($范围,{event:e});
});
}
}
//如果设备有触摸屏,请收听此事件
如果(_hasstouch()){
$document.on('touchstart',函数(){
设置超时(RunLogicIfClickedElementSoutSide);
});
}
//即使触摸屏笔记本电脑有触控功能,仍然可以收听点击事件
$document.on('click',runLogicIfClickedElementSoutSide);
//当作用域被销毁时,请清理文档事件处理程序,因为我们不希望它挂起
$scope.$on(“$destroy”,函数(){
如果(_hasstouch()){
$document.off('touchstart',RunLogicIfClickedElementSoutSide);
}
$document.off('click',runLogicIfClickedElementSoutSide);
});
});
},
};
}
函数_hasstouch(){
//适用于大多数浏览器,IE10/11和Surface
在窗口| | navigator.maxTouchPoints中返回“ontouchstart”;
}
性能提示:如果您在页面上分配了这些选择框,您应该只绑定
单击