source

요소 외부를 클릭할 때 이벤트를 발생시키는 지시어

manysource 2023. 3. 26. 11:36

요소 외부를 클릭할 때 이벤트를 발생시키는 지시어

비슷한 질문을 많이 하는 걸로 알고 있어요.하지만 아무도 내 문제를 해결해주지 않아.

현재 요소 밖에서 마우스를 클릭할 때 식을 실행하는 지시문을 작성하려고 합니다.

이 기능이 필요한 이유앱을 만들고 있는데, 이 앱에는 드롭다운 메뉴가 3개, 드롭다운 리스트가 5개 있습니다(선택한 것과 같습니다).이 모든 것들이 각 방향성이에요.이 모든 지시사항이 다르다고 가정해 봅시다.그래서 8개의 지시문이 있습니다.또한 모든 기능에는 동일한 기능이 필요합니다. 요소 측면을 클릭하면 드롭다운을 숨길 수 있습니다.

이 문제에 대한 해결 방법은 두 가지가 있는데, 둘 다 문제가 있습니다.

솔루션 A:

app.directive('clickAnywhereButHere', function($document){
  return {
    restrict: 'A',
    link: function(scope, elem, attr, ctrl) {
      elem.bind('click', function(e) {
        // this part keeps it from firing the click on the document.
        e.stopPropagation();
      });
      $document.bind('click', function() {
        // magic here.
        scope.$apply(attr.clickAnywhereButHere);
      })
    }
  }
})

솔루션 A의 예를 다음에 나타냅니다.여기를 클릭해 주세요.

첫 번째 드롭다운을 클릭한 후 작동한 다음 두 번째 입력을 클릭하면 첫 번째 드롭다운은 숨겨지지만 숨겨지지 않습니다.

솔루션 B:

app.directive('clickAnywhereButHere', ['$document', function ($document) {
    directiveDefinitionObject = {
        link: {
            pre: function (scope, element, attrs, controller) { },
            post: function (scope, element, attrs, controller) {
                onClick = function (event) {
                    var isChild = element.has(event.target).length > 0;
                    var isSelf = element[0] == event.target;
                    var isInside = isChild || isSelf;
                    if (!isInside) {
                        scope.$apply(attrs.clickAnywhereButHere)
                    }
                }
                $document.click(onClick)
            }
        }
    }
    return directiveDefinitionObject
}]);

솔루션 B의 예를 다음에 나타냅니다.여기를 클릭해 주세요.

솔루션 A 페이지에 지시문이 하나만 있고 내 앱에는 없는 경우 작동합니다.버블링을 방지하기 위해 먼저 dropdown1을 클릭하여 show dropdown1을 클릭한 후 dropdown2를 클릭하여 event be prevent를 클릭하면 dropdown1이 계속 표시됩니다.

솔루션 B는 현재 사용하고 있는 앱으로 동작하고 있습니다.하지만 문제는 성능 문제를 일으킨다는 것입니다.앱의 어느 곳을 클릭할 때마다 너무 많은 클릭 이벤트가 처리됩니다.현재 저의 경우 문서와의 8클릭 이벤트 바인드가 있기 때문에 클릭할 때마다 8개의 기능을 실행합니다.그 때문에, 특히 IE8에서는, 앱이 매우 느립니다.

이에 대한 더 나은 해결책은 없을까?감사해요.

event.stopPropagation()은 솔루션A에서 볼 수 있는 것과 같은 문제를 일으키기 때문에 사용하지 않습니다.가능하다면 블러나 포커스 이벤트도 이용하고 싶습니다.드롭다운이 입력에 연결되어 있으면 입력이 포커스를 잃었을 때 닫을 수 있습니다.

단, 문서의 클릭 이벤트 처리도 나쁘지 않으므로 동일한 클릭 이벤트를 여러 번 처리하지 않으려면 더 이상 필요하지 않을 때 문서에서 바인드 해제하면 됩니다.드롭다운 외부를 클릭할 때 평가되는 표현과 더불어 디렉티브는 활성화 여부를 알아야 합니다.

app.directive('clickAnywhereButHere', ['$document', function ($document) {
    return {
        link: function postLink(scope, element, attrs) {
            var onClick = function (event) {
                var isChild = $(element).has(event.target).length > 0;
                var isSelf = element[0] == event.target;
                var isInside = isChild || isSelf;
                if (!isInside) {
                    scope.$apply(attrs.clickAnywhereButHere)
                }
            }
            scope.$watch(attrs.isActive, function(newValue, oldValue) {
                if (newValue !== oldValue && newValue == true) {
                    $document.bind('click', onClick);
                }
                else if (newValue !== oldValue && newValue == false) {
                    $document.unbind('click', onClick);
                }
            });
        }
    };
}]);

디렉티브를 사용할 때는 다음과 같은 다른 표현만 입력합니다.

<your-dropdown click-anywhere-but-here="close()" is-active="isDropdownOpen()"></your-dropdown>

onClick 기능을 테스트하지 않았습니다.예상대로 되는 것 같아요.이게 도움이 됐으면 좋겠다.

드롭다운을 표시하거나 숨기려면 ngBlurngFocus를 사용해야 합니다.누군가 클릭하면 초점이 맞춰지고 그렇지 않으면 흐려집니다.

또한 입력 필드에 포커스를 설정하는 방법 질문을 참조하십시오.Angular에서 포커스를 설정하기 위해JS.

EDIT : 모든 디렉티브(드롭다운메뉴 또는 목록, Y라고 부름)에 대해 요소(X라고 부름)를 클릭했을 때 표시해야 하며, Y 이외의 임의의 장소(X는 분명히 제외)를 클릭했을 때 숨길 필요가 있습니다.Y의 속성은 Yvisble입니다.따라서 누군가가 X(ng-click)를 클릭하면 "Isvisible"을 true로 설정하고 Focus on Y를 설정합니다.누군가가 Y(ng-blur) 바깥쪽을 클릭하면 "Isvisible"이 false로 설정되며 숨겨집니다.변수("표시 가능")를 2개의 다른 요소/지침 간에 공유해야 하며 이를 위해 컨트롤러 또는 서비스의 범위를 사용할 수 있습니다.그것에 대한 다른 대안들도 있지만 그것은 질문의 범위 밖이다.

솔루션 A가 가장 올바르지만 열려 있는지 추적하기 위한 지시문에 다른 매개 변수를 추가해야 합니다.

link: function(scope, elem, attr, ctrl) {
  elem.bind('click', function(e) {
    // this part keeps it from firing the click on the document.
    if (isOpen) {
      e.stopPropagation();
    }
  });
  $document.bind('click', function() {
    // magic here.
    isOpen = false;
    scope.$apply(attr.clickAnywhereButHere);
  })
}
post: function ($scope, element, attrs, controller) { 
  element.on("click", function(){
    console.log("in element Click event");
    $scope.onElementClick = true;
    $document.on("click", $scope.onClick);
  });

  $scope.onClick = function (event) {
    if($scope.onElementClick && $scope.open)
    {
      $scope.onElementClick = false;
      return;
    }
    $scope.open = false;
    $scope.$apply(attrs.clickAnywhereButHere)
    $document.off("click", $scope.onClick);
  };
}

대부분의 상향식 답변보다 조금 더 심플한 버전입니다.저에게는 더 명확하고 잘 동작합니다.

app.directive('clickAnywhereButHere', function() {
        return {
            restrict : 'A',
            link: { 
                post: function(scope, element, attrs) {
                    element.on("click", function(event) {
                        scope.elementClicked = event.target;
                        $(document).on("click", onDocumentClick);
                    });

                    var onDocumentClick = function (event) {
                        if(scope.elementClicked === event.target) {
                            return;
                        }
                        scope.$apply(attrs.clickAnywhereButHere);
                        $(document).off("click", onDocumentClick);
                    };
                }
            }
        };
    });

사용하고 있는 솔루션을 다음에 나타냅니다(회답이 조금 늦어질 수 있지만, 이 문제를 겪고 있는 다른 사람에게 도움이 될 것으로 기대됩니다).

 link: function (scope, element, attr) {

        var clickedOutsite = false;
        var clickedElement = false;

        $(document).mouseup(function (e) {
            clickedElement = false;
            clickedOutsite = false;
        });

        element.on("mousedown", function (e) {

                clickedElement = true;
                if (!clickedOutsite && clickedElement) {
                    scope.$apply(function () {
                    //user clicked the element
                    scope.codeCtrl.elementClicked = true;
                    });
                }

        });

        $(document).mousedown(function (e) {
            clickedOutsite = true;
            if (clickedOutsite && !clickedElement) {
                scope.$apply(function () {
                    //user clicked outsite the element 
                    scope.codeCtrl.elementClicked = false;
                });
            }
        });
    }

다음은 클릭 이벤트(ngClick 디렉티브에서 $event로 사용 가능)만 필요한 솔루션을 보여 줍니다.클릭했을 때 다음과 같은 항목이 있는 메뉴가 필요했습니다.

  • 하위 메뉴 표시 전환
  • 표시된 경우 다른 하위 메뉴 숨기기
  • 밖에서 클릭이 발생한 경우 하위 메뉴를 숨깁니다.

이 코드는 하위 메뉴를 표시하거나 숨기는 데 사용할 수 있도록 메뉴 항목에서 클래스를 '활성'으로 설정합니다.

// this could also be inside a directive's link function.
// each menu element will contain data-ng-click="onMenuItemClick($event)".
// $event is the javascript event object made available by ng-click.
$scope.onMenuItemClick = function(menuElementEvent) {
    var menuElement = menuElementEvent.currentTarget,
        clickedElement = menuElementEvent.target,
        offRootElementClick; // where we will save angular's event unbinding function

    if (menuElement !== clickedElement) {
        return;
    }

    if (menuElement.classList.contains('active')) {
        menuElement.classList.remove('active');
        // if we were listening for outside clicks, stop
        offRootElementClick && offRootElementClick();
        offRootElementClick = undefined;
    } else {
        menuElement.classList.add('active');
        // listen for any click inside rootElement.
        // angular's bind returns a function that can be used to stop listening
        // I used $rootElement, but use $document if your angular app is nested in the document
        offRootElementClick = $rootElement.bind('click', function(rootElementEvent) {
            var anyClickedElement = rootElementEvent.target;
            // if it's not a child of the menuElement, close the submenu
            if(!menuElement.contains(anyClickedElement)) {
                menuElement.classList.remove('active');
                // and stop outside listenting
                offRootElementClick && offRootElementClick();
                offOutsideClick = undefined;
            }
        });
    }
}

@lex82 답변은 이 답변의 기반이 되지만, 몇 가지 점에서 다릅니다.

  1. TypeScript에 있습니다.
  2. 스코프가 파기되면 클릭바인딩이 삭제됩니다.즉, 속성으로 클릭바인딩을 개별적으로 관리할 필요가 없습니다.
  3. , 「」를 가지는 가, 「」를 가지는 ,click-outon은 마우스 이벤트를 통해 생성되며, 동일한 마우스 이벤트가 실제로 닫힘 메커니즘을 트리거하지 않습니다.

    export interface IClickOutDirectiveScope extends angular.IScope {
    
        clickOut: Function;
    }
    
    export class ClickOutDirective implements angular.IDirective {
    
        public restrict = "A";
        public scope = {
            clickOut: "&"
        }
    
        public link: ($scope: IClickOutDirectiveScope, element: angular.IAugmentedJQuery, attrs: angular.IAttributes) => void;
    
        constructor($timeout: angular.ITimeoutService, $document: angular.IDocumentService) {
    
            ClickOutDirective.prototype.link = ($scope: IClickOutDirectiveScope, $element: angular.IAugmentedJQuery, attrs: ng.IAttributes) => {
    
                var onClick = (event: JQueryEventObject) => {
                    var isChild = $element[0].contains(event.target);
                    var isSelf = $element[0] === event.target;
                    var isInside = isChild || isSelf;
    
                    if (!isInside) {
                        if ($scope.clickOut) {
                            $scope.$apply(() => {
                                $scope.clickOut();
                            });
                        }
                    }
                }
    
                $timeout(() => {
                    $document.bind("click", onClick);
                }, 500);
    
                $scope.$on("$destroy", () => {
                    $document.unbind("click", onClick);
                });
            }
        }
    
        static factory(): ng.IDirectiveFactory {
            const directive = ($timeout: angular.ITimeoutService, $document: angular.IDocumentService) => new ClickOutDirective($timeout, $document);
    
            directive.$inject = ["$timeout", "$document"];
    
            return directive;
        }
    }
    
    angular.module("app.directives")
        .directive("clickOut", ClickOutDirective.factory());
    

언급URL : https://stackoverflow.com/questions/20186438/directive-that-fires-an-event-when-clicking-outside-of-the-element