Подтвердить что ты не робот

Angular 2: Внедрение настраиваемого контекстного меню

Я реализую директиву атрибута Angular 2, чтобы я мог добавить настраиваемое контекстное меню к элементу, подобному этому:

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

Эта директива добавляет обработчик события мыши, чтобы щелкнуть правой кнопкой мыши, и идея состоит в том, чтобы затем построить контекстное меню, добавить его в DOM и затем уничтожить, когда пользователь закончит с ним.

У меня есть компонент, который реализует контекстное меню. Я хотел бы построить этот компонент, вызвать метод на нем, чтобы установить список элементов, а затем добавить его в DOM.

Похоже, я мог бы сделать это с помощью AppViewManager.createHostViewInContainer. Это подходящий способ сделать это? И если да, есть ли способ построить/получить ElementRef до document.body, чтобы я мог показать createHostViewInContainer, чтобы построить там компонент? Очевидно, я не хочу, чтобы мое меню было обрезано внутри элемента, в которое я добавляю контекстное меню.

4b9b3361

Ответ 1

Вот что я думаю, это хороший способ сделать это.
Вам нужно 1 сервис, 1 компонент и 1 директива.

Вот плункер

Объяснение:

Служба ContextMenuService:

  • предоставляет объект типа {event:MouseEvent,obj:any[]} для подписанный на ContextMenuHolderComponent, и получать значения от ContextMenuDirective

код:

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

И добавьте его в список поставщиков в bootstrap()

bootstrap(AppComponent,[ContextMenuService]);

Компонент ContextMenuHolderComponent:

  • Этот компонент добавляется внутри корневого компонента. например AppComponent и имеет положение fixed.
  • Он подписывается на subject в ContextMenuService для получения:

    • элементы меню типа {title:string,subject:Subject}[], объект используется для отправки щелчка по значению внутри меню
    • Объект MouseEvent
  • Он имеет прослушиватель событий (document:click), чтобы закрыть меню на кликах вне меню.

код:

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

И добавьте его в корневой компонент:

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

Последний, ContextMenuDirective:

  • Он добавляет событие contextmenu к элементу узла.
  • Принять ввод списка элементов, которые будут переданы в ContextMenuHolderComponent.

код:

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

Что это. Теперь вам нужно приложить директиву [context-menu] к элементу и привязать его к списку элементов. Например:

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