--- /dev/null
+import {Component, Input, Output, EventEmitter, OnInit, Host} from '@angular/core';
+import {GroupedMenuComponent, GroupedMenuEntry} from './grouped-menu.component';
+
+@Component({
+ selector: 'eg-grouped-menu-entry',
+ template: '<ng-container></ng-container>'
+})
+
+export class GroupedMenuEntryComponent implements OnInit {
+
+ @Input() label: string;
+ @Input() group: string;
+ @Input() disabled: boolean;
+ @Input() isSeparator: boolean;
+ @Input() newTab: boolean;
+ @Input() routerLink: string;
+ @Input() href: string;
+ @Output() entryClicked: EventEmitter<GroupedMenuEntry> =
+ new EventEmitter<GroupedMenuEntry>();
+
+ menu: GroupedMenuComponent;
+
+ constructor(@Host() menu: GroupedMenuComponent) {
+ this.menu = menu;
+ }
+
+ ngOnInit() {
+ const entry = new GroupedMenuEntry();
+ entry.label = this.label;
+ entry.group = this.group;
+ entry.disabled = this.disabled;
+ entry.newTab = this.newTab;
+ entry.routerLink = this.routerLink;
+ entry.href = this.href;
+ entry.isSeparator = this.isSeparator;
+ entry.entryClicked = this.entryClicked;
+ this.menu.menuEntries.push(entry);
+ }
+}
+
+
--- /dev/null
+
+<div ngbDropdown>
+ <button class="btn btn-outline-dark" id="{{domId}}"
+ ngbDropdownToggle i18n>{{label}}</button>
+ <div ngbDropdownMenu>
+ <button class="dropdown-item" *ngFor="let entry of menuEntries"
+ [disabled]="entry.disabled"
+ (click)="performAction(entry)" tabindex="0">
+ <ng-container *ngIf="entry.isGroup">
+ <span class="font-weight-bold font-italic">{{entry.label}}</span>
+ </ng-container>
+ <ng-container *ngIf="entry.isSeparator">
+ <div class="dropdown-divider"></div>
+ </ng-container>
+ <ng-container *ngIf="!entry.isGroup && !entry.isSeparator">
+ <!-- grouped entries are left paddded for group indentation -->
+ <span [ngClass]="{'ml-2': entry.group}">{{entry.label}}</span>
+ </ng-container>
+ </button>
+ </div>
+</div>
--- /dev/null
+import {Component, Input, Output, EventEmitter, OnInit, AfterViewInit} from '@angular/core';
+import {Router} from '@angular/router';
+import {Location} from '@angular/common';
+
+export class GroupedMenuEntry {
+ label: string;
+ group: string;
+ disabled: boolean;
+ isSeparator: boolean;
+ isGroup: boolean;
+ routerLink: string;
+ newTab: boolean; // routerLink or href open a new tab
+ href: string;
+ entryClicked: EventEmitter<GroupedMenuEntry>;
+}
+
+@Component({
+ selector: 'eg-grouped-menu',
+ templateUrl: './grouped-menu.component.html'
+})
+
+export class GroupedMenuComponent implements OnInit, AfterViewInit {
+
+ static autoId = 0;
+
+ // Label for dropdown button
+ @Input() label: string;
+
+ @Input() domId = 'grouped-menu-' + GroupedMenuComponent.autoId++;
+
+ menuEntries: GroupedMenuEntry[] = [];
+
+ constructor(
+ private router: Router,
+ private ngLocation: Location
+ ) {}
+
+ ngOnInit() {
+ }
+
+ ngAfterViewInit() {
+ setTimeout(() => this.sortActions());
+ }
+
+ sortActions() {
+ const actions = this.menuEntries;
+
+ const unGrouped = actions.filter(a => !a.group)
+ .sort((a, b) => {
+ return a.label < b.label ? -1 : 1;
+ });
+
+ const grouped = actions.filter(a => Boolean(a.group))
+ .sort((a, b) => {
+ if (a.group === b.group) {
+ return a.label < b.label ? -1 : 1;
+ } else {
+ return a.group < b.group ? -1 : 1;
+ }
+ });
+
+ // Insert group markers for rendering
+ const seen: any = {};
+ const grouped2: any[] = [];
+ grouped.forEach(action => {
+ if (!seen[action.group]) {
+ seen[action.group] = true;
+ const act = new GroupedMenuEntry();
+ act.label = action.group;
+ act.isGroup = true;
+ grouped2.push(act);
+ }
+ grouped2.push(action);
+ });
+
+ this.menuEntries = unGrouped.concat(grouped2);
+ }
+
+ performAction(entry: GroupedMenuEntry) {
+ if (entry.isGroup || entry.isSeparator) { return; }
+
+ // Always emit, even if no one is listening.
+ entry.entryClicked.emit(entry);
+
+ let url;
+ if (entry.href) {
+ url = entry.href;
+ } else if (entry.routerLink) {
+ if (entry.newTab) {
+ url = this.ngLocation.prepareExternalUrl(entry.routerLink);
+ } else {
+ this.router.navigate([entry.routerLink]);
+ return;
+ }
+ }
+
+ if (url) {
+ if (entry.newTab) {
+ window.open(url);
+ } else {
+ location.href = url;
+ }
+ return;
+ }
+ }
+}
+
--- /dev/null
+import {NgModule} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
+import {GroupedMenuComponent} from './grouped-menu.component';
+import {GroupedMenuEntryComponent} from './grouped-menu-entry.component';
+
+@NgModule({
+ declarations: [
+ GroupedMenuComponent,
+ GroupedMenuEntryComponent
+ ],
+ imports: [
+ CommonModule,
+ NgbModule
+ ],
+ exports: [
+ GroupedMenuComponent,
+ GroupedMenuEntryComponent
+ ]
+})
+
+export class GroupedMenuModule { }
+