import {OrgSelectComponent} from '@eg/share/org-select/org-select.component';
import {DateRangeSelectComponent} from '@eg/share/daterange-select/daterange-select.component';
import {DateTimeSelectComponent} from '@eg/share/datetime-select/datetime-select.component';
-import {ContextMenuDirective, ContextMenuComponent} from '@eg/share/context-menu/context-menu.component';
+import {ContextMenuModule} from '@eg/share/context-menu/context-menu.module';
@NgModule({
DateSelectComponent,
OrgSelectComponent,
DateRangeSelectComponent,
- DateTimeSelectComponent,
- ContextMenuDirective,
- ContextMenuComponent
+ DateTimeSelectComponent
],
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
NgbModule,
- EgCoreModule
+ EgCoreModule,
+ ContextMenuModule
],
exports: [
CommonModule,
OrgSelectComponent,
DateRangeSelectComponent,
DateTimeSelectComponent,
- ContextMenuComponent,
- ContextMenuDirective
- ],
+ ContextMenuModule
+ ]
})
export class CommonWidgetsModule { }
--- /dev/null
+
+.eg-context-menu {
+ /* These fonts were applied specifically for the MARC editor
+ * context menus. Might want to make these optional. */
+ font-family: 'Lucida Console', Monaco, monospace;
+
+ /* put a hard limit on the popover width */
+ max-width: 550px;
+}
+
+.eg-context-menu .popover-body {
+ max-height: 400px;
+
+ /* Text exceeding the max-height / max-width will results in scrolls.
+ * In most cases, this should not happen. */
+ overflow-y: auto;
+ overflow-x: auto;
+}
+
+.eg-context-menu .popover-body .menu-entry {
+ /* force the menu to expand horizontally to display the text */
+ white-space: nowrap;
+}
+
+.eg-context-menu .popover-body .menu-entry:hover {
+ background-color: #f8f9fa;
+}
--- /dev/null
+
+<ng-template #menuTemplate>
+ <div *ngFor="let entry of menuEntries; first as isFirst"
+ class="menu-entry {{entryClasses}}">
+ <a
+ [attr.id]="isFirst ? randId : ''"
+ (click)="entryClicked(entry)">{{entry.label}}</a>
+ </div>
+</ng-template>
--- /dev/null
+import {Component, Input, Output, EventEmitter, OnInit, ViewChild,
+ AfterViewInit, TemplateRef, ViewEncapsulation} from '@angular/core';
+import {ContextMenuService, ContextMenu, ContextMenuEntry} from './context-menu.service';
+
+@Component({
+ selector: 'eg-context-menu-container',
+ templateUrl: './context-menu-container.component.html',
+ styleUrls: ['context-menu-container.component.css'],
+ /* Our CSS affects the style of the popover, which may
+ * be beyond our reach for standard view encapsulation */
+ encapsulation: ViewEncapsulation.None
+})
+
+export class ContextMenuContainerComponent implements OnInit, AfterViewInit {
+
+ menuEntries: ContextMenuEntry[] = [];
+ @ViewChild('menuTemplate', {static: false}) menuTemplate: TemplateRef<any>;
+
+ constructor(private menuService: ContextMenuService) {}
+
+ ngOnInit() {
+
+ this.menuService.showMenuRequest.subscribe(
+ (menu: ContextMenu) => {
+
+ this.menuEntries = menu.entries
+ });
+ }
+
+ ngAfterViewInit() {
+ this.menuService.menuTemplate = this.menuTemplate;
+ }
+
+ entryClicked(entry: ContextMenuEntry) {
+ this.menuService.menuItemSelected.emit(entry);
+ }
+}
+
+++ /dev/null
-
-.eg-context-menu {
- /* These fonts were applied specifically for the MARC editor
- * context menus. Might want to make these optional. */
- font-family: 'Lucida Console', Monaco, monospace;
-
- /* put a hard limit on the popover width */
- max-width: 550px;
-}
-
-.eg-context-menu .popover-body {
- max-height: 400px;
-
- /* Text exceeding the max-height / max-width will results in scrolls.
- * In most cases, this should not happen. */
- overflow-y: auto;
- overflow-x: auto;
-}
-
-.eg-context-menu .popover-body .menu-entry {
- /* force the menu to expand horizontally to display the text */
- white-space: nowrap;
-}
-
-.eg-context-menu .popover-body .menu-entry:hover {
- background-color: #f8f9fa;
-}
+++ /dev/null
-
-<ng-template #menuTemplate>
- <div *ngFor="let entry of menuEntries; first as isFirst"
- class="menu-entry {{entryClasses}}">
- <a
- [attr.id]="isFirst ? randId : ''"
- (click)="entryClicked(entry)">{{entry.label}}</a>
- </div>
-</ng-template>
+++ /dev/null
-import {Component, Input, Output, AfterViewInit, EventEmitter, Directive, ViewChild,
- Renderer2, ViewEncapsulation, ComponentFactoryResolver, TemplateRef} from '@angular/core';
-import {NgbPopover} from '@ng-bootstrap/ng-bootstrap';
-
-/**
- * Context menu component.
- *
- * No state is maintained (i.e. there is no current value), events are
- * simply emitted as entries are chosen.
- *
- * <eg-context-menu #menu
- * [menuEntries]="ContextMenuEntry[]"
- * (entrySelected)="$event === ContextMenuEntry">
- *
- * <input [egContextMenu]="menu" ... />
- */
-
-export interface ContextMenuEntry {
- value: string;
- label: string;
-}
-
-@Component({
- selector: 'eg-context-menu',
- templateUrl: './context-menu.component.html',
- styleUrls: ['context-menu.component.css'],
- /* Our CSS affects the style of the popover, which may
- * be beyond our reach for standard view encapsulation */
- encapsulation: ViewEncapsulation.None
-})
-
-export class ContextMenuComponent implements AfterViewInit {
-
- @Input() menuEntries: ContextMenuEntry[] = [];
-
- // Additional CSS classes (space separated) to apply to the entry links
- @Input() entryClasses = '';
-
- @Output() entrySelected: EventEmitter<ContextMenuEntry>;
-
- @ViewChild('menuTemplate', {static: false})
- public menuTemplate: TemplateRef<any>;
-
- randId = Math.floor(Math.random() * 10000000);
-
- constructor(private renderer: Renderer2) {
- this.entrySelected = new EventEmitter<ContextMenuEntry>();
- }
-
- ngAfterViewInit() {
- }
-
- grabFocus() {
- // ARG TODO
- const link = this.renderer.selectRootElement(`[id='${this.randId}']`);
- setTimeout(() => {
- console.log('focusing ', link);
- link.focus();
- }, 400);
- }
-
- entryClicked(entry: ContextMenuEntry) {
- console.log('emitting ', entry);
- this.entrySelected.emit(entry);
- }
-}
-
-@Directive({
- selector: '[egContextMenu]',
- exportAs: 'egContextMenu'
-})
-export class ContextMenuDirective extends NgbPopover {
-
- menuComp: ContextMenuComponent;
-
- triggers = 'contextmenu'; // TODO TODO
- popoverClass = 'eg-context-menu';
-
- @Input() set egContextMenu(menuComp: ContextMenuComponent) {
- this.menuComp = menuComp;
- }
-
- // Only one active menu is allowed at a time.
- static activeMenu: ContextMenuDirective;
-
- open() {
-
- // The popover will automatically close in most cases, but
- // context menus preventDefault(), which prevents right-click on
- // other context menus from firing the close operation. Or at
- // least, that's my assumption. This fixes it.
- if (ContextMenuDirective.activeMenu) {
- ContextMenuDirective.activeMenu.close();
- }
-
- if (!this.menuComp.menuEntries ||
- this.menuComp.menuEntries.length === 0) {
- return;
- }
-
- this.ngbPopover = this.menuComp.menuTemplate;
- ContextMenuDirective.activeMenu = this;
-
- super.open();
-
- // ARG TODO
- setTimeout(() => this.menuComp.grabFocus());
- }
-}
-
-
--- /dev/null
+import {Input, Output, EventEmitter, Directive} from '@angular/core';
+import {NgbPopover} from '@ng-bootstrap/ng-bootstrap';
+import {ContextMenuService, ContextMenu, ContextMenuEntry} from './context-menu.service';
+
+
+/* Import all of this stuff so we can pass it to our parent
+ * class via its constructor */
+import {
+ Inject, Injector, Renderer2, ElementRef, TemplateRef, ViewContainerRef,
+ ComponentFactoryResolver, NgZone, ChangeDetectorRef, ApplicationRef
+} from '@angular/core';
+import {DOCUMENT} from '@angular/common';
+import {NgbPopoverConfig} from '@ng-bootstrap/ng-bootstrap';
+/* --- */
+
+@Directive({
+ selector: '[egContextMenu]',
+ exportAs: 'egContextMenu'
+})
+export class ContextMenuDirective extends NgbPopover {
+
+ triggers = 'contextmenu';
+ popoverClass = 'eg-context-menu';
+
+ menuEntries: ContextMenuEntry[] = [];
+ menu: ContextMenu;
+
+ @Input() set egContextMenu(menuEntries: ContextMenuEntry[]) {
+ this.menuEntries = menuEntries;
+ }
+
+ @Output() menuItemSelected: EventEmitter<ContextMenuEntry>;
+
+ // Only one active menu is allowed at a time.
+ static activeDirective: ContextMenuDirective;
+ static menuId = 0;
+
+ constructor(
+ p1: ElementRef<HTMLElement>, p2: Renderer2, p3: Injector,
+ p4: ComponentFactoryResolver, p5: ViewContainerRef, p6: NgbPopoverConfig,
+ p7: NgZone, @Inject(DOCUMENT) p8: any, p9: ChangeDetectorRef,
+ p10: ApplicationRef, private menuService: ContextMenuService) {
+
+ // relay injected services to parent
+ super(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10);
+
+ this.menuItemSelected = new EventEmitter<ContextMenuEntry>();
+
+ this.menuService.menuItemSelected.subscribe(
+ (entry: ContextMenuEntry) => {
+
+ // Only broadcast entry selection to my listeners if I'm
+ // hosting the menu where the selection occurred.
+
+ if (this.menu && this.menu.id === this.menuService.activeMenu.id) {
+ this.menuItemSelected.emit(entry);
+ }
+ });
+ }
+
+ open() {
+
+ // In certain scenarios (e.g. right-clicking on another context
+ // menu) an open popover will stay open. Force it closed here.
+ if (ContextMenuDirective.activeDirective) {
+ ContextMenuDirective.activeDirective.close();
+ ContextMenuDirective.activeDirective = null;
+ this.menuService.activeMenu == null;
+ }
+
+ if (!this.menuEntries ||
+ this.menuEntries.length === 0) {
+ return;
+ }
+
+ this.menu = new ContextMenu();
+ this.menu.id = ContextMenuDirective.menuId++;
+ this.menu.entries = this.menuEntries;
+
+ this.menuService.activeMenu = this.menu;
+ this.menuService.showMenuRequest.emit(this.menu);
+ this.ngbPopover = this.menuService.menuTemplate;
+
+ ContextMenuDirective.activeDirective = this;
+
+ super.open();
+ }
+}
+
+
--- /dev/null
+import {NgModule} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
+import {ContextMenuService} from './context-menu.service';
+import {ContextMenuDirective} from './context-menu.directive';
+import {ContextMenuContainerComponent} from './context-menu-container.component';
+
+@NgModule({
+ declarations: [
+ ContextMenuDirective,
+ ContextMenuContainerComponent
+ ],
+ imports: [
+ CommonModule,
+ NgbModule
+ ],
+ exports: [
+ ContextMenuDirective,
+ ContextMenuContainerComponent
+ ]
+})
+
+export class ContextMenuModule { }
+
--- /dev/null
+import {Injectable, EventEmitter, TemplateRef} from '@angular/core';
+import {tap} from 'rxjs/operators';
+
+/* Relay requests to/from the context menu directive and its
+ * template container component */
+
+export interface ContextMenuEntry {
+ value: string;
+ label: string;
+}
+
+export class ContextMenu {
+ id: number;
+ entries: ContextMenuEntry[];
+}
+
+@Injectable({providedIn: 'root'})
+export class ContextMenuService {
+
+ showMenuRequest: EventEmitter<ContextMenu>;
+ menuItemSelected: EventEmitter<ContextMenuEntry>;
+
+ menuTemplate: TemplateRef<any>;
+ activeMenu: ContextMenu;
+
+ constructor() {
+ this.showMenuRequest = new EventEmitter<ContextMenu>();
+ this.menuItemSelected = new EventEmitter<ContextMenuEntry>();
+ }
+}
+
+
-<eg-context-menu #contextMenu
- [menuEntries]="contextMenuEntries()"
- (entrySelected)="contextMenuChange($event.value)">
-</eg-context-menu>
-
<ng-container *ngIf="bigText">
<div contenteditable
id='{{randId}}'
spellcheck="false"
class="d-inline-block p-1 pt-2 text-dark text-break {{moreClasses}}"
[attr.tabindex]="fieldText ? -1 : ''"
- [egContextMenu]="contextMenu"
+ [egContextMenu]="contextMenuEntries()"
+ (menuItemSelected)="contextMenuChange($event.value)"
(keydown)="inputKeyDown($event)"
(focus)="focusBigText()"
(input)="bigTextValueChange()">
[maxlength]="maxLength || ''"
[disabled]="fieldText"
[attr.tabindex]="fieldText ? -1 : ''"
- [egContextMenu]="contextMenu"
+ [egContextMenu]="contextMenuEntries()"
+ (menuItemSelected)="contextMenuChange($event.value)"
(keydown)="inputKeyDown($event)"
(focus)="$event.target.select()"
[ngModel]="getContent()"
import {filter} from 'rxjs/operators';
import {MarcRecord, MarcField, MarcSubfield} from './marcrecord';
import {MarcEditContext, FieldFocusRequest} from './editor-context';
-import {ContextMenuEntry} from '@eg/share/context-menu/context-menu.component';
+import {ContextMenuEntry} from '@eg/share/context-menu/context-menu.service';
import {TagTableService} from './tagtable.service';
/**
<ng-container *ngIf="fieldMeta">
- <eg-context-menu #contextMenu
- [menuEntries]="fieldValues"
- (entrySelected)="valueChange($event.value)">
- </eg-context-menu>
-
<div class="form-inline d-flex">
<div class="flex-4">
<span id='label-{{randId}}' class="text-left font-weight-bold">
(change)="valueChange()"
[(ngModel)]="fieldValue"
[attr.maxlength]="fieldLength" [attr.size]="fieldLength"
- [egContextMenu]="contextMenu"
+ [egContextMenu]="fieldValues"
+ (menuItemSelected)="valueChange($event.value)"
/>
</div>
</ng-container>
import {MarcRecord} from './marcrecord';
import {MarcEditContext} from './editor-context';
import {TagTableService, ValueLabelPair} from './tagtable.service';
-//import {NgbPopover} from '@ng-bootstrap/ng-bootstrap';
-//import {ContextMenuDirective} from './context-menu.component';
/**
* MARC Fixed Field Editing Component
fieldMeta: any;
fieldLength: number = null;
fieldValues: ValueLabelPair[] = null;
- //popOver: NgbPopover;
randId = Math.floor(Math.random() * 10000000);
constructor(private tagTable: TagTableService) {}
<!-- global toast alerts -->
<eg-toast></eg-toast>
+
+<!-- context menu DOM insertion point -->
+<eg-context-menu-container></eg-context-menu-container>