且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

角度2:实现自定义上下文菜单

更新时间:2023-08-31 21:54:10

这是我认为的一种好方法.
您需要1个服务,1个组件和1个指令.

Here is what I think is a good way to do it.
You need 1 service, 1 component and 1 directive.

这里是一个小矮人

说明:

服务ContextMenuService:

  • 提供类型为{event:MouseEvent,obj:any[]}的主题为 由ContextMenuHolderComponent订阅并接收值 来自ContextMenuDirective
    • provides a subject of type {event:MouseEvent,obj:any[]} to be subscribed to by ContextMenuHolderComponent, and to receive values from 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.
  • 它订阅ContextMenuService中的subject来接收:

    • This component is added inside the root component. e.g. AppComponent and it has a fixed position.
    • It subscribes to the subject in ContextMenuService to receive:

      1. {title:string,subject:Subject}[]类型的菜单项,主题用于发送菜单内单击的值
      2. MouseEvent对象
      1. menu items of type {title:string,subject:Subject}[], the subject is used to send the clicked on value inside the menu
      2. MouseEvent object

    • 它具有一个(document:click)事件侦听器,可以在菜单外单击时关闭菜单.

    • It has a (document:click) event listener to close the menu on clicks outside the menu.

      代码:

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

      并将其添加到根组件中:

      And add it to the root component:

      @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的项目列表的输入.
        • It adds a contextmenu event to the host element.
        • Accept an input of a list of items to be passed to 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]指令附加到元素并将其绑定到项目列表.例如:

      That's it. All you need to do now is attach the [context-menu] directive to an element and bind it to a list of items. For example:

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