2016-01-18 12 views
12

ich eine Angular 2 Attribut Richtlinie bin Umsetzung mir zu erlauben, wie dies eine benutzerdefinierte Kontextmenü ein Element hinzuzufügen:Angular 2: Implementieren eines benutzerdefinierten Kontextmenü

<p context-menu="myItems">Hello world</p> 

Diese Richtlinie fügt eine Maus-Ereignishandler Klicken Sie mit der rechten Maustaste, und die Idee dahinter ist, ein Kontextmenü zu erstellen, es dem DOM hinzuzufügen und es dann zu zerstören, wenn der Benutzer damit fertig ist.

Ich habe eine Komponente, die das Kontextmenü selbst implementiert. Ich möchte diese Komponente konstruieren, eine Methode aufrufen, um die Elementliste festzulegen, und sie dann zum DOM hinzufügen.

Es sieht so aus, als könnte ich dies mit AppViewManager.createHostViewInContainer tun. Ist dies ein geeigneter Weg, dies zu tun? Und wenn ja, gibt es eine Möglichkeit, ein ElementRef zu document.body zu konstruieren/zu bekommen, so dass ich createHostViewInContainer sagen kann, um die Komponente dort zu konstruieren? Offensichtlich möchte ich nicht, dass mein Menü innerhalb des Elements, dem ich das Kontextmenü hinzufüge, abgeschnitten wird.

+0

Ist es wirklich wichtig, wo Sie die Komponente einfügen? Sie können es einfach absolut positionieren. Ich würde der 'AppComponent' eine Kontext-Menü-Komponente hinzufügen und Anweisungen senden, was sie mit einem gemeinsam genutzten globalen Service anzeigen soll. Hete ist ein Beispiel, in dem HTML dem Körper, aber nicht den Komponenten dynamisch hinzugefügt wird. AFAIK https://github.com/angular/angular/blob/master/modules/angular2_material/src/components/dialog/dialog.ts –

+2

Es ist wichtig wenn der Elternteil "overflow: hidden" hat, was viele tun. –

+1

Ich habe tatsächlich mit "position: fixed" aufgewickelt, was den "overflow: hidden" der Eltern zu ignorieren scheint. Es fühlt sich ein wenig dreckig an, aber es macht den Job erledigt. –

Antwort

23

Hier ist, was ich denke, ist ein guter Weg, es zu tun.
Sie benötigen 1 Service, 1 Komponente und 1 Direktive.

Here is a plunker

Erläuterung:

Der Service ContextMenuService:

  • ein Thema vom Typ bietet {event:MouseEvent,obj:any[]} durch ContextMenuHolderComponent abonniert zu sein und Werte zu erhalten von ContextMenuDirective

Code:

import {Injectable} from 'angular2/core'; 
import {Subject} from 'rxjs/Rx'; 

@Injectable() 
export class ContextMenuService{ 

    public show:Subject<{event:MouseEvent,obj:any[]}> = new Subject<{event:MouseEvent,obj:any[]}>(); 
} 

und fügen Sie es in die Liste der Anbieter in bootstrap()

bootstrap(AppComponent,[ContextMenuService]); 

Die Komponente ContextMenuHolderComponent:

  • Diese c omponent wird innerhalb der Root-Komponente hinzugefügt. z.B. AppComponent und es hat eine fixed Position.
  • Es schließt sich der subject in ContextMenuService zu erhalten:

    1. Menüpunkte vom Typ {title:string,subject:Subject}[] wird das Objekt verwendet, um die angeklickt Wert innerhalb des Menüs zu senden
    2. Mouseevent-Objekt
  • Es verfügt über einen Ereignis-Listener (document:click), um das Menü bei Klicks außerhalb des Menüs zu schließen.

Code:

@Component({ 
    selector:'context-menu-holder', 
    styles:[ 
    '.container{width:150px;background-color:#eee}', 
    '.link{}','.link:hover{background-color:#abc}', 
    'ul{margin:0px;padding:0px;list-style-type: none}' 
    ], 
    host:{ 
    '(document:click)':'clickedOutside()' 
    }, 
    template: 
    `<div [ngStyle]="locationCss" class="container"> 
     <ul> 
      <li (click)="link.subject.next(link.title)" class="link" *ngFor="#link of links"> 
       {{link.title}} 
      </li> 
     </ul> 
    </div> 
    ` 
}) 
class ContextMenuHolderComponent{ 
    links = []; 
    isShown = false; 
    private mouseLocation :{left:number,top:number} = {left:0;top:0}; 
    constructor(private _contextMenuService:ContextMenuService){ 
    _contextMenuService.show.subscribe(e => this.showMenu(e.event,e.obj)); 
    } 
    // the css for the container div 
    get locationCss(){ 
    return { 
     'position':'fixed', 
     'display':this.isShown ? 'block':'none', 
     left:this.mouseLocation.left + 'px', 
     top:this.mouseLocation.top + 'px', 
    }; 
    } 
    clickedOutside(){ 
    this.isShown= false; // hide the menu 
    } 

    // show the menu and set the location of the mouse 
    showMenu(event,links){ 
    this.isShown = true; 
    this.links = links; 
    this.mouseLocation = { 
     left:event.clientX, 
     top:event.clientY 
    } 
    } 
} 

Und es um die Wurzelkomponente hinzuzufügen:

@Component({ 
    selector: 'my-app', 
    directives:[ContextMenuHolderComponent,ChildComponent], 
    template: ` 
    <context-menu-holder></context-menu-holder> 
    <div>Whatever contents</div> 
    <child-component></child-component> 
    ` 
}) 
export class AppComponent { } 

Die letzte, ContextMenuDirective:

  • Es fügt dem Host-Element ein contextmenu-Ereignis hinzu.
  • Übernehmen Sie eine Eingabe einer Liste von Elementen, die an ContextMenuHolderComponent übergeben werden sollen.

Code:

@Directive({ 
    selector:'[context-menu]', 
    host:{'(contextmenu)':'rightClicked($event)'} 
}) 
class ContextMenuDirective{ 
    @Input('context-menu') links; 
    constructor(private _contextMenuService:ContextMenuService){ 
    } 
    rightClicked(event:MouseEvent){ 
    this._contextMenuService.show.next({event:event,obj:this.links}); 
    event.preventDefault(); // to prevent the browser contextmenu 
    } 
} 

Das ist es. Alles, was Sie jetzt tun müssen, ist die Anweisung an ein Element anzuhängen und es an eine Liste von Elementen zu binden. Zum Beispiel:

@Component({ 
    selector:'child-component', 
    directives:[ContextMenuDirective], 
    template:` 
    <div [context-menu]="links" >right click here ... {{firstRightClick}}</div> 
    <div [context-menu]="anotherLinks">Also right click here...{{secondRightClick}}</div> 
    ` 
}) 
class ChildComponent{ 
    firstRightClick; secondRightClick; 
    links; 
    anotherLinks; 
    constructor(){ 
    this.links = [ 
     {title:'a',subject:new Subject()}, 
     {title:'b',subject:new Subject()}, 
     {title:'b',subject:new Subject()} 
    ]; 
    this.anotherLinks = [ 
     {title:'link 1',subject:new Subject()}, 
     {title:'link 2',subject:new Subject()}, 
     {title:'link 3',subject:new Subject()} 
    ]; 
    } 

    // subscribe to subjects 
    ngOnInit(){ 
    this.links.forEach(l => l.subject.subscribe(val=> this.firstCallback(val))); 
    this.anotherLinks.forEach(l => l.subject.subscribe(val=> this.secondCallback(val))) 
    } 
    firstCallback(val){ 
    this.firstRightClick = val; 
    } 
    secondCallback(val){ 
    this.secondRightClick = val; 
    } 
} 
+0

Kannst du bitte erklären, was '=' hier in 'public show bedeutet: Subject <{event: MouseEvent, obj: any []}> = neues Subject();'. Ich kann keine Informationen über diese Notation finden. 'tsc' beschwert sich über' Type 'Subject <{}>' ist nicht dem Typ 'Subject ...' zuweisbar. –

+2

@Zhytkevich Dies ist nur ein neues Thema zu 'show' zuweisen. Die Beschwerde ist, weil ich das Thema "Typ" nicht angegeben habe. Sie können es loswerden, indem Sie 'public show: Betreff <{event: MouseEvent, obj: any []}> = neues Thema <{event: MouseEvent, obj: any []}>(); Oder einfach indem man das Subjekt vom Typ 'any' wie' public show 'macht: Betreff = new Betreff (); ' – Abdulrahman

+0

Tolle Antwort – Ryan

Verwandte Themen