diff --git a/src/app/components/context-menu/context-menu-outside-event.directive.ts b/src/app/components/context-menu/context-menu-outside-event.directive.ts index ff352239b..c840eb32e 100644 --- a/src/app/components/context-menu/context-menu-outside-event.directive.ts +++ b/src/app/components/context-menu/context-menu-outside-event.directive.ts @@ -6,7 +6,7 @@ import { OnDestroy } from '@angular/core'; import { fromEvent, Subscription } from 'rxjs'; -import { delay } from 'rxjs/operators'; +import { filter } from 'rxjs/operators'; @Directive({ selector: '[acaContextMenuOutsideEvent]' @@ -22,7 +22,7 @@ export class OutsideEventDirective implements OnInit, OnDestroy { ngOnInit() { this.subscriptions = this.subscriptions.concat([ fromEvent(document.body, 'click') - .pipe(delay(1)) + .pipe(filter(event => !this.findAncestor(event.target as Element))) .subscribe(() => this.clickOutside.next()) ]); } @@ -31,4 +31,15 @@ export class OutsideEventDirective implements OnInit, OnDestroy { this.subscriptions.forEach(subscription => subscription.unsubscribe()); this.subscriptions = []; } + + private findAncestor(el: Element): boolean { + const className = 'aca-context-menu'; + + if (el.classList.contains(className)) { + return true; + } + // tslint:disable-next-line:curly + while ((el = el.parentElement) && !el.classList.contains(className)); + return !!el; + } } diff --git a/src/app/components/context-menu/context-menu.directive.ts b/src/app/components/context-menu/context-menu.directive.ts index 2acc26031..5fb5af91c 100644 --- a/src/app/components/context-menu/context-menu.directive.ts +++ b/src/app/components/context-menu/context-menu.directive.ts @@ -23,20 +23,24 @@ * along with Alfresco. If not, see . */ -import { Directive, HostListener, Input } from '@angular/core'; +import { + Directive, + HostListener, + Input, + OnInit, + OnDestroy +} from '@angular/core'; import { ContextMenuOverlayRef } from './context-menu-overlay'; import { ContextMenuService } from './context-menu.service'; -import { DocumentListComponent } from '@alfresco/adf-content-services'; -import { SetSelectedNodesAction } from '../../store/actions'; -import { Store } from '@ngrx/store'; -import { AppStore } from '../../store/states/app.state'; -import { DataRow } from '@alfresco/adf-core'; -import { MinimalNodeEntity } from 'alfresco-js-api'; +import { debounceTime } from 'rxjs/operators'; +import { Subject, fromEvent, Subscription } from 'rxjs'; @Directive({ selector: '[acaContextActions]' }) -export class ContextActionsDirective { +export class ContextActionsDirective implements OnInit, OnDestroy { + private execute$: Subject = new Subject(); + private subscriptions: Subscription[] = []; private overlayRef: ContextMenuOverlayRef = null; // tslint:disable-next-line:no-input-rename @@ -49,40 +53,44 @@ export class ContextActionsDirective { event.preventDefault(); if (this.enabled) { - this.execute(event); + const target = this.getTarget(event); + if (target) { + this.execute(event, target); + } } } } - constructor( - private documentList: DocumentListComponent, - private store: Store, - private contextMenuService: ContextMenuService - ) {} + constructor(private contextMenuService: ContextMenuService) {} - private execute(event: MouseEvent) { - // todo: review this in ADF - const selected = this.getSelectedRow(event); + ngOnInit() { + this.subscriptions.push( + fromEvent(document.body, 'contextmenu').subscribe(() => { + if (this.overlayRef) { + this.overlayRef.close(); + } + }), - if (selected) { - if (!this.isInSelection(selected)) { - this.clearSelection(); + this.execute$.pipe(debounceTime(300)).subscribe((event: MouseEvent) => { + this.render(event); + }) + ); + } - this.documentList.dataTable.selectRow(selected, true); - this.documentList.selection.push((selected).node); + ngOnDestroy() { + this.subscriptions.forEach(subscription => subscription.unsubscribe()); + this.subscriptions = []; + this.execute$ = null; + } - this.updateSelection(); - } - - this.render(event); + execute(event: MouseEvent, target: Element) { + if (!this.isSelected(target)) { + target.dispatchEvent(new MouseEvent('click')); } + this.execute$.next(event); } private render(event: MouseEvent) { - if (this.overlayRef) { - this.overlayRef.close(); - } - this.overlayRef = this.contextMenuService.open({ source: event, hasBackdrop: false, @@ -91,46 +99,24 @@ export class ContextActionsDirective { }); } - private updateSelection() { - this.store.dispatch( - new SetSelectedNodesAction(this.documentList.selection) - ); + private getTarget(event: MouseEvent): Element { + return this.findAncestor(event.target, 'adf-datatable-table-cell'); } - private isInSelection(row: DataRow): MinimalNodeEntity { - return this.documentList.selection.find( - selected => row.getValue('name') === selected.entry.name - ); - } - - private getSelectedRow(event): DataRow { - const rowElement = this.findAncestor( - event.target, - 'adf-datatable-row' - ); - - if (!rowElement) { - return null; + private isSelected(target): boolean { + if (!target) { + return false; } - const rowName = rowElement - .querySelector('.adf-data-table-cell--text .adf-datatable-cell') - .textContent.trim(); - - return this.documentList.data - .getRows() - .find((row: DataRow) => row.getValue('name') === rowName); - } - - private clearSelection() { - this.documentList.data.getRows().map((row: DataRow) => { - return this.documentList.dataTable.selectRow(row, false); - }); - - this.documentList.selection = []; + return this.findAncestor(target, 'adf-datatable-row').classList.contains( + 'is-selected' + ); } private findAncestor(el: Element, className: string): Element { + if (el.classList.contains(className)) { + return el; + } // tslint:disable-next-line:curly while ((el = el.parentElement) && !el.classList.contains(className)); return el; diff --git a/src/app/components/context-menu/context-menu.directives.spec.ts b/src/app/components/context-menu/context-menu.directives.spec.ts index 88516a83f..13d57e0f1 100644 --- a/src/app/components/context-menu/context-menu.directives.spec.ts +++ b/src/app/components/context-menu/context-menu.directives.spec.ts @@ -24,39 +24,40 @@ */ import { ContextActionsDirective } from './context-menu.directive'; +import { fakeAsync, tick } from '@angular/core/testing'; describe('ContextActionsDirective', () => { let directive; const contextMenuServiceMock = { open: jasmine.createSpy('open') }; - const storeMock = {}; - const documentListMock = {}; beforeEach(() => { - directive = new ContextActionsDirective( - documentListMock, - storeMock, - contextMenuServiceMock - ); + directive = new ContextActionsDirective(contextMenuServiceMock); }); - it('should not render context menu when disable property is false', () => { + it('should not render context menu when `enabled` property is false', () => { directive.enabled = false; - spyOn(directive, 'getSelectedRow').and.returnValue({}); - directive.onContextMenuEvent(new MouseEvent('contextmenu')); expect(contextMenuServiceMock.open).not.toHaveBeenCalled(); }); - it('should render context menu when disable property is true', () => { - directive.enabled = true; - spyOn(directive, 'getSelectedRow').and.returnValue({}); - spyOn(directive, 'isInSelection').and.returnValue(true); + it('should call service to render context menu', fakeAsync(() => { + const el = document.createElement('div'); + el.className = + 'adf-data-table-cell adf-datatable-table-cell adf-datatable-row'; - directive.onContextMenuEvent(new MouseEvent('contextmenu')); + const fragment = document.createDocumentFragment(); + fragment.appendChild(el); + const target = fragment.querySelector('div'); + + directive.ngOnInit(); + + directive.onContextMenuEvent({ preventDefault: () => {}, target }); + + tick(500); expect(contextMenuServiceMock.open).toHaveBeenCalled(); - }); + })); });