ユーザーがドロップダウンの外側のどこかをクリックしたら、ログインメニューのドロップダウンを閉じます。Angular2とAngular2の「アプローチ」を使用してそれを行いたいのですが….
私は解決策を実行しました、しかし私は本当にそれに自信がありません。私は同じ結果を達成するための最も簡単な方法がなければならないと思うので、あなたが何かアイデアがあれば...議論しましょう:)!
これが私の実装です:
これは私のドロップダウンのコンポーネントです。
これがコードです
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;
}
}
一方、アプリケーションコンポーネント(ドロップダウンコンポーネントの親)があります。
これがコードです:
export class AppComponent {
constructor( public subjects: SubjectsService) {
document.addEventListener('click', () => this.onClick());
}
onClick( ) {
this.subjects.userMenu.next({});
}
}
このタイムアウト設定は、問題を解決する現在の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();
}
}
もう1つの方法は、ディレクティブとしてカスタムイベントを作成することです。 Ben Nadelによるこれらの投稿をチェックしてください。
私はこのclickOut
ディレクティブを見つけました: https://github.com/chliebel/angular2-click-outside 。私はそれをチェックし、それはうまく機能します(私は私のプロジェクトにclickOutside.directive.ts
をコピーするだけです)。 Uはこのようにそれを使うことができます:
<div (clickOutside)="close($event)"></div>
close
はユーザーがdivの外側をクリックしたときに呼び出される関数です。問題の問題を処理するための非常にエレガントな方法です。
上記のディレクティブを使用してポップアップウィンドウを閉じる場合は、まずポップアップを開くボタンクリックイベントハンドラにevent.stopPropagation()
を追加してください。
以下で私はファイルclickOutside.directive.ts
からオリジナルのディレクティブコードをコピーします(将来リンクが機能しなくなる場合) - 作者は Christian Liebel :
import {Directive, ElementRef, Output, EventEmitter, HostListener} from '@angular/core';
@Directive({
selector: '[clickOutside]'
})
export class ClickOutsideDirective {
constructor(private _elementRef: ElementRef) {
}
@Output()
public clickOutside = new EventEmitter<MouseEvent>();
@HostListener('document:click', ['$event', '$event.target'])
public onClick(event: MouseEvent, targetElement: HTMLElement): void {
if (!targetElement) {
return;
}
const clickedInside = this._elementRef.nativeElement.contains(targetElement);
if (!clickedInside) {
this.clickOutside.emit(event);
}
}
}
私はこうやった。
ドキュメント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がほとんどの人に答えを認めたと思います。ただし、クリック以外のイベントを監視する必要がある要素のコンテンツが動的に変化する状況がありました。そのため、要素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パス)にあるかどうかをチェックします。そのようにして動的に作成された要素を扱うことが可能です。
ドロップダウンdivがクリックされたときに消えるようにする方法を見つけようとして、私たちは今日職場で同様の問題に取り組んでいます。別のコンポーネントまたはディレクティブからクリックしたくないため、最初の投稿者の質問とは少し異なります。単に特定のdivの外側にあります。
(window:mouseup)イベントハンドラを使って解決しました。
ステップ:
1)ドロップダウンメニューdiv全体に一意のクラス名を付けました。
2)内側のドロップダウンメニュー自体(クリックしてメニューを閉じたくない部分)に(window:mouseup)イベントハンドラを追加して$イベントを渡しました。
注:これは親のクリックハンドラと競合するため、一般的な「クリック」ハンドラでは実行できませんでした。
3。)私たちのコントローラーで、click outイベントで呼び出されたいメソッドを作成し、event.closest( のドキュメントをここで使用します )クリックした場所がターゲットクラスのdiv内にあるかどうかを調べます。
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>
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();
}
}
}
回避策はありませんでした。添付ファイルを添付しました。トグル機能をクリックしてください。
@Directive({ セレクタ: '[appDropDown]' }) エクスポートクラスDropdownDirectiveはOnInitを実装します{ [ @HostBinding( 'class.open')isOpen:boolean; コンストラクタ(private elemRef:ElementRef){} ngOnInit(): void { this.isOpen = false; } @HostListener( 'document:click'、['$ event']) @HostListener( 'document:touchstart'、['$ event']) toggle(event){ if(this.elemRef.nativeElement.contains(event.target)){[.____ this.isOpen =!this.isOpen; } else { this.isOpen = false; } }
だから、私が自分の指令の外にいるときは、ドロップダウンを閉じます。
import { Component, HostListener } from '@angular/core';
@Component({
selector: 'custom-dropdown',
template: `
<div class="custom-dropdown-container">
Dropdown code here
</div>
`
})
export class CustomDropdownComponent {
thisElementClicked: boolean = false;
constructor() { }
@HostListener('click', ['$event'])
onLocalClick(event: Event) {
this.thisElementClicked = true;
}
@HostListener('document:click', ['$event'])
onClick(event: Event) {
if (!this.thisElementClicked) {
//click was outside the element, do stuff
}
this.thisElementClicked = false;
}
}
ダウンワード: - ページ上のこれらのコンポーネントごとに2つのクリックイベントリスナー。何百回もページにあるコンポーネントにこれを使用しないでください。
あなたは見えないでクリックイベントを捉えるためだけにある画面全体をカバーするドロップダウンへの兄弟要素を作成することができます。それからあなたはその要素のクリックを検出し、クリックされたらドロップダウンを閉じることができます。要素はシルクスクリーンクラスのものであると言うことができます、ここでそれのためのいくつかのスタイルがあります:
.silkscreen {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 1;
}
Zインデックスは、ドロップダウンを除くすべてのものの上に配置するのに十分な高さである必要があります。この場合、私のドロップダウンはz-index 2です。
他の答えは私のためにいくつかのケースでうまくいきました。予想したように、イベントターゲットに応じて、コンポーネントに含まれていない要素を動的に追加しました。その混乱を整理するのではなく、シルクスクリーンの方法で試してみることにしました。
正しい答えには問題があります。ポップオーバーに難解なコンポーネントがある場合、その要素はcontain
メソッド上になくなり、自分で作成した@ JuHarm89に基づいて閉じます。
export class PopOverComponent implements AfterViewInit {
private parentNode: any;
constructor(
private _element: ElementRef
) { }
ngAfterViewInit(): void {
this.parentNode = this._element.nativeElement.parentNode;
}
@HostListener('document:click', ['$event.path'])
onClickOutside($event: Array<any>) {
const elementRefInPath = $event.find(node => node === this.parentNode);
if (!elementRefInPath) {
this.closeEventEmmit.emit();
}
}
}
助けてくれてありがとう!
イベントはコンポーネントの外側をクリックしても削除されないため、@Tonyの回答を補完したいと思います。完全な領収書:
あなたの主な要素を#containerでマークしてください
@ViewChild('container') container;
_dropstatus: boolean = false;
get dropstatus() { return this._dropstatus; }
set dropstatus(b: boolean)
{
if (b) { document.addEventListener('click', this.offclickevent);}
else { document.removeEventListener('click', this.offclickevent);}
this._dropstatus = b;
}
offclickevent: any = ((evt:any) => { if (!this.container.nativeElement.contains(evt.target)) this.dropstatus= false; }).bind(this);
クリック可能な要素では、次のように使用します。
(click)="dropstatus=true"
これで、dropstatus変数でドロップダウン状態を制御し、[ngClass]で適切なクラスを適用することができます.
あなたはディレクティブを書くことができます:
@Directive({
selector: '[clickOut]'
})
export class ClickOutDirective implements AfterViewInit {
@Input() clickOut: boolean;
@Output() clickOutEvent: EventEmitter<any> = new EventEmitter<any>();
@HostListener('document:mousedown', ['$event']) onMouseDown(event: MouseEvent) {
if (this.clickOut &&
!event.path.includes(this._element.nativeElement))
{
this.clickOutEvent.emit();
}
}
}
あなたのコンポーネントで:
@Component({
selector: 'app-root',
template: `
<h1 *ngIf="isVisible"
[clickOut]="true"
(clickOutEvent)="onToggle()"
>{{title}}</h1>
`,
styleUrls: ['./app.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {
title = 'app works!';
isVisible = false;
onToggle() {
this.isVisible = !this.isVisible;
}
}
このディレクティブは、html要素がDOMに含まれているとき、および[clickOut]入力プロパティが 'true'のときにイベントを発行します。要素がDOMから削除される前にイベントを処理するためにmousedownイベントをリッスンします。
そして1つの注意:firefoxはあなたがパスを作成するためにfunctionを使うことができるイベントのプロパティ 'path'を含んでいません:
const getEventPath = (event: Event): HTMLElement[] => {
if (event['path']) {
return event['path'];
}
if (event['composedPath']) {
return event['composedPath']();
}
const path = [];
let node = <HTMLElement>event.target;
do {
path.Push(node);
} while (node = node.parentElement);
return path;
};
そのため、ディレクティブでイベントハンドラを変更する必要があります。event.pathはgetEventPath(event)に置き換えます。
このモジュールは役に立ちます。 https://www.npmjs.com/package/ngx-clickout 同じロジックを含みますが、ソースHTML要素のescイベントも処理します。
代わりにモーダルオーバーレイをクリックしたかどうかを確認してください。
あなたのテンプレート:
<div #modalOverlay (click)="clickOutside($event)" class="modal fade show" role="dialog" style="display: block;">
<div class="modal-dialog" [ngClass]='size' role="document">
<div class="modal-content" id="modal-content">
<div class="close-modal" (click)="closeModal()"> <i class="fa fa-times" aria-hidden="true"></i></div>
<ng-content></ng-content>
</div>
</div>
</div>
そして方法:
@ViewChild('modalOverlay') modalOverlay: ElementRef;
// ... your constructor and other method
clickOutside(event: Event) {
const target = event.target || event.srcElement;
console.log('click', target);
console.log("outside???", this.modalOverlay.nativeElement == event.target)
// const isClickOutside = !this.modalBody.nativeElement.contains(event.target);
// console.log("click outside ?", isClickOutside);
if ("isClickOutside") {
// this.closeModal();
}
}
@Tonyの優れたソリューションのためのより良いバージョン:
@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.closest(".ourDropdown.open").classList.remove("open");
}
}
}
CSSファイルの場合://ブートストラップドロップダウンを使用する場合は不要。
.ourDropdown{
display: none;
}
.ourDropdown.open{
display: inherit;
}
ブートストラップを使用している場合は、ドロップダウン(ブートストラップコンポーネント)を介してブートストラップ方法で直接実行できます。
<div class="input-group">
<div class="input-group-btn">
<button aria-expanded="false" aria-haspopup="true" class="btn btn-default dropdown-toggle" data-toggle="dropdown" type="button">
Toggle Drop Down. <span class="fa fa-sort-alpha-asc"></span>
</button>
<ul class="dropdown-menu">
<li>List 1</li>
<li>List 2</li>
<li>List 3</li>
</ul>
</div>
</div>
ボタンに(click)="clickButton()"
のものを入れても大丈夫です。 http://getbootstrap.com/javascript/#dropdowns
私も私自身のちょっとした回避策をしました。
Ng-select要素コンポーネントでリスンして呼び出す(dropdownOpen)イベントを作成しました現在開いているSelectComponentとは別に開かれている他のすべてのSelectComponentを閉じる関数。
イベントを発行するために、select.tsファイル内の関数を次のように変更しました。
private open():void {
this.options = this.itemObjects
.filter((option:SelectItem) => (this.multiple === false ||
this.multiple === true && !this.active.find((o:SelectItem) => option.text === o.text)));
if (this.options.length > 0) {
this.behavior.first();
}
this.optionsOpened = true;
this.dropdownOpened.emit(true);
}
HTMLでは、(dropdownOpened)のイベントリスナを追加しました。
<ng-select #elem (dropdownOpened)="closeOtherElems(elem)"
[multiple]="true"
[items]="items"
[disabled]="disabled"
[isInputAllowed]="true"
(data)="refreshValue($event)"
(selected)="selected($event)"
(removed)="removed($event)"
placeholder="No city selected"></ng-select>
これは、ng2-selectタグを持つコンポーネント内のイベントトリガーに関する私の呼び出し関数です。
@ViewChildren(SelectComponent) selectElem :QueryList<SelectComponent>;
public closeOtherElems(element){
let a = this.selectElem.filter(function(el){
return (el != element)
});
a.forEach(function(e:SelectComponent){
e.closeDropdown();
})
}
私はこの同様の問題に対処するためのディレクティブを作りました、そして私はBootstrapを使用しています。しかし、私の場合は、要素の外側のclickイベントが現在開いているドロップダウンメニューを閉じるのを待つ代わりに、 'mouseleave'イベントを見て自動的にメニューを閉じるのが良いと思います。
これが私の解決策です:
指令
import { Directive, HostListener, HostBinding } from '@angular/core';
@Directive({
selector: '[appDropdown]'
})
export class DropdownDirective {
@HostBinding('class.open') isOpen = false;
@HostListener('click') toggleOpen() {
this.isOpen = !this.isOpen;
}
@HostListener('mouseleave') closeDropdown() {
this.isOpen = false;
}
}
HTML
<ul class="nav navbar-nav navbar-right">
<li class="dropdown" appDropdown>
<a class="dropdown-toggle" data-toggle="dropdown">Test <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li routerLinkActive="active"><a routerLink="/test1">Test1</a></li>
<li routerLinkActive="active"><a routerLink="/test2/">Test2</a></li>
</ul>
</li>
</ul>
注:Webワーカーを使用したい場合、documentおよびnativeElementを使用しないようにする必要があります。これは機能します。
私はここで同じ質問に答えた。 https://stackoverflow.com/questions/47571144
上記のリンクからコピー/貼り付け:
ドロップダウンメニューと確認ダイアログを作成しているときにも同じ問題がありました。外側をクリックしたときにそれらを消したかったのです。
私の最後の実装は完璧に動作しますが、いくつかのcss3アニメーションとスタイリングが必要です。
NOTE:私は以下のコードをテストしていません、解決する必要があるいくつかの構文上の問題、またあなた自身のプロジェクトのための明らかな調整があるかもしれません!
私がしたこと:
高さ100%、幅100%、変換:scale(0)を使って別の固定divを作成しました。これは基本的に背景です。background-colorを使用してスタイルを設定できます。メニューが開いていて、背景がクリックして閉じるのがわかりやすいように。メニューは他よりも高いz-indexを取得し、次に背景divはメニューよりも低いが他のすべてよりも高いz-indexを取得します。その後、背景にドロップダウンを閉じるクリックイベントがあります。
これはあなたのHTMLコードです。
<div class="dropdownbackground" [ngClass]="{showbackground: qtydropdownOpened}" (click)="qtydropdownOpened = !qtydropdownOpened"><div>
<div class="zindex" [class.open]="qtydropdownOpened">
<button (click)="qtydropdownOpened = !qtydropdownOpened" type="button"
data-toggle="dropdown" aria-haspopup="true" [attr.aria-expanded]="qtydropdownOpened ? 'true': 'false' ">
{{selectedqty}}<span class="caret margin-left-1x "></span>
</button>
<div class="dropdown-wrp dropdown-menu">
<ul class="default-dropdown">
<li *ngFor="let quantity of quantities">
<a (click)="qtydropdownOpened = !qtydropdownOpened;setQuantity(quantity)">{{quantity }}</a>
</li>
</ul>
</div>
</div>
これはいくつかの簡単なアニメーションを必要とするcss3です。
/* make sure the menu/drop-down is in front of the background */
.zindex{
z-index: 3;
}
/* make background fill the whole page but sit behind the drop-down, then
scale it to 0 so its essentially gone from the page */
.dropdownbackground{
width: 100%;
height: 100%;
position: fixed;
z-index: 2;
transform: scale(0);
opacity: 0;
background-color: rgba(0, 0, 0, 0.466);
}
/* this is the class we add in the template when the drop down is opened
it has the animation rules set these how you like */
.showbackground{
animation: showBackGround 0.4s 1 forwards;
}
/* this animates the background to fill the page
if you don't want any thing visual you could use a transition instead */
@keyframes showBackGround {
1%{
transform: scale(1);
opacity: 0;
}
100% {
transform: scale(1);
opacity: 1;
}
}
視覚的なことが何もない場合は、次のようなトランジションを使用することができます。
.dropdownbackground{
width: 100%;
height: 100%;
position: fixed;
z-index: 2;
transform: scale(0);
opacity: 0;
transition all 0.1s;
}
.dropdownbackground.showbackground{
transform: scale(1);
}
フォーカス/ぼかしイベントの例に触発された別のソリューションに出会いました。
したがって、グローバルドキュメントリスナーをアタッチせずに同じ機能を実現する場合は、次の例を有効と見なすことができます。ボタンフォーカスイベントの他の処理があるにもかかわらず、OSx上のSafariおよびFirefoxでも動作します: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus
angular 8を使用したstackbizの動作例: https://stackblitz.com/edit/angular-sv4tbi?file=src%2Ftoggle-dropdown%2Ftoggle-dropdown.directive.ts
HTMLマークアップ:
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" aria-haspopup="true" aria-expanded="false">Dropdown button</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<a class="dropdown-item" href="#">Something else here</a>
</div>
</div>
ディレクティブは次のようになります。
import { Directive, HostBinding, ElementRef, OnDestroy, Renderer2 } from '@angular/core';
@Directive({
selector: '.dropdown'
})
export class ToggleDropdownDirective {
@HostBinding('class.show')
public isOpen: boolean;
private buttonMousedown: () => void;
private buttonBlur: () => void;
private navMousedown: () => void;
private navClick: () => void;
constructor(private element: ElementRef, private renderer: Renderer2) { }
ngAfterViewInit() {
const el = this.element.nativeElement;
const btnElem = el.querySelector('.dropdown-toggle');
const menuElem = el.querySelector('.dropdown-menu');
this.buttonMousedown = this.renderer.listen(btnElem, 'mousedown', (evt) => {
console.log('MOUSEDOWN BTN');
this.isOpen = !this.isOpen;
evt.preventDefault(); // prevents loose of focus (default behaviour) on some browsers
});
this.buttonMousedown = this.renderer.listen(btnElem, 'click', () => {
console.log('CLICK BTN');
// firefox OSx, Safari, Ie OSx, Mobile browsers.
// Whether clicking on a <button> causes it to become focused varies by browser and OS.
btnElem.focus();
});
// only for debug
this.buttonMousedown = this.renderer.listen(btnElem, 'focus', () => {
console.log('FOCUS BTN');
});
this.buttonBlur = this.renderer.listen(btnElem, 'blur', () => {
console.log('BLUR BTN');
this.isOpen = false;
});
this.navMousedown = this.renderer.listen(menuElem, 'mousedown', (evt) => {
console.log('MOUSEDOWN MENU');
evt.preventDefault(); // prevents nav element to get focus and button blur event to fire too early
});
this.navClick = this.renderer.listen(menuElem, 'click', () => {
console.log('CLICK MENU');
this.isOpen = false;
btnElem.blur();
});
}
ngOnDestroy() {
this.buttonMousedown();
this.buttonBlur();
this.navMousedown();
this.navClick();
}
}
最も優秀な方法:D
そのための最も簡単な方法は1つあります。そのためのディレクティブは必要ありません。
"element-that-toggle-your-dropdown"はボタンタグであるべきです。 (blur)属性には任意の方法を使用してください。それで全部です。
<button class="element-that-toggle-your-dropdown"
(blur)="isDropdownOpen = false"
(click)="isDropdownOpen = !isDropdownOpen">
</button>