{
"name": "ag-grid-packages",
"description": "NOTE: This package.json file is solely used by Plunker to look up type definitions.",
"dependencies": {
"@angular/core": "^15",
"ag-grid-angular": "^29.0.0",
"ag-grid-community": "^29.0.0",
"ag-grid-enterprise": "^29.0.0",
"rxjs": "^7.8.0",
"@mdi/js": "^7.1.96",
"@ng-bootstrap/ng-bootstrap": "^14.0.1",
"bootstrap": "^5.2.3"
}
}
import { Component, HostListener, OnInit } from '@angular/core';
import {
CellClickedEvent,
ColDef,
ColumnApi,
GetRowIdFunc,
GetRowIdParams,
GridApi,
GridReadyEvent,
ICellRendererParams,
} from 'ag-grid-community';
import 'ag-grid-enterprise';
import { Observable, of, tap } from 'rxjs';
import {
AgCellLink,
AgGridDataModel,
AgTag,
AgTagRemoveEvent,
AgTagRenameEvent,
} from './models/ag-cell-link.model';
import { ContextMenuService } from './services/context-menu.service';
import { CellIconComponent } from './components/cell-icon/cell-icon.component';
import { CellLinkComponent } from './components/cell-link/cell-link.component';
import { CellTagComponent } from './components/cell-tag/cell-tag.component';
import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-balham.css';
import 'ag-grid-enterprise';
@Component({
selector: 'my-app',
template: `<div style="height: 400px">
<div class="p-1 d-flex">
<div>
<button type="button" class="btn btn-chambers-cream py-0">
<span style="font-size: 10px; font-weight: 500" (click)="addTagColumn()">Add Tag Column</span>
</button>
</div>
<div class="ps-2">
<input class="form-control form-control-sm" [(ngModel)]="columnName" />
</div>
</div>
<ag-grid-angular
style="width: 100%; height: 100%"
class="ag-theme-balham"
[rowHeight]="34"
[columnDefs]="columnDefs"
[defaultColDef]="defaultColDef"
[rowData]="rowData$ | async"
[getRowId]="getRowId"
[rowSelection]="'multiple'"
[animateRows]="true"
(columnEverythingChanged)="columnsChanged($event)"
(gridReady)="onGridReady($event)"
(cellClicked)="onCellClicked($event)"></ag-grid-angular>
<app-ag-add-tag
*ngIf="isDisplayContextMenu$ | async"
[ngStyle]="menuPosition$ | async"
[contextMenuItems]="(rightClickMenuItems$ | async) ?? []"
(contextMenuItemClick)="handleMenuItemClick($event)"
(removeTagClick)="removeTag($event)"
(renameTagClick)="renameTag($event)"></app-ag-add-tag>
</div>`,
})
export class AppComponent implements OnInit {
//context menu
title = 'context-menu';
isDisplayContextMenu$!: Observable<boolean>;
rightClickMenuItems$!: Observable<any>;
menuPosition$!: Observable<any>;
columnName = '';
// DefaultColDef sets props common to all Columns
public defaultColDef: ColDef = {
sortable: true,
filter: true,
autoHeaderHeight: true,
//floatingFilter: true, //filter showing all the time as the second line
};
private gridColumnApi!: ColumnApi;
private gridApi!: GridApi<any>;
public getRowId: GetRowIdFunc = (params: GetRowIdParams) => params.data.id;
// Each Column Definition results in one Column.
public columnDefs: ColDef[] = [
{
field: 'practiceArea',
headerName: 'Chambers Practice Area',
wrapText: true,
wrapHeaderText: true,
width: 160,
// headerComponent: HeaderWithContextComponent,
// headerComponentParams: {
// field: 'practiceArea',
// headerName: 'Chambers Practice Area',
// enableMenu: true,
// enableSorting: true,
// },
sortable: true,
// cellRendererSelector: (params: ICellRendererParams<any, AgCellLink>) => {
// return {
// component: CellLinkComponent,
// params: {
// value: {
// link: params.value.link,
// text: params.value.text,
// },
// },
// };
// },
comparator: (
// used to sort
valueA: AgCellLink,
valueB: AgCellLink,
nodeA,
nodeB,
isDescending,
) => {
if (valueA.text == valueB.text) return 0;
return valueA.text > valueB.text ? 1 : -1;
},
cellRenderer: function (params: any) {
return (
'<a href="' +
params.value.link +
'" target="_blank" rel="noopener">' +
params.value.text +
'</a>'
);
},
filterValueGetter: (params) => params.data.practiceArea.text,
},
{
field: 'division',
headerName: 'Division',
wrapText: true,
wrapHeaderText: true,
width: 160,
suppressAutoSize: true,
//hide: true,
cellRendererSelector: (params: ICellRendererParams<any, AgTag[]>) => {
return {
component: CellTagComponent,
params: { value: params.value },
};
},
filterValueGetter: (params) => {
return params.data.division?.flatMap((tag: AgTag) => tag.text);
},
},
{
field: 'location',
headerName: 'Location',
wrapText: true,
wrapHeaderText: true,
suppressAutoSize: true,
width: 160,
//hide: true,
cellRendererSelector: (params: ICellRendererParams<any, AgTag[]>) => {
return {
component: CellTagComponent,
params: { value: params.value },
};
},
filterValueGetter: (params) => {
return params.data.division?.flatMap((tag: AgTag) => tag.text);
},
},
{
field: 'rankingStatus',
headerName: 'Ranking Status',
wrapText: true,
wrapHeaderText: true,
width: 110,
filter: true,
cellRendererSelector: (params: ICellRendererParams<any, string>) => {
return {
component: CellIconComponent,
params: {
value: params.value,
},
};
},
},
{
field: 'ranking2023',
headerName: 'Rankings (2023)',
wrapText: true,
wrapHeaderText: true,
width: 100,
filter: true,
cellRendererSelector: (params: ICellRendererParams<any, AgCellLink>) => {
return {
component: CellLinkComponent,
params: {
value: {
link: params.value.link,
text: params.value.text,
},
},
};
},
filterValueGetter: (params) => params.data.ranking2023.text,
},
{
field: 'ranking2022',
headerName: 'Rankings (2022)',
wrapText: true,
wrapHeaderText: true,
width: 100,
filter: true,
},
];
// Data that gets displayed in the grid
public rowData$!: Observable<any[]>;
constructor(private contextMenuService: ContextMenuService) {}
ngOnInit(): void {
console.log('loaded');
this.isDisplayContextMenu$ =
this.contextMenuService.isDisplayContextMenu$.pipe(
tap((display) => {
console.log('display', display);
}),
);
this.rightClickMenuItems$ =
this.contextMenuService.rightClickMenuItems$.pipe(
tap((items) => {
console.log('items', items);
}),
);
this.menuPosition$ = this.contextMenuService.menuPosition$.pipe(
tap((menu) => {
console.log('menu', menu);
}),
);
}
addTagColumn(columnField?: string, columnName?: string): void {
columnField = this.columnName;
columnName = this.columnName;
this.columnDefs.push({
field: columnField ?? 'test',
headerName: columnName ?? 'test',
wrapText: true,
wrapHeaderText: true,
cellRendererSelector: (params: ICellRendererParams<any, AgTag[]>) => {
return {
component: CellTagComponent,
params: { value: params.value },
};
},
});
this.gridApi.setColumnDefs(this.columnDefs);
}
onGridReady(params: GridReadyEvent) {
console.log('gridReady', params);
this.rowData$ = of(this.jsonData);
this.gridApi = params.api;
this.gridColumnApi = params.columnApi;
// this.gridOptions.getRowNodeId = (row: AgGridDataModel) => {
// return row.id; // return the property you want set as the id.
// };
// let allColumnIds: any = [];
// this.gridColumnApi.getColumns()?.forEach(function (column: any) {
// allColumnIds.push(column.colId);
// });
// this.gridColumnApi.autoSizeColumns(allColumnIds);
}
columnsChanged(event: any): void {
console.log(event);
}
onCellClicked(cell: CellClickedEvent): void {
console.log('cellClicked', cell);
this.contextMenuService.setCellClicked(cell.data, cell.column.getColId());
}
jsonData: AgGridDataModel[] = [
{
id: 1,
practiceArea: {
link: 'blabla',
text: 'Banking & Finance',
} as AgCellLink,
division: [
{
text: 'Finance',
},
] as AgTag[],
location: [],
rankingStatus: 'Improved',
ranking2023: {
link: 'Band 1',
text: 'Band 1',
},
ranking2022: 'Band 1',
},
{
id: 2,
practiceArea: {
link: 'blabla',
text: 'Charities',
} as AgCellLink,
division: [
{
text: 'Social',
},
{
text: 'Youth',
},
] as AgTag[],
location: [],
rankingStatus: 'No Change',
ranking2023: {
link: 'Band 6',
text: 'Band 6',
},
ranking2022: 'Band 4',
},
{
id: 3,
practiceArea: {
link: 'blabla',
text: 'Aviation',
} as AgCellLink,
division: [],
location: [],
rankingStatus: 'Downgraded',
ranking2023: {
link: 'Band 6',
text: 'Band 6',
},
ranking2022: 'Band 6',
},
{
id: 4,
practiceArea: {
link: 'blabla',
text: 'Comercial',
} as AgCellLink,
division: [],
location: [],
rankingStatus: 'Removed',
ranking2023: {
link: 'Band 1',
text: 'Band 1',
},
ranking2022: 'Band 1',
},
{
id: 5,
practiceArea: {
link: 'blabla',
text: 'Dispute Resolution',
} as AgCellLink,
division: [],
location: [],
rankingStatus: 'Improved',
ranking2023: {
link: 'Band 2',
text: 'Band 2',
},
ranking2022: 'Band 2',
},
];
handleMenuItemClick(tag: AgTag) {
const cellClicked = this.contextMenuService.cellClicked;
var rowNode = this.gridApi.getRowNode(cellClicked.data.id)!;
rowNode.setDataValue(
cellClicked.colId,
cellClicked.data?.[cellClicked.colId]?.length > 0
? cellClicked.data?.[cellClicked.colId].concat(tag)
: [tag],
);
this.contextMenuService.hideContextMenu();
this.contextMenuService.hideContextMenuChild();
}
@HostListener('document:click', ['$event'])
documentClick(event: any): void {
this.hideAddTagContextMenu(event);
}
removeTag(event: AgTagRemoveEvent): void {
const rowIds = this.jsonData.flatMap((row) => row.id);
rowIds.forEach((rowId) => {
var rowNode = this.gridApi.getRowNode(rowId.toString())!;
rowNode.setDataValue(
'division',
rowNode.data?.['division']?.filter(
(data: AgTag) => data.text != event.tag,
),
);
rowNode.setDataValue(
'location',
rowNode.data?.['location']?.filter(
(data: AgTag) => data.text != event.tag,
),
);
});
}
renameTag(tag: AgTagRenameEvent): void {
const rowIds = this.jsonData.flatMap((row) => row.id);
rowIds.forEach((rowId) => {
var rowNode = this.gridApi.getRowNode(rowId.toString())!;
const currentDivisionRow = rowNode.data?.['division'].filter(
(data: AgTag) => data.text === tag.oldTag,
) as AgTag[];
const currentDivisionColumn = currentDivisionRow.find(
(data: AgTag) => data.text === tag.oldTag,
) as AgTag;
if (currentDivisionColumn) {
currentDivisionColumn.text = tag.newTag;
rowNode.setDataValue('division', currentDivisionRow);
}
const currentLocationRow = rowNode.data?.['location'].filter(
(data: AgTag) => data.text === tag.oldTag,
) as AgTag[];
const currentLocationColumn = currentLocationRow.find(
(data: AgTag) => data.text === tag.oldTag,
) as AgTag;
if (currentLocationColumn) {
currentLocationColumn.text = tag.newTag;
rowNode.setDataValue('location', currentLocationRow);
}
});
this.contextMenuService.hideContextMenuChild();
}
private hideAddTagContextMenu(event: Event): void {
if (this.contextMenuService.clickInside) {
} else {
if (
!['ag-new-tag', 'rename-tag-input'].includes((event.target as any).id)
) {
this.contextMenuService.hideContextMenu();
this.contextMenuService.hideContextMenuChild();
}
}
}
}
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AgGridModule } from 'ag-grid-angular';
import { AppComponent } from './app.component';
import { ContextMenuService } from './services/context-menu.service';
import { FormsModule } from '@angular/forms';
import { CellTagComponent } from './components/cell-tag/cell-tag.component';
import { CellLinkComponent } from './components/cell-link/cell-link.component';
import { CellIconComponent } from './components/cell-icon/cell-icon.component';
import { AgAddTagComponent } from './components/ag-add-tag/ag-add-tag.component';
import { RemoveTagPopoverComponent } from './components/ag-add-tag/components/remove-tag-popover/remove-tag-popover.component';
import { RenameTagModalComponent } from './components/ag-add-tag/components/rename-tag-modal/rename-tag-modal.component';
import { NgbModule } from '../node_modules/@ng-bootstrap/ng-bootstrap';
@NgModule({
imports: [BrowserModule, HttpClientModule, AgGridModule, FormsModule,
NgbModule],
declarations: [
AppComponent,
CellTagComponent,
CellLinkComponent,
CellIconComponent,
AgAddTagComponent,
RemoveTagPopoverComponent,
RenameTagModalComponent
],
providers: [ContextMenuService],
bootstrap: [AppComponent],
})
export class AppModule {}
export interface IData {
allRankings: string;
field2: string;
field3: string;
field4: string;
field5: string;
field6: string;
field7: string;
field8: string;
field9: string;
field10: string;
field11: string;
field12: string;
field13: string;
field14: string;
}
export interface AgCellLink {
text: string;
link: string;
}
export interface AgTag {
text: string;
}
export interface TagClickedModel {
colId: string;
data: any;
}
interface IObjectKeys {
[key: string]: string | number | AgCellLink | AgTag[];
}
export interface AgGridDataModel extends IObjectKeys {
id: number;
practiceArea: AgCellLink;
division: AgTag[];
location: AgTag[];
rankingStatus: string;
ranking2023: AgCellLink;
ranking2022: string;
}
export interface AgTagRemoveEvent {
tag: string;
}
export interface AgTagRenameEvent {
oldTag: string;
newTag: string;
}
import { mdiPlusCircleOutline, mdiDotsVertical } from '@mdi/js';
export const mdiIcons = {
mdiPlusCircleOutline,
mdiDotsVertical,
};
<div class="d-flex">
<div *ngFor="let tag of tags" class="px-1">
<button type="button" class="btn btn-sm btn-chambers-dark-blue-grey py-0 px-1">
<span class="px-1" style="font-size: 10px; font-weight: 500">{{ tag.text | uppercase }} </span>
<!-- (click)="addTag($event)" -->
</button>
</div>
<div *ngIf="tags?.length === 0" class="px-1">
<button type="button" class="btn btn-sm btn-chambers-cream py-0" (click)="displayContextMenu($event); (false)">
<span style="font-size: 10px; font-weight: 500" id="ag-new-tag">NEW TAG </span>
<cmb-mdi-icon size="13" [path]="mdiIcons.mdiPlusCircleOutline"></cmb-mdi-icon>
</button>
</div>
</div>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CellTagComponent } from './cell-tag.component';
describe('CellTagComponent', () => {
let component: CellTagComponent;
let fixture: ComponentFixture<CellTagComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [CellTagComponent],
}).compileComponents();
fixture = TestBed.createComponent(CellTagComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component } from '@angular/core';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
import { AgTag } from '../../models/ag-cell-link.model';
import { mdiIcons } from '../../models/mdi-icons';
import { ContextMenuService } from '../../services/context-menu.service';
@Component({
selector: 'app-cell-tag',
templateUrl: './cell-tag.component.html',
styleUrls: ['./cell-tag.component.scss'],
})
export class CellTagComponent implements ICellRendererAngularComp {
public tags: AgTag[] = [];
private params!: ICellRendererParams<any, AgTag[]>;
mdiIcons = mdiIcons;
constructor(private contextMenuService: ContextMenuService) {}
agInit(params: ICellRendererParams<any, AgTag[]>): void {
this.params = params;
this.setTags(params);
}
refresh(params: ICellRendererParams<any, AgTag[]>): boolean {
this.params = params;
this.setTags(params);
return true;
}
addTag(event: any): void {
console.log('clickButton', this.params.data);
}
displayContextMenu(event: any): void {
this.contextMenuService.displayContextMenu(event);
}
private setTags(params: ICellRendererParams<any, AgTag[]>) {
this.tags = params.value ?? [];
}
}
<a [href]="data.link" target="_blank">{{ data.text }} </a>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CellLinkComponent } from './cell-link.component';
describe('CellLinkComponent', () => {
let component: CellLinkComponent;
let fixture: ComponentFixture<CellLinkComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [CellLinkComponent],
}).compileComponents();
fixture = TestBed.createComponent(CellLinkComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component } from '@angular/core';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
import { AgCellLink } from '../../models/ag-cell-link.model';
@Component({
selector: 'app-cell-link',
templateUrl: './cell-link.component.html',
styleUrls: ['./cell-link.component.scss'],
})
export class CellLinkComponent implements ICellRendererAngularComp {
data!: AgCellLink;
agInit(params: ICellRendererParams<any, AgCellLink>): void {
this.setLink(params);
}
refresh(params: ICellRendererParams<any, AgCellLink>): boolean {
this.setLink(params);
return true;
}
private setLink(params: ICellRendererParams<any, AgCellLink>) {
this.data = params.value;
}
}
<div class="d-flex align-items-baseline justify-content-start">
<img [src]="iconPath" class="pe-2" />
<span>{{ text }}</span>
</div>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CellIconComponent } from './cell-icon.component';
describe('CellIconComponent', () => {
let component: CellIconComponent;
let fixture: ComponentFixture<CellIconComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [CellIconComponent],
}).compileComponents();
fixture = TestBed.createComponent(CellIconComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component } from '@angular/core';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
@Component({
selector: 'app-cell-icon',
templateUrl: './cell-icon.component.html',
styleUrls: ['./cell-icon.component.scss'],
})
export class CellIconComponent implements ICellRendererAngularComp {
text!: string;
iconPath!: string;
agInit(params: ICellRendererParams<any, string>): void {
this.setLink(params);
}
refresh(params: ICellRendererParams<any, string>): boolean {
this.setLink(params);
return true;
}
private setLink(params: ICellRendererParams<any, string>) {
this.text = params.value;
if (params.value === 'Improved') {
this.iconPath = './assets/icons/ag-up-icon.svg';
} else if (params.value === 'No Change') {
this.iconPath = './assets/icons/ag-middle-icon.svg';
} else if (params.value === 'Downgraded') {
this.iconPath = './assets/icons/ag-down-icon.svg';
} else if (params.value === 'Removed') {
this.iconPath = './assets/icons/ag-remove-icon.svg';
}
}
}
<div class="bg-white border border-1 rounded">
<a class="dropdown-item w-100 p-1 border border-1 cursor-pointer menu-item" (click)="renameTag()"
><span style="font-size: 12px">Rename tag</span></a
>
<a class="dropdown-item w-100 p-1 border border-1 cursor-pointer menu-item" (click)="removeTag()"
><span style="font-size: 12px">Remove tag</span></a
>
</div>
.menu-item:hover {
background-color: lightgray;
}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RemoveTagPopoverComponent } from './remove-tag-popover.component';
describe('RemoveTagPopoverComponent', () => {
let component: RemoveTagPopoverComponent;
let fixture: ComponentFixture<RemoveTagPopoverComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [RemoveTagPopoverComponent],
}).compileComponents();
fixture = TestBed.createComponent(RemoveTagPopoverComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { DestroyableComponent } from '@chambersp/ng-devkit';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { takeUntil } from 'rxjs';
import {
AgTagRemoveEvent,
AgTagRenameEvent,
} from 'src/app/ag-grid/models/ag-cell-link.model';
import { ContextMenuService } from 'src/app/ag-grid/services/context-menu.service';
import { RenameTagModalComponent } from '../rename-tag-modal/rename-tag-modal.component';
@Component({
selector: 'app-remove-tag-popover',
templateUrl: './remove-tag-popover.component.html',
styleUrls: ['./remove-tag-popover.component.scss'],
})
export class RemoveTagPopoverComponent
extends DestroyableComponent
implements OnInit
{
tag!: string;
modalRef!: NgbModalRef;
@Output() removeTagClick: EventEmitter<AgTagRemoveEvent> =
new EventEmitter<AgTagRemoveEvent>();
@Output() renameTagClick: EventEmitter<AgTagRenameEvent> =
new EventEmitter<AgTagRenameEvent>();
constructor(
private contextMenuService: ContextMenuService,
private modalService: NgbModal,
) {
super();
}
ngOnInit(): void {
this.contextMenuService.tag$
.pipe(takeUntil(this.destroyed$))
.subscribe((tag) => (this.tag = tag.text));
}
removeTag(): void {
//open with confirmation to remove
this.contextMenuService.hideContextMenuChild();
this.removeTagClick.emit({
tag: this.tag,
});
}
renameTag(): void {
//open modal to rename
this.modalRef = this.modalService.open(RenameTagModalComponent, {
size: 'sm',
centered: true,
windowClass: 'modal-class',
});
this.modalRef.componentInstance.oldTag = this.tag;
this.modalRef.dismissed.subscribe((text) => {
this.renameTagClick.emit({
oldTag: this.tag,
newTag: text,
});
});
}
}
<div class="bg-white border border-1 d-flex flex-column align-items-center">
<div class="p-2">Rename tag {{ oldTag }}</div>
<div class="w-100 p-2 bg-chambers-cream-200">
<input id="rename-tag-input" type="text" placeholder="tag name" [(ngModel)]="text" class="w-100 form-control form-control-sm rounded-3" />
</div>
<div class="p-2">
<button type="button" class="btn btn-chambers-dark-blue-grey py-0 px-1">
<span class="px-1" style="font-size: 10px; font-weight: 500" (click)="confirm()">Confirm</span>
</button>
</div>
</div>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RenameTagModalComponent } from './rename-tag-modal.component';
describe('RenameTagModalComponent', () => {
let component: RenameTagModalComponent;
let fixture: ComponentFixture<RenameTagModalComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [RenameTagModalComponent],
}).compileComponents();
fixture = TestBed.createComponent(RenameTagModalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, Input } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
@Component({
selector: 'app-rename-tag-modal',
templateUrl: './rename-tag-modal.component.html',
styleUrls: ['./rename-tag-modal.component.scss'],
})
export class RenameTagModalComponent {
text = '';
@Input() oldTag = '';
constructor(public activeModal: NgbActiveModal) {}
confirm(): void {
this.activeModal.dismiss(this.text);
}
}
<div style="width: 200px" class="bg-white border border-1">
<div class="p-2 bg-chambers-cream-200">
<input type="text" placeholder="tag name" class="w-100 form-control form-control-sm rounded-3" (keyup.enter)="addTag($event)" />
</div>
<span style="font-size: 11px" class="px-2">Select a tag division or create one</span>
<div *ngFor="let tag of tags" class="p-1 d-flex align-items-center justify-content-between">
<div>
<button type="button" class="btn btn-sm btn-chambers-dark-blue-grey py-0 px-1">
<span class="px-1" style="font-size: 10px; font-weight: 500" (click)="selectTag(tag)">{{ tag.text | uppercase }} </span>
</button>
</div>
<div>
<cmb-mdi-icon class="cursor-pointer" size="13" [path]="mdiIcons.mdiDotsVertical" (click)="displayContextMenu($event, tag)"></cmb-mdi-icon>
</div>
</div>
</div>
<app-remove-tag-popover
*ngIf="isDisplayContextMenuChild$ | async"
[ngStyle]="menuPositionChild$ | async"
(removeTagClick)="removeTag($event)"
(renameTagClick)="renameTag($event)"></app-remove-tag-popover>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AgAddTagComponent } from './ag-add-tag.component';
describe('AgAddTagComponent', () => {
let component: AgAddTagComponent;
let fixture: ComponentFixture<AgAddTagComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AgAddTagComponent],
}).compileComponents();
fixture = TestBed.createComponent(AgAddTagComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import {
Component,
EventEmitter,
HostListener,
Input,
OnInit,
Output,
} from '@angular/core';
import { Observable, tap } from 'rxjs';
import {
AgTag,
AgTagRemoveEvent,
AgTagRenameEvent,
} from '../../models/ag-cell-link.model';
import { mdiIcons } from '../../models/mdi-icons';
import { ContextMenuService } from '../../services/context-menu.service';
@Component({
selector: 'app-ag-add-tag',
templateUrl: './ag-add-tag.component.html',
styleUrls: ['./ag-add-tag.component.scss'],
})
export class AgAddTagComponent implements OnInit {
mdiIcons = mdiIcons;
isDisplayContextMenuChild$!: Observable<boolean>;
rightClickMenuItemsChild$!: Observable<any>;
menuPositionChild$!: Observable<any>;
tags: AgTag[] = [];
@Input() contextMenuItems: any[] = [];
@Output() contextMenuItemClick: EventEmitter<AgTag> =
new EventEmitter<AgTag>();
@Output() removeTagClick: EventEmitter<AgTagRemoveEvent> =
new EventEmitter<AgTagRemoveEvent>();
@Output() renameTagClick: EventEmitter<AgTagRenameEvent> =
new EventEmitter<AgTagRenameEvent>();
constructor(private contextMenuService: ContextMenuService) {}
ngOnInit(): void {
this.isDisplayContextMenuChild$ =
this.contextMenuService.isDisplayContextMenuChild$.pipe(
tap((display) => {
console.log('display', display);
}),
);
this.rightClickMenuItemsChild$ = this.contextMenuService.tag$.pipe(
tap((items) => {
console.log('tag', items);
}),
);
this.menuPositionChild$ = this.contextMenuService.menuPositionChild$.pipe(
tap((menu) => {
console.log('menu', menu);
}),
);
this.tags = this.contextMenuService.getTags();
}
selectTag(tag: AgTag): void {
this.contextMenuItemClick.emit(tag);
}
addTag(event: any): void {
const newTag = {
text: event.target.value,
} as AgTag;
this.contextMenuItemClick.emit(newTag);
this.contextMenuService.addTag(newTag);
this.tags = this.contextMenuService.getTags();
}
@HostListener('click')
clicked() {
this.contextMenuService.setClickInside(true);
}
@HostListener('document:click')
clickedOut() {
this.contextMenuService.setClickInside(false);
}
displayContextMenu(event: any, tag: AgTag): void {
this.contextMenuService.displayContextMenuChild(event, tag);
}
removeTag(tagRemoved: AgTagRemoveEvent): void {
this.removeTagClick.emit(tagRemoved);
this.contextMenuService.removeTag(tagRemoved.tag);
this.tags = this.contextMenuService.getTags();
}
renameTag(tagRenamed: AgTagRenameEvent): void {
this.renameTagClick.emit(tagRenamed);
this.contextMenuService.renameTag(tagRenamed.oldTag, tagRenamed.newTag);
this.tags = this.contextMenuService.getTags();
}
}
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { AgTag, TagClickedModel } from '../models/ag-cell-link.model';
@Injectable()
export class ContextMenuService {
// custom menu
title = 'context-menu';
isDisplayContextMenu$ = new BehaviorSubject<boolean>(false);
rightClickMenuItems$ = new BehaviorSubject<any[]>([]);
menuPosition$ = new BehaviorSubject<any>({
position: 'fixed',
left: `${0}px`,
top: `${0}px`,
});
isDisplayContextMenuChild$ = new BehaviorSubject<boolean>(false);
tag$ = new BehaviorSubject<AgTag>({ text: '' });
menuPositionChild$ = new BehaviorSubject<any>({
position: 'fixed',
left: `${0}px`,
top: `${0}px`,
});
clickInside = false;
cellClicked!: TagClickedModel;
tags: AgTag[] = [];
constructor() {
this.tags = [
{
text: 'Finance',
},
{
text: 'Social',
},
{
text: 'Youth',
},
];
}
removeTag(tagRemoved: string): void {
this.tags = this.tags.filter((tag) => tag.text !== tagRemoved);
}
renameTag(oldTag: string, newTag: string): void {
console.log('oldTag', oldTag);
console.log('newTag', newTag);
const tagsOld = this.tags.filter((tag) => tag.text === oldTag);
tagsOld.forEach((tagOld) => {
tagOld.text = newTag;
});
}
addTag(tagAdded: AgTag): void {
this.tags = this.tags.concat(tagAdded);
}
getTags(): AgTag[] {
return this.tags;
}
setClickInside(click: boolean): void {
this.clickInside = click;
}
setCellClicked(data: any, colId: string): void {
this.cellClicked = {
colId,
data,
};
}
displayContextMenu(event: any) {
//console.log('clickButton displayContextMenu', this.params.data);
this.isDisplayContextMenu$.next(true);
this.rightClickMenuItems$.next([
{
menuText: 'Refactor',
menuEvent: 'Handle refactor',
},
{
menuText: 'Format',
menuEvent: 'Handle format',
},
]);
this.menuPosition$.next({
position: 'fixed',
left: `${event.clientX}px`,
top: `${event.clientY}px`,
});
}
hideContextMenu(): void {
this.isDisplayContextMenu$.next(false);
}
displayContextMenuChild(event: any, tag: AgTag) {
//console.log('clickButton displayContextMenu', this.params.data);
this.isDisplayContextMenuChild$.next(true);
this.tag$.next(tag);
this.menuPositionChild$.next({
position: 'fixed',
left: `${event.clientX}px`,
top: `${event.clientY}px`,
});
}
hideContextMenuChild(): void {
this.isDisplayContextMenuChild$.next(false);
}
}
// Angular entry point file
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from 'app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
/**
* WEB ANGULAR VERSION
* (based on systemjs.config.js from the angular tutorial - https://angular.io/tutorial)
* System configuration for Angular samples
* Adjust as necessary for your application needs.
*/
var templateUrlRegex = /templateUrl\s*:(\s*['"`](.*?)['"`]\s*)/gm;
var stylesRegex = /styleUrls *:(\s*\[[^\]]*?\])/g;
var stringRegex = /(['`"])((?:[^\\]\\\1|.)*?)\1/g;
module.exports.translate = function(load) {
if (load.source.indexOf('moduleId') != -1) return load;
var url = document.createElement('a');
url.href = load.address;
var basePathParts = url.pathname.split('/');
basePathParts.pop();
var basePath = basePathParts.join('/');
var baseHref = document.createElement('a');
baseHref.href = this.baseURL;
baseHref = baseHref.pathname;
if (!baseHref.startsWith('/base/')) { // it is not karma
basePath = basePath.replace(baseHref, '');
}
load.source = load.source
.replace(templateUrlRegex, function(match, quote, url) {
var resolvedUrl = url;
if (url.startsWith('.')) {
resolvedUrl = basePath + url.substr(1);
}
return 'templateUrl: "' + resolvedUrl + '"';
})
.replace(stylesRegex, function(match, relativeUrls) {
var urls = [];
while ((match = stringRegex.exec(relativeUrls)) !== null) {
if (match[2].startsWith('.')) {
urls.push('"' + basePath + match[2].substr(1) + '"');
} else {
urls.push('"' + match[2] + '"');
}
}
return "styleUrls: [" + urls.join(', ') + "]";
});
return load;
};
/**
* WEB ANGULAR VERSION
* (based on systemjs.config.js from the angular tutorial - https://angular.io/tutorial)
* System configuration for Angular samples
* Adjust as necessary for your application needs.
*/
(function (global) {
var ANGULAR_VERSION = "14.2.6";
var ANGULAR_CDK_VERSION = "14.2.6";
var ANGULAR_MATERIAL_VERSION = "14.2.6";
function assign() {
var result = {};
for (var i = 0, len = arguments.length; i < len; i++) {
var arg = arguments[i];
for (var prop in arg) {
result[prop] = arg[prop];
}
}
return result;
}
System.config({
// DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER
transpiler: "ts",
typescriptOptions: {
// Copy of compiler options in standard tsconfig.json
target: "es5",
module: "system", //gets rid of console warning
moduleResolution: "node",
sourceMap: true,
emitDecoratorMetadata: true,
experimentalDecorators: true,
lib: ["es2015", "dom"],
noImplicitAny: true,
suppressImplicitAnyIndexErrors: true
},
meta: {
typescript: {
exports: "ts"
},
'*.css': { loader: 'css' }
},
paths:
assign(
{
// paths serve as alias
"npm:": "https://cdn.jsdelivr.net/npm/",
}, systemJsPaths)
,
// RxJS makes a lot of requests to jsdelivr. This guy addressed it:
// https://github.com/OasisDigital/rxjs-system-bundle.
bundles: {
"npm:rxjs-system-bundle@6.3.3/Rx.system.min.js": [
"rxjs",
"rxjs/*",
"rxjs/operator/*",
"rxjs/operators/*",
"rxjs/observable/*",
"rxjs/scheduler/*",
"rxjs/symbol/*",
"rxjs/add/operator/*",
"rxjs/add/observable/*",
"rxjs/util/*"
]
},
// map tells the System loader where to look for things
map: assign(
{
// Angular bundles in System.register format via esm-bundle
// Cell renderers only work with the esm-bundle version
// TemplateUrls only works with platform-browser-dynamic from esm-bundle
'@angular/compiler': 'npm:@esm-bundle/angular__compiler@' + ANGULAR_VERSION + '/system/es2015/ivy/angular-compiler.min.js',
'@angular/platform-browser-dynamic': 'npm:@esm-bundle/angular__platform-browser-dynamic@' + ANGULAR_VERSION + '/system/es2015/ivy/angular-platform-browser-dynamic.min.js',
'@angular/core': 'npm:@angular/core@' + ANGULAR_VERSION + '/fesm2015/core.mjs',
'@angular/common': 'npm:@angular/common@' + ANGULAR_VERSION + '/fesm2015/common.mjs',
'@angular/common/http': 'npm:@angular/common@' + ANGULAR_VERSION + '/fesm2015/http.mjs',
'@angular/platform-browser': 'npm:@angular/platform-browser@' + ANGULAR_VERSION + '/fesm2015/platform-browser.mjs',
'@angular/platform-browser/animations': 'npm:@angular/platform-browser@' + ANGULAR_VERSION + '/fesm2015/animations.mjs',
'@angular/forms': 'npm:@angular/forms@' + ANGULAR_VERSION + '/fesm2015/forms.mjs',
'@angular/router': 'npm:@angular/router@' + ANGULAR_VERSION + '/fesm2015/router.mjs',
'@angular/animations': 'npm:@angular/animations@' + ANGULAR_VERSION + '/fesm2015/animations.mjs',
'@angular/animations/browser': 'npm:@angular/animations@' + ANGULAR_VERSION + '/fesm2015/browser.mjs',
// material design
"@angular/material/core": "npm:@angular/material@" + ANGULAR_MATERIAL_VERSION + "/fesm2015/core.mjs",
"@angular/material/card": "npm:@angular/material@" + ANGULAR_MATERIAL_VERSION + "/fesm2015/card.mjs",
"@angular/material/radio": "npm:@angular/material@" + ANGULAR_MATERIAL_VERSION + "/fesm2015/radio.mjs",
"@angular/material/card": "npm:@angular/material@" + ANGULAR_MATERIAL_VERSION + "/fesm2015/card.mjs",
"@angular/material/slider": "npm:@angular/material@" + ANGULAR_MATERIAL_VERSION + "/fesm2015/slider.mjs",
"@angular/material/select": "npm:@angular/material@" + ANGULAR_MATERIAL_VERSION + "/fesm2015/select.mjs",
"@angular/material/progress-spinner": "npm:@angular/material@" + ANGULAR_MATERIAL_VERSION + "/fesm2015/progress-spinner.mjs",
"@angular/material/input": "npm:@angular/material@" + ANGULAR_MATERIAL_VERSION + "/fesm2015/input.mjs",
"@angular/material/icon": "npm:@angular/material@" + ANGULAR_MATERIAL_VERSION + "/fesm2015/icon.mjs",
"@angular/material/form-field": "npm:@angular/material@" + ANGULAR_MATERIAL_VERSION + "/fesm2015/form-field.mjs",
"@angular/material/checkbox": "npm:@angular/material@" + ANGULAR_MATERIAL_VERSION + "/fesm2015/checkbox.mjs",
"@angular/material/button-toggle": "npm:@angular/material@" + ANGULAR_MATERIAL_VERSION + "/fesm2015/button-toggle.mjs",
"@angular/cdk": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/fesm2015/cdk.mjs",
"@angular/cdk/platform": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/fesm2015/platform.mjs",
"@angular/cdk/layout": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/fesm2015/layout.mjs",
"@angular/cdk/bidi": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/fesm2015/bidi.mjs",
"@angular/cdk/coercion": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/fesm2015/coercion.mjs",
"@angular/cdk/keycodes": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/fesm2015/keycodes.mjs",
"@angular/cdk/a11y": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/fesm2015/a11y.mjs",
"@angular/cdk/overlay": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/fesm2015/overlay.mjs",
"@angular/cdk/portal": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/fesm2015/portal.mjs",
"@angular/cdk/observers": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/fesm2015/observers.mjs",
"@angular/cdk/collections": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/fesm2015/collections.mjs",
"@angular/cdk/scrolling": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/fesm2015/scrolling.mjs",
"@angular/cdk/text-field": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/fesm2015/text-field.mjs",
css: boilerplatePath + "css.js",
// css: 'npm:systemjs-plugin-css@0.1.37/css.js',
ts: "npm:plugin-typescript@8.0.0/lib/plugin.js",
tslib: "npm:tslib@2.3.1/tslib.js",
typescript: "npm:typescript@4.0.8/lib/typescript.min.js",
// our app is within the app folder, appLocation comes from index.html
app: appLocation + "app",
},
systemJsMap
),
// packages tells the System loader how to load when no filename and/or no extension
packages: {
app: {
main: "./main.ts",
defaultExtension: "ts",
meta: {
"./*.ts": {
loader: boilerplatePath + "systemjs-angular-loader.js"
}
}
},
'ag-grid-angular': {
main: './fesm2015/ag-grid-angular.js',
defaultExtension: 'js'
},
'ag-grid-community': {
main: './dist/ag-grid-community.cjs.min.js',
defaultExtension: 'js'
},
'ag-grid-enterprise': {
main: './dist/ag-grid-enterprise.cjs.min.js',
defaultExtension: 'js'
},
"@ag-grid-community/angular": {
main: "./fesm2015/ag-grid-community-angular.js",
defaultExtension: "js"
},
rxjs: {
defaultExtension: false
}
}
});
})(this);
if (typeof window !== 'undefined') {
var waitSeconds = 100;
var head = document.getElementsByTagName('head')[0];
var isWebkit = !!window.navigator.userAgent.match(/AppleWebKit\/([^ ;]*)/);
var webkitLoadCheck = function (link, callback) {
setTimeout(function () {
for (var i = 0; i < document.styleSheets.length; i++) {
var sheet = document.styleSheets[i];
if (sheet.href == link.href)
return callback();
}
webkitLoadCheck(link, callback);
}, 10);
};
var cssIsReloadable = function cssIsReloadable(links) {
// Css loaded on the page initially should be skipped by the first
// systemjs load, and marked for reload
var reloadable = true;
forEach(links, function (link) {
if (!link.hasAttribute('data-systemjs-css')) {
reloadable = false;
link.setAttribute('data-systemjs-css', '');
}
});
return reloadable;
}
var findExistingCSS = function findExistingCSS(url) {
// Search for existing link to reload
var links = head.getElementsByTagName('link')
return filter(links, function (link) {
return link.href === url;
});
}
var noop = function () {
};
var loadCSS = function (url, existingLinks) {
const stylesUrl = url.includes("styles.css") || url.includes("style.css");
return new Promise((outerResolve, outerReject) => {
setTimeout(() => {
new Promise(function (resolve, reject) {
var timeout = setTimeout(function () {
reject('Unable to load CSS');
}, waitSeconds * 1000);
var _callback = function (error) {
clearTimeout(timeout);
link.onload = link.onerror = noop;
setTimeout(function () {
if (error) {
reject(error);
outerReject(error)
} else {
resolve('');
outerResolve('');
}
}, 7);
};
var link = document.createElement('link');
link.type = 'text/css';
link.rel = 'stylesheet';
link.href = url;
link.setAttribute('data-systemjs-css', '');
if (!isWebkit) {
link.onload = function () {
_callback();
}
} else {
webkitLoadCheck(link, _callback);
}
link.onerror = function (event) {
_callback(event.error || new Error('Error loading CSS file.'));
};
if (existingLinks.length)
head.insertBefore(link, existingLinks[0]);
else
head.appendChild(link);
})
// Remove the old link regardless of loading outcome
.then(function (result) {
forEach(existingLinks, function (link) {
link.parentElement.removeChild(link);
})
return result;
}, function (err) {
forEach(existingLinks, function (link) {
link.parentElement.removeChild(link);
})
throw err;
})
}, stylesUrl ? 5 : 0)
})
};
exports.fetch = function (load) {
// dont reload styles loaded in the head
var links = findExistingCSS(load.address);
if (!cssIsReloadable(links))
return '';
return loadCSS(load.address, links);
};
} else {
var builderPromise;
function getBuilder(loader) {
if (builderPromise)
return builderPromise;
return builderPromise = System['import']('./css-plugin-base.js', module.id)
.then(function (CSSPluginBase) {
return new CSSPluginBase(function compile(source, address) {
return {
css: source,
map: null,
moduleSource: null,
moduleFormat: null
};
});
});
}
exports.cssPlugin = true;
exports.fetch = function (load, fetch) {
if (!this.builder)
return '';
return fetch(load);
};
exports.translate = function (load, opts) {
if (!this.builder)
return '';
var loader = this;
return getBuilder(loader).then(function (builder) {
return builder.translate.call(loader, load, opts);
});
};
exports.instantiate = function (load, opts) {
if (!this.builder)
return;
var loader = this;
return getBuilder(loader).then(function (builder) {
return builder.instantiate.call(loader, load, opts);
});
};
exports.bundle = function (loads, compileOpts, outputOpts) {
var loader = this;
return getBuilder(loader).then(function (builder) {
return builder.bundle.call(loader, loads, compileOpts, outputOpts);
});
};
exports.listAssets = function (loads, opts) {
var loader = this;
return getBuilder(loader).then(function (builder) {
return builder.listAssets.call(loader, loads, opts);
});
};
}
// Because IE8?
function filter(arrayLike, func) {
var arr = []
forEach(arrayLike, function (item) {
if (func(item))
arr.push(item);
});
return arr;
}
// Because IE8?
function forEach(arrayLike, func) {
for (var i = 0; i < arrayLike.length; i++) {
func(arrayLike[i])
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<title>Angular example</title>
<meta charSet="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<style media="only screen">
html, body, #app {
height: 100%;
width: 100%;
margin: 0;
box-sizing: border-box;
-webkit-overflow-scrolling: touch;
}
html {
position: absolute;
top: 0;
left: 0;
padding: 0;
overflow: auto;
}
body {
padding: 1rem;
overflow: auto;
}
.underline {
text-decoration: underline;
}
</style>
</head>
<body>
<div id="app">
<my-app>Loading Angular example…</my-app>
</div>
<script>document.write('<base href="' + document.location + '" />');</script>
<script src="https://cdn.jsdelivr.net/npm/core-js-bundle@3.6.5/minified.js">
</script>
<script src="https://cdn.jsdelivr.net/npm/zone.js@0.11.2/dist/zone.min.js">
</script>
<script>
var appLocation = './';
var boilerplatePath = '';
var systemJsMap = {
"@ag-grid-community/angular": "https://cdn.jsdelivr.net/npm/@ag-grid-community/angular@29.0.0/",
"@ag-grid-community/styles": "https://cdn.jsdelivr.net/npm/@ag-grid-community/styles@29.0.0",
"ag-grid-angular": "https://cdn.jsdelivr.net/npm/ag-grid-angular@29.0.0/",
"ag-grid-community": "https://cdn.jsdelivr.net/npm/ag-grid-community@29.0.0",
"ag-grid-enterprise": "https://cdn.jsdelivr.net/npm/ag-grid-enterprise@29.0.0/"
};
var systemJsPaths = {
"@ag-grid-community/client-side-row-model": "https://cdn.jsdelivr.net/npm/@ag-grid-community/client-side-row-model@29.0.0/dist/client-side-row-model.cjs.min.js",
"@ag-grid-community/core": "https://cdn.jsdelivr.net/npm/@ag-grid-community/core@29.0.0/dist/core.cjs.min.js",
"@ag-grid-community/csv-export": "https://cdn.jsdelivr.net/npm/@ag-grid-community/csv-export@29.0.0/dist/csv-export.cjs.min.js",
"@ag-grid-community/infinite-row-model": "https://cdn.jsdelivr.net/npm/@ag-grid-community/infinite-row-model@29.0.0/dist/infinite-row-model.cjs.min.js",
"@ag-grid-enterprise/charts": "https://cdn.jsdelivr.net/npm/@ag-grid-enterprise/charts@29.0.0/dist/charts.cjs.min.js",
"@ag-grid-enterprise/clipboard": "https://cdn.jsdelivr.net/npm/@ag-grid-enterprise/clipboard@29.0.0/dist/clipboard.cjs.min.js",
"@ag-grid-enterprise/column-tool-panel": "https://cdn.jsdelivr.net/npm/@ag-grid-enterprise/column-tool-panel@29.0.0/dist/column-tool-panel.cjs.min.js",
"@ag-grid-enterprise/core": "https://cdn.jsdelivr.net/npm/@ag-grid-enterprise/core@29.0.0/dist/core.cjs.min.js",
"@ag-grid-enterprise/excel-export": "https://cdn.jsdelivr.net/npm/@ag-grid-enterprise/excel-export@29.0.0/dist/excel-export.cjs.min.js",
"@ag-grid-enterprise/filter-tool-panel": "https://cdn.jsdelivr.net/npm/@ag-grid-enterprise/filter-tool-panel@29.0.0/dist/filter-tool-panel.cjs.min.js",
"@ag-grid-enterprise/master-detail": "https://cdn.jsdelivr.net/npm/@ag-grid-enterprise/master-detail@29.0.0/dist/master-detail.cjs.min.js",
"@ag-grid-enterprise/menu": "https://cdn.jsdelivr.net/npm/@ag-grid-enterprise/menu@29.0.0/dist/menu.cjs.min.js",
"@ag-grid-enterprise/multi-filter": "https://cdn.jsdelivr.net/npm/@ag-grid-enterprise/multi-filter@29.0.0/dist/multi-filter.cjs.min.js",
"@ag-grid-enterprise/range-selection": "https://cdn.jsdelivr.net/npm/@ag-grid-enterprise/range-selection@29.0.0/dist/range-selection.cjs.min.js",
"@ag-grid-enterprise/rich-select": "https://cdn.jsdelivr.net/npm/@ag-grid-enterprise/rich-select@29.0.0/dist/rich-select.cjs.min.js",
"@ag-grid-enterprise/row-grouping": "https://cdn.jsdelivr.net/npm/@ag-grid-enterprise/row-grouping@29.0.0/dist/row-grouping.cjs.min.js",
"@ag-grid-enterprise/server-side-row-model": "https://cdn.jsdelivr.net/npm/@ag-grid-enterprise/server-side-row-model@29.0.0/dist/server-side-row-model.cjs.min.js",
"@ag-grid-enterprise/set-filter": "https://cdn.jsdelivr.net/npm/@ag-grid-enterprise/set-filter@29.0.0/dist/set-filter.cjs.min.js",
"@ag-grid-enterprise/side-bar": "https://cdn.jsdelivr.net/npm/@ag-grid-enterprise/side-bar@29.0.0/dist/side-bar.cjs.min.js",
"@ag-grid-enterprise/sparklines": "https://cdn.jsdelivr.net/npm/@ag-grid-enterprise/sparklines@29.0.0/dist/sparklines.cjs.min.js",
"@ag-grid-enterprise/status-bar": "https://cdn.jsdelivr.net/npm/@ag-grid-enterprise/status-bar@29.0.0/dist/status-bar.cjs.min.js",
"@ag-grid-enterprise/viewport-row-model": "https://cdn.jsdelivr.net/npm/@ag-grid-enterprise/viewport-row-model@29.0.0/dist/viewport-row-model.cjs.min.js"
};
</script>
<script src="https://cdn.jsdelivr.net/npm/systemjs@0.21.6/dist/system.js">
</script>
<script src="systemjs.config.js">
</script>
<script>System.import('main.ts').catch(function(err) { console.error(err); });</script>
</body>
</html>