[javascript] 외부 클릭시 드롭 다운을 어떻게 닫을 수 있습니까?

사용자가 해당 드롭 다운 외부의 아무 곳이나 클릭하면 로그인 메뉴 드롭 다운을 닫고 싶습니다. Angular2와 Angular2 “접근법”으로 수행하고 싶습니다 …

솔루션을 구현했지만 실제로는 자신감이 없습니다. 나는 같은 결과를 얻는 가장 쉬운 방법이 있어야한다고 생각하므로 아이디어가 있다면 … : 토론합시다!

내 구현은 다음과 같습니다.

드롭 다운 구성 요소 :

이것은 내 드롭 다운의 구성 요소입니다.

  • 이 컴포넌트가 표시되도록 설정할 때마다 (예 : 사용자가 버튼을 클릭하여 표시 할 때) SubjectsService 내에 저장된 “global”rxjs subject userMenu를 구독합니다 .
  • 그리고 숨길 때마다이 주제를 구독 취소합니다.
  • 이 구성 요소의 템플릿 어디에서나 클릭 할 때마다 onClick () 메서드가 트리거됩니다.이 메서드는 이벤트 버블 링을 맨 위 (및 응용 프로그램 구성 요소)로 중지합니다.

여기 코드가 있습니다

export class UserMenuComponent {

    _isVisible: boolean = false;
    _subscriptions: Subscription<any> = null;

    constructor(public subjects: SubjectsService) {
    }

    onClick(event) {
        event.stopPropagation();
    }

    set isVisible(v) {
        if( v ){
            setTimeout( () => {
this._subscriptions =  this.subjects.userMenu.subscribe((e) => {
                       this.isVisible = false;
                       })
            }, 0);
        } else {
            this._subscriptions.unsubscribe();
        }
        this._isVisible = v;
    }

    get isVisible() {
        return this._isVisible;
    }
}

응용 프로그램 구성 요소 :

반면에 응용 프로그램 구성 요소 (드롭 다운 구성 요소의 부모)가 있습니다.

  • 이 구성 요소는 모든 클릭 이벤트를 포착하고 동일한 rxjs 주제 ( userMenu )에서 생성합니다.

코드는 다음과 같습니다.

export class AppComponent {

    constructor( public subjects: SubjectsService) {
        document.addEventListener('click', () => this.onClick());
    }
    onClick( ) {
        this.subjects.userMenu.next({});
    }
}

나를 귀찮게하는 것 :

  1. 나는 그 구성 요소들 사이의 커넥터 역할을하는 글로벌 주제를 가지고 있다는 생각에 정말로 편안하지 않습니다.
  2. 에서는 setTimeout : 여기에 사용자가 버튼을 클릭 드롭 다운을 표시하는 경우, 그렇지 않으면 일이 무엇 때문이 필요합니다 :
    • 드롭 다운 구성 요소의 일부가 아닌 단추를 클릭하면 드롭 다운이 표시됩니다.
    • 드롭 다운이 표시되고 즉시 userMenu 주제를 구독합니다 .
    • 클릭 이벤트가 앱 구성 요소에 버블 링되어 붙잡음
    • 응용 프로그램 구성 요소가 userMenu 주제 에서 이벤트를 생성합니다.
    • 드롭 다운 컴포넌트는 userMenu 에서이 조치를 포착 하고 드롭 다운을 숨 깁니다.
    • 마지막에 드롭 다운이 표시되지 않습니다.

이 설정 시간 초과는 현재 JavaScript 코드 턴의 끝까지 구독을 지연시켜 문제를 해결하지만 매우 우아하게 생각합니다.

더 깨끗하고, 더 똑똑하고, 더 빠르거나 강한 솔루션을 알고 있다면 알려주세요 :)!



답변

당신은 (document:click)이벤트 를 사용할 수 있습니다 :

@Component({
  host: {
    '(document:click)': 'onClick($event)',
  },
})
class SomeComponent() {
  constructor(private _eref: ElementRef) { }

  onClick(event) {
   if (!this._eref.nativeElement.contains(event.target)) // or some similar check
     doSomething();
  }
}

또 다른 방법은 사용자 지정 이벤트를 지시문으로 만드는 것입니다. Ben Nadel의 다음 게시물을 확인하십시오.


답변

우아한 방법

https://github.com/chliebel/angular2-click-outsideclickOut 지시어를
찾았습니다 . 확인하고 제대로 작동합니다 ( 프로젝트 에만 복사 ). U는 다음과 같이 사용할 수 있습니다.clickOutside.directive.ts

<div (clickOutside)="close($event)"></div>

close사용자가 div 외부를 클릭하면 호출 될 함수는 어디에 있습니까 ? 문제가 설명 된 문제를 처리하는 매우 우아한 방법입니다.

위의 지시문을 사용하여 팝업 창을 닫는 경우 먼저 event.stopPropagation()팝업을 여는 단추 클릭 이벤트 핸들러 에 추가 해야합니다.

보너스:

아래에서 파일에서 oryginal 지시어 코드를 복사합니다 clickOutside.directive.ts(향후 링크가 작동하지 않을 경우). 저자는 Christian Liebel입니다 .


답변

나는 이렇게했다.

문서 click와 해당 핸들러 에 이벤트 리스너를 추가하여 내 container포함 여부를 확인했습니다 event.target. 드롭 다운을 숨 깁니다.

이렇게 보일 것입니다.

@Component({})
class SomeComponent {
    @ViewChild('container') container;
    @ViewChild('dropdown') dropdown;

    constructor() {
        document.addEventListener('click', this.offClickHandler.bind(this)); // bind on doc
    }

    offClickHandler(event:any) {
        if (!this.container.nativeElement.contains(event.target)) { // check click origin
            this.dropdown.nativeElement.style.display = "none";
        }
    }
}


답변

나는 Sasxa가 대부분의 사람들에게 대답을 받아 들였다고 생각합니다. 그러나 오프 클릭 이벤트를 수신 해야하는 Element의 내용이 동적으로 변경되는 상황이있었습니다. 따라서 Elements nativeElement는 동적으로 작성된 event.target을 포함하지 않았습니다. 다음 지시문으로 해결할 수 있습니다

@Directive({
  selector: '[myOffClick]'
})
export class MyOffClickDirective {

  @Output() offClick = new EventEmitter();

  constructor(private _elementRef: ElementRef) {
  }

  @HostListener('document:click', ['$event.path'])
  public onGlobalClick(targetElementPath: Array<any>) {
    let elementRefInPath = targetElementPath.find(e => e === this._elementRef.nativeElement);
    if (!elementRefInPath) {
      this.offClick.emit(null);
    }
  }
}

elementRef에 event.target이 포함되어 있는지 확인하는 대신 elementRef가 이벤트의 경로 (DOM 경로)에 있는지 확인합니다. 이렇게하면 동적으로 생성 된 요소를 처리 할 수 ​​있습니다.


답변

iOS에서이 작업을 수행하는 경우 touchstart 이벤트도 .

Angular 4부터는 HostListener꾸미기가 선호되는 방법입니다.

import { Component, OnInit, HostListener, ElementRef } from '@angular/core';
...
@Component({...})
export class MyComponent implement OnInit {

  constructor(private eRef: ElementRef){}

  @HostListener('document:click', ['$event'])
  @HostListener('document:touchstart', ['$event'])
  handleOutsideClick(event) {
    // Some kind of logic to exclude clicks in Component.
    // This example is borrowed Kamil's answer
    if (!this.eRef.nativeElement.contains(event.target) {
      doSomethingCool();
    }
  }

}


답변

우리는 오늘 직장에서 비슷한 문제를 해결하기 위해 드롭 다운 div를 클릭하면 사라지는 방법을 알아 내려고 노력했습니다. 우리는 다른 구성 요소 또는 지시문 을 클릭하지 않고 특정 div 외부 에서 클릭하기를 원하지 않기 때문에 초기 포스터의 질문과 약간 다릅니다 .

우리는 (window : mouseup) 이벤트 핸들러를 사용하여 문제를 해결했습니다.

단계 :
1.) 전체 드롭 다운 메뉴 div에 고유 한 클래스 이름을 지정했습니다.

2.) 내부 드롭 다운 메뉴 자체 (메뉴를 닫지 않기 위해 클릭하려는 유일한 부분)에 (window : mouseup) 이벤트 핸들러를 추가하고 $ event에 전달했습니다.

참고 : 일반적인 “클릭”처리기는 부모 클릭 처리기와 충돌하기 때문에 수행 할 수 없습니다.

3.) 컨트롤러에서, 우리는 클릭 아웃 이벤트에서 호출되기를 원하는 메소드를 만들었고 , 클릭 된 스팟이 타겟 클래스 div 내에 있는지 알아 내기 위해 event.closest ( docs here )를 사용합니다 .

 autoCloseForDropdownCars(event) {
        var target = event.target;
        if (!target.closest(".DropdownCars")) {
            // do whatever you want here
        }
    }
 <div class="DropdownCars">
   <span (click)="toggleDropdown(dropdownTypes.Cars)" class="searchBarPlaceholder">Cars</span>
   <div class="criteriaDropdown" (window:mouseup)="autoCloseForDropdownCars($event)" *ngIf="isDropdownShown(dropdownTypes.Cars)">
   </div>
</div>


답변

드롭 다운에 형제 요소를 만들어 보이지 않는 전체 화면을 덮고 클릭 이벤트를 캡처 할 수 있습니다. 그런 다음 해당 요소의 클릭을 감지하고 클릭하면 드롭 다운을 닫을 수 있습니다. 요소가 실크 스크린 클래스라고 가정 해 보겠습니다. 여기에 스타일이 있습니다.

.silkscreen {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1;
}

z- 색인은 드롭 다운을 제외한 모든 것 위에 배치하기에 충분히 높아야합니다. 이 경우 내 드롭 다운은 z-index 2입니다.

다른 답변은 경우에 따라 효과가 있었지만 때로는 요소 내의 요소와 상호 작용할 때 내 드롭 다운이 닫히고 싶지 않았기 때문에 제외했습니다. 예상대로 이벤트 대상에 따라 구성 요소에 포함되지 않은 요소를 동적으로 추가했습니다. 그 엉망을 분류하는 대신 실크 스크린 방식으로 시도해 보았습니다.