是否可以在 ag-Grid-community 中拥有自己的自定义上下文菜单

Is it possible to have own custom Context Menu in ag-Grid-community

找不到确切答案。 如果我决定选择加入 vanilla JavaScript(非 Angular & Co)ag-Grid-community 版本,我可以轻松添加我自己的自定义上下文菜单和其他自定义扩展吗? 正如我看到他们的文档,上下文菜单只是企业级功能。 我看到一些脚踏板有一些警告,但我个人并没有深入挖掘。 总的来说,在ag-Grid-community中实现自建功能有多容易。还是自己写网格比较好?

我们在我们的 Angular 项目中有一个自定义上下文菜单组件与 ag-grid 社区,所以这绝对是可能的。

工作原理:

我们在模板中定义所有网格列。如果你想要一个上下文菜单,你将一个空列放入列集中并在其上放置一个特殊指令。该指令接受一个上下文菜单模板,该模板被传递到自定义 cellRendererFramework(基本上是一个菜单触发按钮)。该指令还配置列以确保跨网格实例的外观一致。

如果您需要用鼠标右键单击连续任意位置打开菜单,这可能不是您一直在寻找的东西,但我想它不应该是 很难从不同的事件触发菜单(查看 ag-grid 事件,可能有合适的东西)。

下面的代码片段应该很简单,可以适应您选择的框架。鉴于您选择了香草 JS,您将不得不使用常规函数来执行相同的操作,如下所示:

const grid = withContextMenu(new Grid(element, gridOptions), menuOptions).

下面是我们如何使用它的示例:

<ag-grid-angular>
  <ag-grid-column headerName='ID' field='id'></ag-grid-column>
  <ag-grid-column [contextMenu]='menu'>
    <mat-menu #menu='matMenu'>
      <ng-template matMenuContent let-item='data'>
        <button mat-menu-item (click)='restoreSnapshot(item.id)'>Restore From Snapshot</button>
        <a mat-menu-item [routerLink]='[item.id, "remove"]'>Remove</a>
      </ng-template>
    </mat-menu>
  </ag-grid-column>
</ag-grid-angular>

应用菜单的指令:

const WIDTH = 42;
export const CONTEXT_MENU_COLID = 'context-menu';

@Directive({
  selector: '[agGridContextMenu]'
})
export class AgGridContextMenuDirective implements AfterViewInit {
  constructor(private gridComponent: AgGridAngular) {}

  @Input()
  agGridContextMenu!: ElementRef<MatMenu>;

  ngAfterViewInit() {
    if (!this.agGridContextMenu) return;
    setTimeout(() => {
      this.gridComponent.api.setColumnDefs([
        ...this.gridComponent.columnDefs,
        {
          colId: CONTEXT_MENU_COLID,
          cellRendererFramework: CellRendererContextMenuComponent,
          width: WIDTH,
          maxWidth: WIDTH,
          minWidth: WIDTH,
          cellStyle: {padding: 0},
          pinned: 'right',
          resizable: false,
          cellRendererParams: {
            suppressHide: true,
            contextMenu: {
              menu: this.agGridContextMenu
            }
          }
        }
      ]);
    });
  }
}

单元格渲染器组件:

@Component({
  selector: 'cell-renderer-context-menu',
  template: `
    <ng-container *ngIf='params.data && params.colDef.cellRendererParams.contextMenu.menu'>
      <button
        type='button'
        mat-icon-button
        [matMenuTriggerFor]='params.colDef.cellRendererParams.contextMenu.menu'
        [matMenuTriggerData]='{data: params.data}'
      >
        <mat-icon svgIcon='fas:ellipsis-v'></mat-icon>
      </button>
    </ng-container>
  `,
  styleUrls: ['./cell-renderer-context-menu.component.scss']
})
export class CellRendererContextMenuComponent implements ICellRendererAngularComp {
  params!: ICellRendererParams;
  agInit(params: ICellRendererParams) {
    this.params = params;
  }
  refresh() {
    return false;
  }
}

截图:

我关注了这篇博文,使用的是社区版 ag-grid,它成功了!我很惊讶,因为以前我有过单元格渲染器不允许显示单元格边界之外的内容的经验,但是 popper/tippy 以某种方式解决了这个问题(我认为它会将自己添加到 DOM 加上这段代码 appendTo: document.body).

https://blog.ag-grid.com/creating-popups-in-ag-grid/

基本上,在我的 javascript CellRenderer:

class MyCellRenderer{
  // https://www.ag-grid.com/javascript-data-grid/component-cell-renderer/
  init(e){
    this.isOpen = false;
    this.container = document.createElement("span");
    let menubutton = document.createElement("button");
    menubutton.innerHTML="&#x1F80B"; //downward arrow
    this.tippyInstance = tippy(menubutton);
    this.tippyInstance.disable();
    this.container.appendChild(menubutton);
    menubutton.addEventListener('click', that.togglePopup.bind(this));
  }

  getGui() {
    return this.container;
  }

  togglePopup() {
      this.isOpen = !this.isOpen;
      if (this.isOpen) {
        this.configureTippyInstance();
        this.eMenu = this.createMenuComponent();
        this.tippyInstance.setContent(this.eMenu);
      } else {
        this.tippyInstance.unmount();
      }
  }

  configureTippyInstance() {
    this.tippyInstance.enable();
    this.tippyInstance.show();
    this.tippyInstance.setProps({
      trigger: 'manual',
      placement: 'bottom-start',
      arrow: false,
      interactive: true,
      appendTo: document.body,
      hideOnClick: true,
      onShow: (instance) => {
        tippy.hideAll({ exclude: instance });
      },
      onClickOutside: (instance, event) => {
        this.isOpen = false;
        instance.unmount();
      },
    });
  }

  createMenuComponent() {
    let menu = document.createElement('div');
    menu.classList.add('menu-container');
    let options = {};
    options['Delete Row'] = this.menuItemClickHandler.bind(this);
    options['Popup an Alert!'] = function(){alert("hello!");};
    options['Popup an Alert 2!'] = this.menuItemClickHandler.bind(this);

    for (const [key, value] of Object.entries(options)) {
      let item = document.createElement('div');
      item.classList.add('menu-item');
      item.setAttribute('data-action', key.toLowerCase());
      item.classList.add('hover_changes_color');
      item.innerText = `${key}`; // string formatting example
      item.addEventListener('click', value);
      menu.appendChild(item);
    }
    return menu;
  }

  menuItemClickHandler(event) {
    this.togglePopup();
    const action = event.target.dataset.action;
    if (action === 'delete row') {
        this.params.api.applyTransaction({ remove: [this.params.data] });
    }
    if (action === 'popup an alert 2!') {
      alert("2");
    }
  }
}

并在 styles.css 中:

.hover_changes_color:hover {
  background-color: dimgrey;
  cursor: pointer;
}