ฉันจะปิดดร็อปดาวน์เมื่อคลิกภายนอกได้อย่างไร


144

ฉันต้องการปิดเมนูดร็อปดาวน์ของฉันเมื่อผู้ใช้คลิกที่ใดก็ได้นอกเหนือจากดรอปดาวน์นั้นและฉันต้องการจะทำเช่นนั้นกับ Angular2 และกับ Angular2 "วิธี" ...

ฉันติดตั้งโซลูชันแล้ว แต่ฉันไม่รู้สึกมั่นใจเลย ฉันคิดว่าต้องมีวิธีที่ง่ายที่สุดในการบรรลุผลลัพธ์เดียวกันดังนั้นหากคุณมีความคิดใด ๆ ... มาคุยกัน :)!

นี่คือการดำเนินการของฉัน:

องค์ประกอบแบบเลื่อนลง:

นี่เป็นส่วนประกอบสำหรับดรอปดาวน์ของฉัน:

  • ทุกครั้งที่ส่วนนี้มันตั้งค่าให้สามารถมองเห็นได้ (ตัวอย่างเช่นเมื่อผู้ใช้คลิกที่ปุ่มเพื่อแสดง) ก็สมัครเป็นสมาชิก "ทั่วโลก" rxjs เรื่องuserMenuเก็บไว้ภายในSubjectsService
  • และทุกครั้งที่มันถูกซ่อนไว้
  • ทุกการคลิกที่ใดก็ได้ภายในเทมเพลตของส่วนประกอบนี้จะเรียกใช้เมธอด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 ปัจจุบันซึ่งแก้ไขปัญหาได้

หากคุณรู้จักวิธีทำความสะอาดที่ดีกว่าฉลาดขึ้นเร็วขึ้นหรือแข็งแกร่งขึ้นโปรดแจ้งให้เราทราบ :)!


คำตอบเหล่านี้อาจให้ความคิดกับคุณ: stackoverflow.com/a/35028820/215945 , stackoverflow.com/questions/35024495#35024651
Mark Rajcok

คำตอบ:


245

คุณสามารถใช้(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:


1
@Sasxa ขอบคุณและเห็นด้วย ฉันคิดว่ามีเอกสาร API ที่ไม่คัดค้านก็จะปรากฏในการค้นหาที่นำฉันมาที่นี่
danludwig

4
ถ้า event.target เป็นองค์ประกอบที่ถูกเพิ่มแบบไดนามิกผ่านบางสิ่งบางอย่างเช่นการผูก [innerHTML] แล้ว nativeElement ของ elementRef จะไม่ประกอบด้วย
Patrick Graham

8
ข้อเสียเพียงอย่างเดียวของเทคนิคนี้คือตอนนี้คุณมีฟังเหตุการณ์การคลิกในแอปพลิเคชันของคุณที่จะยิงทุกครั้งที่คุณคลิก
codeepic

37
ตามคู่มือสไตล์ Angular 2 อย่างเป็นทางการคุณควรใช้@HostListener('document:click', ['$event'])แทนhostคุณสมบัติของComponentมัณฑนากร
Michał Miszczyszyn

15
หรือคุณสามารถใช้ rxjs Observable.fromEvent(document, 'click').subscribe(event => {your code here})เพื่อให้คุณสามารถสมัครรับข้อมูลได้เมื่อคุณต้องการฟังตัวอย่างเช่นคุณเปิดรายการแบบหล่นลงและเมื่อคุณปิดมันคุณจะยกเลิกการเป็นสมาชิก
Blind Despair

43

วิธีการที่สง่างาม

ฉันพบนี้clickOutสั่ง: https://github.com/chliebel/angular2-click-outside ฉันตรวจสอบและใช้งานได้ดี (ฉันคัดลอกclickOutside.directive.tsไปยังโครงการของฉันเท่านั้น) คุณสามารถใช้วิธีนี้:

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

closeฟังก์ชั่นของคุณอยู่ที่ไหนซึ่งจะถูกเรียกเมื่อผู้ใช้คลิกนอก div มันเป็นวิธีที่สวยงามมากในการจัดการกับปัญหาที่อธิบายไว้ในคำถาม

หากคุณใช้คำสั่งข้างต้นเพื่อปิดหน้าต่างป๊อปอัพอย่าลืมก่อนอื่นเพื่อเพิ่มevent.stopPropagation()ปุ่มคลิกจัดการเหตุการณ์ที่เปิดป๊อปอัป

โบนัส:

ด้านล่างฉันคัดลอกรหัสคำสั่ง oryginal จากไฟล์clickOutside.directive.ts(ในกรณีที่ลิงค์จะหยุดทำงานในอนาคต) - ผู้แต่งคือChristian Liebel :


2
@Vega คำแนะนำของฉันคือการใช้ Directive ในองค์ประกอบที่มี * ng หากในกรณีที่มีการเลื่อนลงสิ่งนี้อาจเป็นสิ่งที่ต้องการ<div class="wrap" *ngIf="isOpened" (clickOutside)="...// this should set this.isOpen=false"
Gabriel Balsa Cantú

19

ฉันทำอย่างนี้แล้ว

เพิ่มฟังเหตุการณ์ในเอกสาร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";
        }
    }
}

สวัสดี จำเป็นต้องผูก (สิ่งนี้) หรือไม่
Drenai

1
@Brian มันอาจหรืออาจไม่จำเป็น แต่ก็ไม่เป็นไรถ้าเขาหุ้มthis.offClickHandlerฟังก์ชั่นลูกศร
Lansana Camara

17

ฉันคิดว่า Sasxa ยอมรับคำตอบสำหรับคนส่วนใหญ่แล้ว อย่างไรก็ตามฉันมีสถานการณ์ที่เนื้อหาขององค์ประกอบที่ควรฟังเหตุการณ์นอกคลิกมีการเปลี่ยนแปลงแบบไดนามิก ดังนั้น ElementElementElement จึงไม่ได้มี 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 path to target) ของเหตุการณ์หรือไม่ ด้วยวิธีนี้จึงเป็นไปได้ในการจัดการองค์ประกอบที่สร้างขึ้นแบบไดนามิก


ขอบคุณ - มันจะทำงานได้ดีขึ้นเมื่อมีส่วนประกอบของเด็กอยู่
MAhsan

สิ่งนี้มีประโยชน์มากสำหรับฉัน ไม่แน่ใจว่าทำไมการคลิกนอกส่วนประกอบไม่ได้รับการตรวจพบพร้อมคำตอบอื่น ๆ
JavaQuest

13

หากคุณกำลังทำสิ่งนี้บน 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();
    }
  }

}

10

เราได้พยายามแก้ไขปัญหาที่คล้ายกันในที่ทำงานวันนี้พยายามหาวิธีที่จะทำให้ div แบบเลื่อนลงหายไปเมื่อมันถูกคลิกออกไป เราแตกต่างจากคำถามแรกของผู้โพสต์เล็กน้อยเนื่องจากเราไม่ต้องการคลิกออกไปจากองค์ประกอบหรือคำสั่งที่แตกต่างกันแต่อยู่นอกหน่วยงานเฉพาะ

เราสิ้นสุดการแก้ไขโดยใช้ตัวจัดการเหตุการณ์ (window: mouseup)

ขั้นตอน:
1. ) เราให้ชื่อเมนูคลาสแบบดรอปดาวน์ทั้งหมดแล้ว

2. ) ในเมนูแบบเลื่อนลงภายใน (ส่วนเดียวที่เราต้องการให้คลิกเพื่อไม่ปิดเมนู) เราได้เพิ่มตัวจัดการเหตุการณ์ (window: mouseup) และส่งผ่านไปยัง $ event

หมายเหตุ: ไม่สามารถทำได้ด้วยตัวจัดการ "คลิก" ทั่วไปเนื่องจากสิ่งนี้ขัดแย้งกับตัวจัดการคลิกหลัก

3. ) ในตัวควบคุมของเราเราได้สร้างวิธีการที่เราต้องการที่จะถูกเรียกในเหตุการณ์การคลิกและเราใช้ event.closest ( เอกสารที่นี่ ) เพื่อค้นหาว่าจุดที่ถูกคลิกนั้นอยู่ใน div-class ของเราหรือไม่

 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>


"window: mouseup" ควรใช้ภายใน host decorator
Shivam

@ Shivam - ฉันไม่แน่ใจว่าสิ่งที่คุณหมายถึงโดย "ควรใช้ภายในมัณฑนากรโฮสต์" คุณสามารถอธิบายเพิ่มเติมได้ไหม ขอบคุณ!
Paige Bolduc

ฉันหมายถึงแทนที่จะใช้ "หน้าต่าง" วัตถุโดยตรงคุณควรใช้คุณสมบัติ "โฮสต์" ของมัณฑนากรประกอบ / มัณฑนากร "HostListener" ขององค์ประกอบ นั่นคือการปฏิบัติมาตรฐานในขณะที่ทำงานกับ "หน้าต่าง" หรือ "เอกสาร" วัตถุในเชิงมุม 2
Shivam

2
เพียงคอยจับตาดูความเข้ากันได้ของเบราว์เซอร์.closest()ไม่รองรับ IE / Edge ณ วันนี้ ( caniuse )
superjos

5

คุณสามารถสร้างองค์ประกอบพี่น้องให้กับรายการแบบหล่นลงที่ครอบคลุมทั้งหน้าจอซึ่งจะมองไม่เห็นและอยู่ที่นั่นเพียงเพื่อบันทึกเหตุการณ์การคลิก จากนั้นคุณสามารถตรวจพบการคลิกที่องค์ประกอบนั้นและปิดรายการแบบหล่นลงเมื่อมีการคลิก ให้บอกว่าองค์ประกอบนั้นเป็นของคลาสซิลค์สกรีนนี่คือสไตล์สำหรับมัน:

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

ดัชนี z จำเป็นต้องสูงพอที่จะวางไว้เหนือทุกสิ่งยกเว้นรายการแบบหล่นลงของคุณ ในกรณีนี้ดรอปดาวน์ของฉันคือ b z-index 2

คำตอบอื่น ๆ ใช้งานได้ในบางกรณีสำหรับฉันยกเว้นบางครั้งเมนูแบบเลื่อนลงของฉันปิดเมื่อฉันโต้ตอบกับองค์ประกอบภายในและฉันไม่ต้องการมัน ฉันได้เพิ่มองค์ประกอบแบบไดนามิกที่ไม่ได้อยู่ในองค์ประกอบของฉันตามเป้าหมายเหตุการณ์เช่นที่ฉันคาดไว้ แทนที่จะเรียงลำดับที่ยุ่งเหยิงฉันคิดว่าฉันแค่ลองใช้วิธีซิลค์สกรีน


5

ฉันไม่ได้แก้ปัญหาใด ๆ ฉันเพิ่งแนบเอกสาร: คลิกที่ฟังก์ชันสลับของฉันดังนี้:

    @Directive ({
      ตัวเลือก: '[appDropDown]'
    })
    คลาสส่งออก DropdownDirective ใช้ OnInit {

      @HostBinding ('class.open') isOpen: บูลีน;

      คอนสตรัค (ส่วนตัว elemRef: ElementRef) {}

      ngOnInit (): void {
        this.isOpen = false;
      }

      @HostListener ('เอกสาร: คลิก', ['$ event'])
      @HostListener ('document: touchstart', ['$ event'])
      สลับ (เหตุการณ์) {
        ถ้า (this.elemRef.nativeElement.contain (event.target)) {
          this.isOpen =! this.isOpen;
        } อื่น {
          this.isOpen = false;
      }
    }

ดังนั้นเมื่อฉันอยู่นอกคำสั่งของฉันฉันปิดแบบเลื่อนลง


4
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;
    }
}

DOWNSIDES: - ผู้ฟังเหตุการณ์คลิกสองครั้งสำหรับองค์ประกอบเหล่านี้ในหน้า อย่าใช้สิ่งนี้กับส่วนประกอบที่อยู่บนหน้าหลายร้อยครั้ง


ไม่ฉันใช้เฉพาะในเบราว์เซอร์เดสก์ท็อปเท่านั้น
Alex Egli

3

ฉันต้องการเติมเต็มคำตอบ @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] ...


3

คุณสามารถเขียนคำสั่ง:

@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;
  }
}

เหตุการณ์ directive emit นี้เมื่อองค์ประกอบ HTML มีใน DOM และเมื่อคุณสมบัติอินพุต [clickOut] เป็น 'จริง' ฟังเหตุการณ์ mousedown เพื่อจัดการเหตุการณ์ก่อนที่องค์ประกอบจะถูกลบออกจาก DOM

และหมายเหตุหนึ่ง: firefox ไม่มีคุณสมบัติ 'เส้นทาง' ในเหตุการณ์คุณสามารถใช้ฟังก์ชันเพื่อสร้างเส้นทาง:

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 (เหตุการณ์)

โมดูลนี้สามารถช่วย https://www.npmjs.com/package/ngx-clickout มันมีลอจิกเดียวกัน แต่ยังจัดการเหตุการณ์ esc ในองค์ประกอบ html แหล่งที่มา


3

คำตอบที่ถูกต้องมีปัญหาหากคุณมีองค์ประกอบ clicakble ใน popover ของคุณองค์ประกอบจะไม่อยู่ใน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();
    }
  }
}

ขอบคุณสำหรับความช่วยเหลือ!


2

รุ่นที่ดีกว่าสำหรับ @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: // ไม่จำเป็นถ้าคุณใช้ bootstrap หล่นลง

.ourDropdown{
   display: none;
}
.ourDropdown.open{
   display: inherit;
}

2

คุณควรตรวจสอบว่าคุณคลิกที่โมดูเลย์ซ้อนทับแทนหรือเปล่า

แม่แบบของคุณ:

<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();
    }


  }

2

ฉันได้สั่งให้แก้ไขปัญหาที่คล้ายกันนี้และฉันใช้ Bootstrap แต่ในกรณีของฉันแทนที่จะรอให้คลิกกิจกรรมนอกองค์ประกอบเพื่อปิดเมนูแบบเลื่อนลงที่เปิดอยู่ในปัจจุบันฉันคิดว่ามันจะดีกว่าถ้าเราดูเหตุการณ์ '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>

1

หากคุณใช้ Bootstrap คุณสามารถทำได้โดยตรงด้วยวิธี bootstrap ผ่านทางดรอปดาวน์ (องค์ประกอบ Bootstrap)

<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


1

ฉันยังแก้ปัญหาด้วยตัวเองเล็กน้อย

ฉันสร้างเหตุการณ์(dropdownOpen)ซึ่งฉันฟังที่องค์ประกอบองค์ประกอบ ng-select ของฉันและเรียกใช้ฟังก์ชันที่จะปิด 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 ฉันเพิ่มผู้ฟังเหตุการณ์สำหรับ(ดรอปดาวน์เปิด) :

<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();
    })
}

1

หมายเหตุ:สำหรับผู้ที่ต้องการใช้งานเว็บเวิร์คและคุณต้องหลีกเลี่ยงการใช้เอกสารและเนทิฟเอนทิตีซึ่งจะใช้งานได้

ฉันตอบคำถามเดียวกันที่นี่: /programming/47571144

คัดลอก / วางจากลิงค์ด้านบน:

ฉันมีปัญหาเดียวกันเมื่อฉันทำเมนูแบบเลื่อนลงและกล่องโต้ตอบการยืนยันฉันต้องการยกเลิกเมื่อคลิกด้านนอก

การนำไปใช้ครั้งสุดท้ายของฉันทำงานได้อย่างสมบูรณ์แบบ

หมายเหตุ : ฉันยังไม่ได้ทดสอบโค้ดด้านล่างอาจมีปัญหาเกี่ยวกับไวยากรณ์ที่จำเป็นต้องรีดออกมาอีกทั้งยังมีการปรับเปลี่ยนที่ชัดเจนสำหรับโครงการของคุณเอง!

ฉันทำอะไรลงไป:

ฉันสร้าง div คงที่แยกต่างหากที่มีความสูง 100% ความกว้าง 100% และการแปลง: สเกล (0) นี่คือพื้นหลังเป็นหลักคุณสามารถจัดแต่งมันด้วย background-color: rgba (0, 0, 0, 0.466); เพื่อให้ชัดเจนเมนูเปิดและพื้นหลังคลิกเพื่อปิด เมนูรับดัชนี z สูงกว่าทุกอย่างจากนั้น div พื้นหลังจะได้รับดัชนี z ต่ำกว่าเมนู แต่สูงกว่าทุกอย่าง จากนั้นพื้นหลังจะมีเหตุการณ์คลิกที่ปิดรายการแบบหล่นลง

นี่คือรหัส 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);
}

1

ฉันเจอวิธีแก้ไขปัญหาอื่นโดยได้แรงบันดาลใจจากตัวอย่างที่มีเหตุการณ์โฟกัส / เบลอ

ดังนั้นหากคุณต้องการใช้ฟังก์ชันการทำงานเดียวกันโดยไม่ต้องแนบฟังเอกสารส่วนกลางคุณอาจพิจารณาว่าเป็นตัวอย่างที่ถูกต้อง สามารถใช้งานได้ใน Safari และ Firefox บน OSx แม้จะมีการจัดการเหตุการณ์การโฟกัสปุ่มอื่น ๆ : https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus

ตัวอย่างการทำงานกับ stackbiz ที่มีมุม 8: 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();
  }
}

1

คุณสามารถใช้mouseleaveในมุมมองของคุณเช่นนี้

ทดสอบกับมุม 8 และทำงานได้อย่างสมบูรณ์

<ul (mouseleave)="closeDropdown()"> </ul>

นี่จะปิดที่เก็บเมื่อเมาส์ออก แต่ขอขอบคุณสำหรับการแบ่งปันต่อไปเนื่องจากฉันไม่ทราบว่ามีอยู่จริง
เบ็นเฮย์เวิร์ด

0

วิธีที่สง่างามที่สุด: D

มีวิธีที่ง่ายที่สุดวิธีหนึ่งในการทำเช่นนั้นไม่จำเป็นต้องมีคำสั่งใด ๆ

"element-that-toggle-your-dropdown" ควรเป็นแท็กปุ่ม ใช้วิธีการใด ๆ ในแอตทริบิวต์ (เบลอ) นั่นคือทั้งหมดที่

<button class="element-that-toggle-your-dropdown"
               (blur)="isDropdownOpen = false"
               (click)="isDropdownOpen = !isDropdownOpen">
</button>

สิ่งนี้จะใช้งานไม่ได้หากคุณต้องการเปิดค้างไว้เมื่อคลิกตัวอย่างเช่นผู้ใช้อาจพลาดการคลิกปุ่ม
ใช่เมื่อ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.