mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[ADF-3456] DocumentList - context menu (#3694)
* refactor context menu * prune log * remove fdescribe * remove undescore from private * fix e2e action buttons * fix trailing space * remove fdescribe
This commit is contained in:
committed by
Eugenio Romano
parent
62f0804205
commit
4404b724fa
@@ -21,7 +21,7 @@ import TestConfig = require('../test.config');
|
|||||||
import path = require('path');
|
import path = require('path');
|
||||||
import fs = require('fs');
|
import fs = require('fs');
|
||||||
import remote = require('selenium-webdriver/remote');
|
import remote = require('selenium-webdriver/remote');
|
||||||
import { browser } from "protractor";
|
import { browser } from 'protractor';
|
||||||
|
|
||||||
export class UsersActions {
|
export class UsersActions {
|
||||||
|
|
||||||
|
@@ -319,11 +319,11 @@ var ContentList = function () {
|
|||||||
this.rightClickOnRowNamed = function(rowName) {
|
this.rightClickOnRowNamed = function(rowName) {
|
||||||
let row = this.getRowByRowName(rowName);
|
let row = this.getRowByRowName(rowName);
|
||||||
browser.actions().click(row, protractor.Button.RIGHT).perform();
|
browser.actions().click(row, protractor.Button.RIGHT).perform();
|
||||||
Util.waitUntilElementIsVisible(element(by.css('div.context-menu')));
|
Util.waitUntilElementIsVisible(element(by.id('adf-context-menu-content')));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.checkContextActionIsVisible = function(actionName) {
|
this.checkContextActionIsVisible = function(actionName) {
|
||||||
let actionButton = element(by.css(`div.context-menu button[data-automation-id="context-${actionName}"`));
|
let actionButton = element(by.css(`button[data-automation-id="context-${actionName}"`));
|
||||||
Util.waitUntilElementIsVisible(actionButton);
|
Util.waitUntilElementIsVisible(actionButton);
|
||||||
Util.waitUntilElementIsClickable(actionButton);
|
Util.waitUntilElementIsClickable(actionButton);
|
||||||
return actionButton;
|
return actionButton;
|
||||||
|
@@ -17,23 +17,23 @@
|
|||||||
|
|
||||||
import { browser } from 'protractor';
|
import { browser } from 'protractor';
|
||||||
|
|
||||||
import LoginPage = require('./pages/adf/loginPage');
|
import LoginPage = require('../pages/adf/loginPage');
|
||||||
import ProcessServicesPage = require('./pages/adf/process_services/processServicesPage');
|
import ProcessServicesPage = require('../pages/adf/process_services/processServicesPage');
|
||||||
import TasksPage = require('./pages/adf/process_services/tasksPage');
|
import TasksPage = require('../pages/adf/process_services/tasksPage');
|
||||||
|
|
||||||
import CONSTANTS = require('./util/constants');
|
import CONSTANTS = require('../util/constants');
|
||||||
|
|
||||||
import Tenant = require('./models/APS/Tenant');
|
import Tenant = require('../models/APS/Tenant');
|
||||||
import Task = require('./models/APS/Task');
|
import Task = require('../models/APS/Task');
|
||||||
|
|
||||||
import TestConfig = require('./test.config');
|
import TestConfig = require('../test.config');
|
||||||
import resources = require('./util/resources');
|
import resources = require('../util/resources');
|
||||||
|
|
||||||
import AlfrescoApi = require('alfresco-js-api-node');
|
import AlfrescoApi = require('.alfresco-js-api-node');
|
||||||
import { UsersActions } from './actions/users.actions';
|
import { UsersActions } from '../actions/users.actions';
|
||||||
import fs = require('fs');
|
import fs = require('fs');
|
||||||
import path = require('path');
|
import path = require('path');
|
||||||
import Util = require('./util/util');
|
import Util = require('..ro/util/util');
|
||||||
|
|
||||||
describe('Start Task - Task App', () => {
|
describe('Start Task - Task App', () => {
|
||||||
|
|
@@ -15,20 +15,20 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import LoginPage = require('./pages/adf/loginPage');
|
import LoginPage = require('../pages/adf/loginPage');
|
||||||
import ProcessServicesPage = require('./pages/adf/process_services/processServicesPage');
|
import ProcessServicesPage = require('../pages/adf/process_services/processServicesPage');
|
||||||
import TasksPage = require('./pages/adf/process_services/tasksPage');
|
import TasksPage = require('../pages/adf/process_services/tasksPage');
|
||||||
|
|
||||||
import CONSTANTS = require('./util/constants');
|
import CONSTANTS = require('../util/constants');
|
||||||
|
|
||||||
import Tenant = require('./models/APS/Tenant');
|
import Tenant = require('../models/APS/Tenant');
|
||||||
|
|
||||||
import TestConfig = require('./test.config');
|
import TestConfig = require('../test.config');
|
||||||
import resources = require('./util/resources');
|
import resources = require('../util/resources');
|
||||||
|
|
||||||
import AlfrescoApi = require('alfresco-js-api-node');
|
import AlfrescoApi = require('alfresco-js-api-node');
|
||||||
import { UsersActions } from './actions/users.actions';
|
import { UsersActions } from '../actions/users.actions';
|
||||||
import { AppsActions } from './actions/APS/apps.actions';
|
import { AppsActions } from '../actions/APS/apps.actions';
|
||||||
|
|
||||||
import path = require('path');
|
import path = require('path');
|
||||||
import Util = require('./util/util');
|
import Util = require('./util/util');
|
@@ -15,19 +15,19 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import TestConfig = require('./test.config');
|
import TestConfig = require('../test.config');
|
||||||
import resources = require('./util/resources');
|
import resources = require('../util/resources');
|
||||||
import LoginPage = require('./pages/adf/loginPage');
|
import LoginPage = require('../pages/adf/loginPage');
|
||||||
import NavigationBarPage = require('./pages/adf/navigationBarPage');
|
import NavigationBarPage = require('../pages/adf/navigationBarPage');
|
||||||
import ProcessServicesPage = require('./pages/adf/process_services/processServicesPage');
|
import ProcessServicesPage = require('../pages/adf/process_services/processServicesPage');
|
||||||
import TasksPage = require('./pages/adf/process_services/tasksPage');
|
import TasksPage = require('../pages/adf/process_services/tasksPage');
|
||||||
import TasksListPage = require('./pages/adf/process_services/tasksListPage');
|
import TasksListPage = require('../pages/adf/process_services/tasksListPage');
|
||||||
import TaskFiltersPage = require('./pages/adf/process_services/taskFiltersPage');
|
import TaskFiltersPage = require('../pages/adf/process_services/taskFiltersPage');
|
||||||
import TaskDetailsPage = require('./pages/adf/process_services/taskDetailsPage');
|
import TaskDetailsPage = require('../pages/adf/process_services/taskDetailsPage');
|
||||||
|
|
||||||
import AlfrescoApi = require('alfresco-js-api-node');
|
import AlfrescoApi = require('alfresco-js-api-node');
|
||||||
import { AppsActions } from './actions/APS/apps.actions';
|
import { AppsActions } from '../actions/APS/apps.actions';
|
||||||
import { UsersActions } from './actions/users.actions';
|
import { UsersActions } from '../actions/users.actions';
|
||||||
|
|
||||||
describe('Task Filters Test', () => {
|
describe('Task Filters Test', () => {
|
||||||
|
|
46
lib/core/context-menu/animations.ts
Normal file
46
lib/core/context-menu/animations.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2016 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
state,
|
||||||
|
style,
|
||||||
|
animate,
|
||||||
|
transition,
|
||||||
|
query,
|
||||||
|
group,
|
||||||
|
sequence,
|
||||||
|
AnimationStateMetadata,
|
||||||
|
AnimationTransitionMetadata
|
||||||
|
} from '@angular/animations';
|
||||||
|
|
||||||
|
export const contextMenuAnimation: ( AnimationStateMetadata | AnimationTransitionMetadata)[] = [
|
||||||
|
state('void', style({
|
||||||
|
opacity: 0,
|
||||||
|
transform: 'scale(0.01, 0.01)'
|
||||||
|
})),
|
||||||
|
transition('void => *', sequence([
|
||||||
|
query('.mat-menu-content', style({ opacity: 0 })),
|
||||||
|
animate('100ms linear', style({ opacity: 1, transform: 'scale(1, 0.5)' })),
|
||||||
|
group([
|
||||||
|
query('.mat-menu-content', animate('400ms cubic-bezier(0.55, 0, 0.55, 0.2)',
|
||||||
|
style({ opacity: 1 })
|
||||||
|
)),
|
||||||
|
animate('300ms cubic-bezier(0.25, 0.8, 0.25, 1)', style({ transform: 'scale(1, 1)' }))
|
||||||
|
])
|
||||||
|
])),
|
||||||
|
transition('* => void', animate('150ms 50ms linear', style({ opacity: 0 })))
|
||||||
|
];
|
101
lib/core/context-menu/context-menu-list.component.ts
Normal file
101
lib/core/context-menu/context-menu-list.component.ts
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2016 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Component, ViewEncapsulation, HostListener, AfterViewInit,
|
||||||
|
Optional, Inject, QueryList, ViewChildren
|
||||||
|
} from '@angular/core';
|
||||||
|
import { trigger } from '@angular/animations';
|
||||||
|
import { DOWN_ARROW, UP_ARROW } from '@angular/cdk/keycodes';
|
||||||
|
import { FocusKeyManager } from '@angular/cdk/a11y';
|
||||||
|
import { MatMenuItem } from '@angular/material';
|
||||||
|
import { ContextMenuOverlayRef } from './context-menu-overlay';
|
||||||
|
import { contextMenuAnimation } from './animations';
|
||||||
|
import { CONTEXT_MENU_DATA } from './context-menu.tokens';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'adf-context-menu',
|
||||||
|
template: `
|
||||||
|
<div mat-menu class="mat-menu-panel" @panelAnimation>
|
||||||
|
<div id="adf-context-menu-content" class="mat-menu-content">
|
||||||
|
<ng-container *ngFor="let link of links">
|
||||||
|
<button *ngIf="link.model?.visible"
|
||||||
|
[attr.data-automation-id]="'context-'+((link.title || link.model?.title) | translate)"
|
||||||
|
mat-menu-item
|
||||||
|
[disabled]="link.model?.disabled"
|
||||||
|
(click)="onMenuItemClick($event, link)">
|
||||||
|
<mat-icon *ngIf="link.model?.icon">{{ link.model.icon }}</mat-icon>
|
||||||
|
<span>{{ (link.title || link.model?.title) | translate }}</span>
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
host: {
|
||||||
|
role: 'menu',
|
||||||
|
class: 'adf-context-menu'
|
||||||
|
},
|
||||||
|
encapsulation: ViewEncapsulation.None,
|
||||||
|
animations: [
|
||||||
|
trigger('panelAnimation', contextMenuAnimation)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class ContextMenuListComponent implements AfterViewInit {
|
||||||
|
private keyManager: FocusKeyManager<MatMenuItem>;
|
||||||
|
@ViewChildren(MatMenuItem) items: QueryList<MatMenuItem>;
|
||||||
|
links: any[];
|
||||||
|
|
||||||
|
@HostListener('document:keydown.Escape', ['$event'])
|
||||||
|
handleKeydownEscape(event: KeyboardEvent) {
|
||||||
|
if (event) {
|
||||||
|
this.contextMenuOverlayRef.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('document:keydown', ['$event'])
|
||||||
|
handleKeydownEvent(event: KeyboardEvent) {
|
||||||
|
if (event) {
|
||||||
|
const keyCode = event.keyCode;
|
||||||
|
if (keyCode === UP_ARROW || keyCode === DOWN_ARROW) {
|
||||||
|
this.keyManager.onKeydown(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(ContextMenuOverlayRef) private contextMenuOverlayRef: ContextMenuOverlayRef,
|
||||||
|
@Optional() @Inject(CONTEXT_MENU_DATA) private data: any
|
||||||
|
) {
|
||||||
|
this.links = this.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMenuItemClick(event: Event, menuItem: any) {
|
||||||
|
if (menuItem && menuItem.model && menuItem.model.disabled) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
menuItem.subject.next(menuItem);
|
||||||
|
this.contextMenuOverlayRef.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
this.keyManager = new FocusKeyManager<MatMenuItem>(this.items);
|
||||||
|
this.keyManager.setFirstItemActive();
|
||||||
|
}
|
||||||
|
}
|
129
lib/core/context-menu/context-menu-overlay.service.ts
Normal file
129
lib/core/context-menu/context-menu-overlay.service.ts
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2016 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable, Injector, ElementRef, ComponentRef } from '@angular/core';
|
||||||
|
import { Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
|
||||||
|
import { PortalInjector, ComponentPortal } from '@angular/cdk/portal';
|
||||||
|
import { ContextMenuOverlayRef } from './context-menu-overlay';
|
||||||
|
import { ContextMenuOverlayConfig } from './interfaces';
|
||||||
|
import { CONTEXT_MENU_DATA } from './context-menu.tokens';
|
||||||
|
import { ContextMenuListComponent } from './context-menu-list.component';
|
||||||
|
|
||||||
|
const DEFAULT_CONFIG: ContextMenuOverlayConfig = {
|
||||||
|
panelClass: 'cdk-overlay-pane',
|
||||||
|
backdropClass: 'cdk-overlay-transparent-backdrop',
|
||||||
|
hasBackdrop: true
|
||||||
|
};
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ContextMenuOverlayService {
|
||||||
|
|
||||||
|
constructor( private injector: Injector, private overlay: Overlay) { }
|
||||||
|
|
||||||
|
open(config: ContextMenuOverlayConfig): ContextMenuOverlayRef {
|
||||||
|
const overlayConfig = { ...DEFAULT_CONFIG, ...config };
|
||||||
|
|
||||||
|
const overlay = this.createOverlay(overlayConfig);
|
||||||
|
|
||||||
|
const overlayRef = new ContextMenuOverlayRef(overlay);
|
||||||
|
|
||||||
|
this.attachDialogContainer(overlay, config, overlayRef);
|
||||||
|
|
||||||
|
overlay.backdropClick().subscribe(() => overlayRef.close());
|
||||||
|
|
||||||
|
// prevent native contextmenu on overlay element if config.hasBackdrop is true
|
||||||
|
if (overlayConfig.hasBackdrop) {
|
||||||
|
(<any> overlay)._backdropElement
|
||||||
|
.addEventListener('contextmenu', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
(<any> overlay)._backdropClick.next(null);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return overlayRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createOverlay(config: ContextMenuOverlayConfig): OverlayRef {
|
||||||
|
const overlayConfig = this.getOverlayConfig(config);
|
||||||
|
return this.overlay.create(overlayConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
private attachDialogContainer(overlay: OverlayRef, config: ContextMenuOverlayConfig, contextMenuOverlayRef: ContextMenuOverlayRef) {
|
||||||
|
const injector = this.createInjector(config, contextMenuOverlayRef);
|
||||||
|
|
||||||
|
const containerPortal = new ComponentPortal(ContextMenuListComponent, null, injector);
|
||||||
|
const containerRef: ComponentRef<ContextMenuListComponent> = overlay.attach(containerPortal);
|
||||||
|
|
||||||
|
return containerRef.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createInjector(config: ContextMenuOverlayConfig, contextMenuOverlayRef: ContextMenuOverlayRef): PortalInjector {
|
||||||
|
const injectionTokens = new WeakMap();
|
||||||
|
|
||||||
|
injectionTokens.set(ContextMenuOverlayRef, contextMenuOverlayRef);
|
||||||
|
injectionTokens.set(CONTEXT_MENU_DATA, config.data);
|
||||||
|
|
||||||
|
return new PortalInjector(this.injector, injectionTokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getOverlayConfig(config: ContextMenuOverlayConfig): OverlayConfig {
|
||||||
|
const { clientY, clientX } = config.source;
|
||||||
|
|
||||||
|
const fakeElement: any = {
|
||||||
|
getBoundingClientRect: (): ClientRect => ({
|
||||||
|
bottom: clientY,
|
||||||
|
height: 0,
|
||||||
|
left: clientX,
|
||||||
|
right: clientX,
|
||||||
|
top: clientY,
|
||||||
|
width: 0
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const positionStrategy = this.overlay.position()
|
||||||
|
.connectedTo(
|
||||||
|
new ElementRef(fakeElement),
|
||||||
|
{ originX: 'start', originY: 'bottom' },
|
||||||
|
{ overlayX: 'start', overlayY: 'top' })
|
||||||
|
.withFallbackPosition(
|
||||||
|
{ originX: 'start', originY: 'top' },
|
||||||
|
{ overlayX: 'start', overlayY: 'bottom' })
|
||||||
|
.withFallbackPosition(
|
||||||
|
{ originX: 'end', originY: 'top' },
|
||||||
|
{ overlayX: 'start', overlayY: 'top' })
|
||||||
|
.withFallbackPosition(
|
||||||
|
{ originX: 'start', originY: 'top' },
|
||||||
|
{ overlayX: 'end', overlayY: 'top' })
|
||||||
|
.withFallbackPosition(
|
||||||
|
{ originX: 'end', originY: 'center' },
|
||||||
|
{ overlayX: 'start', overlayY: 'center' })
|
||||||
|
.withFallbackPosition(
|
||||||
|
{ originX: 'start', originY: 'center' },
|
||||||
|
{ overlayX: 'end', overlayY: 'center' }
|
||||||
|
);
|
||||||
|
|
||||||
|
const overlayConfig = new OverlayConfig({
|
||||||
|
hasBackdrop: config.hasBackdrop,
|
||||||
|
backdropClass: config.backdropClass,
|
||||||
|
panelClass: config.panelClass,
|
||||||
|
scrollStrategy: this.overlay.scrollStrategies.close(),
|
||||||
|
positionStrategy
|
||||||
|
});
|
||||||
|
|
||||||
|
return overlayConfig;
|
||||||
|
}
|
||||||
|
}
|
27
lib/core/context-menu/context-menu-overlay.ts
Normal file
27
lib/core/context-menu/context-menu-overlay.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2016 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { OverlayRef } from '@angular/cdk/overlay';
|
||||||
|
|
||||||
|
export class ContextMenuOverlayRef {
|
||||||
|
|
||||||
|
constructor(private overlayRef: OverlayRef) { }
|
||||||
|
|
||||||
|
close(): void {
|
||||||
|
this.overlayRef.dispose();
|
||||||
|
}
|
||||||
|
}
|
@@ -1,72 +0,0 @@
|
|||||||
/*!
|
|
||||||
* @license
|
|
||||||
* Copyright 2016 Alfresco Software, Ltd.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { ContextMenuDirective } from './context-menu.directive';
|
|
||||||
import { ContextMenuService } from './context-menu.service';
|
|
||||||
|
|
||||||
describe('ContextMenuDirective', () => {
|
|
||||||
|
|
||||||
let contextMenuService;
|
|
||||||
let directive;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
contextMenuService = new ContextMenuService();
|
|
||||||
directive = new ContextMenuDirective(contextMenuService);
|
|
||||||
directive.enabled = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show menu via service', (done) => {
|
|
||||||
contextMenuService.show.subscribe(() => {
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
directive.links = [{}];
|
|
||||||
directive.onShowContextMenu(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should prevent default behavior', () => {
|
|
||||||
let event = new MouseEvent('click');
|
|
||||||
spyOn(event, 'preventDefault').and.callThrough();
|
|
||||||
|
|
||||||
directive.onShowContextMenu(event);
|
|
||||||
expect(event.preventDefault).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should forward event to service', () => {
|
|
||||||
let event = new MouseEvent('click');
|
|
||||||
|
|
||||||
contextMenuService.show.subscribe(e => {
|
|
||||||
expect(e.event).toBeDefined();
|
|
||||||
expect(e.event).toBe(event);
|
|
||||||
});
|
|
||||||
|
|
||||||
directive.onShowContextMenu(event);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should forward menu items to service', () => {
|
|
||||||
let links = [{}, {}];
|
|
||||||
directive.links = links;
|
|
||||||
|
|
||||||
contextMenuService.show.subscribe(e => {
|
|
||||||
expect(e.obj).toBeDefined();
|
|
||||||
expect(e.obj).toBe(links);
|
|
||||||
});
|
|
||||||
|
|
||||||
directive.onShowContextMenu(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
@@ -18,7 +18,7 @@
|
|||||||
/* tslint:disable:no-input-rename */
|
/* tslint:disable:no-input-rename */
|
||||||
|
|
||||||
import { Directive, HostListener, Input } from '@angular/core';
|
import { Directive, HostListener, Input } from '@angular/core';
|
||||||
import { ContextMenuService } from './context-menu.service';
|
import { ContextMenuOverlayService } from './context-menu-overlay.service';
|
||||||
|
|
||||||
// @deprecated 2.3.0 context-menu tag removed
|
// @deprecated 2.3.0 context-menu tag removed
|
||||||
@Directive({
|
@Directive({
|
||||||
@@ -33,8 +33,7 @@ export class ContextMenuDirective {
|
|||||||
@Input('context-menu-enabled')
|
@Input('context-menu-enabled')
|
||||||
enabled: boolean = false;
|
enabled: boolean = false;
|
||||||
|
|
||||||
constructor(private _contextMenuService: ContextMenuService) {
|
constructor(private contextMenuService: ContextMenuOverlayService) {}
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('contextmenu', ['$event'])
|
@HostListener('contextmenu', ['$event'])
|
||||||
onShowContextMenu(event?: MouseEvent) {
|
onShowContextMenu(event?: MouseEvent) {
|
||||||
@@ -44,9 +43,10 @@ export class ContextMenuDirective {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.links && this.links.length > 0) {
|
if (this.links && this.links.length > 0) {
|
||||||
if (this._contextMenuService) {
|
this.contextMenuService.open({
|
||||||
this._contextMenuService.show.next({event: event, obj: this.links});
|
source: event,
|
||||||
}
|
data: this.links
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,6 +22,7 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||||||
|
|
||||||
import { ContextMenuHolderComponent } from './context-menu-holder.component';
|
import { ContextMenuHolderComponent } from './context-menu-holder.component';
|
||||||
import { ContextMenuDirective } from './context-menu.directive';
|
import { ContextMenuDirective } from './context-menu.directive';
|
||||||
|
import { ContextMenuListComponent } from './context-menu-list.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -31,11 +32,15 @@ import { ContextMenuDirective } from './context-menu.directive';
|
|||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
ContextMenuHolderComponent,
|
ContextMenuHolderComponent,
|
||||||
ContextMenuDirective
|
ContextMenuDirective,
|
||||||
|
ContextMenuListComponent
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
ContextMenuHolderComponent,
|
ContextMenuHolderComponent,
|
||||||
ContextMenuDirective
|
ContextMenuDirective
|
||||||
|
],
|
||||||
|
entryComponents: [
|
||||||
|
ContextMenuListComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class ContextMenuModule {}
|
export class ContextMenuModule {}
|
||||||
|
174
lib/core/context-menu/context-menu.spec.ts
Normal file
174
lib/core/context-menu/context-menu.spec.ts
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2016 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||||
|
import { ContextMenuModule } from './context-menu.module';
|
||||||
|
import { CoreModule } from '../core.module';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'adf-test-component',
|
||||||
|
template: `
|
||||||
|
<div id="target" [context-menu]="actions" [context-menu-enabled]="true"></div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class TestComponent {
|
||||||
|
actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ContextMenuDirective', () => {
|
||||||
|
let fixture: ComponentFixture<TestComponent>;
|
||||||
|
const actions = [
|
||||||
|
{
|
||||||
|
model: {
|
||||||
|
visible: false,
|
||||||
|
title: 'Action 1'
|
||||||
|
},
|
||||||
|
subject: {
|
||||||
|
next: jasmine.createSpy('next')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: {
|
||||||
|
visible: true,
|
||||||
|
disabled: true,
|
||||||
|
title: 'Action 2',
|
||||||
|
icon: null
|
||||||
|
},
|
||||||
|
subject: {
|
||||||
|
next: jasmine.createSpy('next')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: {
|
||||||
|
visible: true,
|
||||||
|
disabled: false,
|
||||||
|
title: 'Action 3',
|
||||||
|
icon: 'action-icon-3'
|
||||||
|
},
|
||||||
|
subject: {
|
||||||
|
next: jasmine.createSpy('next')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: {
|
||||||
|
visible: true,
|
||||||
|
disabled: false,
|
||||||
|
title: 'Action 4',
|
||||||
|
icon: 'action-icon-4'
|
||||||
|
},
|
||||||
|
subject: {
|
||||||
|
next: jasmine.createSpy('next')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
CoreModule.forRoot(),
|
||||||
|
ContextMenuModule,
|
||||||
|
NoopAnimationsModule
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
TestComponent
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(TestComponent);
|
||||||
|
fixture.componentInstance.actions = actions;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not render contextmenu when action was not performed', () => {
|
||||||
|
const containerElement = fixture.debugElement.nativeElement.parentElement;
|
||||||
|
expect(containerElement.querySelector('.adf-context-menu button')).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Events', () => {
|
||||||
|
let targetElement: HTMLElement;
|
||||||
|
let contextMenu: HTMLElement;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
targetElement = fixture.debugElement.nativeElement.querySelector('#target');
|
||||||
|
targetElement.dispatchEvent(new CustomEvent('contextmenu'));
|
||||||
|
fixture.detectChanges();
|
||||||
|
contextMenu = document.querySelector('.adf-context-menu');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show menu on mouse contextmenu event', () => {
|
||||||
|
expect(contextMenu).not.toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set DOM element reference on menu open event', () => {
|
||||||
|
expect(contextMenu.className).toContain('adf-context-menu');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reset DOM element reference on Escape event', () => {
|
||||||
|
const event = new KeyboardEvent('keydown', {
|
||||||
|
bubbles : true, cancelable : true, key : 'Escape'
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelector('.cdk-overlay-backdrop').dispatchEvent(event);
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(document.querySelector('.adf-context-menu')).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Contextmenu list', () => {
|
||||||
|
let targetElement: HTMLElement;
|
||||||
|
let contextMenu: HTMLElement;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
targetElement = fixture.debugElement.nativeElement.querySelector('#target');
|
||||||
|
targetElement.dispatchEvent(new CustomEvent('contextmenu'));
|
||||||
|
fixture.detectChanges();
|
||||||
|
contextMenu = document.querySelector('.adf-context-menu');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not render item with visibility property set to false', () => {
|
||||||
|
expect(contextMenu.querySelectorAll('button').length).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render item as disabled when `disabled` property is set to true', () => {
|
||||||
|
expect(contextMenu.querySelectorAll('button')[0].disabled).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set first not disabled item as active', () => {
|
||||||
|
expect(document.activeElement.querySelector('mat-icon').innerHTML).toContain('action-icon-3');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not allow action event when item is disabled', () => {
|
||||||
|
contextMenu.querySelectorAll('button')[0].click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(fixture.componentInstance.actions[1].subject.next).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should perform action when item is not disabled', () => {
|
||||||
|
contextMenu.querySelectorAll('button')[1].click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(fixture.componentInstance.actions[2].subject.next).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not render item icon if not set', () => {
|
||||||
|
expect(contextMenu.querySelectorAll('button')[0].querySelector('mat-icon')).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
20
lib/core/context-menu/context-menu.tokens.ts
Normal file
20
lib/core/context-menu/context-menu.tokens.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2016 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { InjectionToken } from '@angular/core';
|
||||||
|
|
||||||
|
export const CONTEXT_MENU_DATA = new InjectionToken<any>('CONTEXT_MENU_DATA');
|
24
lib/core/context-menu/interfaces.ts
Normal file
24
lib/core/context-menu/interfaces.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2016 Alfresco Software, Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ContextMenuOverlayConfig {
|
||||||
|
panelClass?: string;
|
||||||
|
hasBackdrop?: boolean;
|
||||||
|
backdropClass?: string;
|
||||||
|
source?: MouseEvent;
|
||||||
|
data?: any;
|
||||||
|
}
|
@@ -85,6 +85,7 @@ import { startupServiceFactory } from './services/startup-service-factory';
|
|||||||
import { SortingPickerModule } from './sorting-picker/sorting-picker.module';
|
import { SortingPickerModule } from './sorting-picker/sorting-picker.module';
|
||||||
import { AppConfigService } from './app-config/app-config.service';
|
import { AppConfigService } from './app-config/app-config.service';
|
||||||
import { ContextMenuService } from './context-menu/context-menu.service';
|
import { ContextMenuService } from './context-menu/context-menu.service';
|
||||||
|
import { ContextMenuOverlayService } from './context-menu/context-menu-overlay.service';
|
||||||
import { ActivitiContentService } from './form/services/activiti-alfresco.service';
|
import { ActivitiContentService } from './form/services/activiti-alfresco.service';
|
||||||
import { EcmModelService } from './form/services/ecm-model.service';
|
import { EcmModelService } from './form/services/ecm-model.service';
|
||||||
import { FormRenderingService } from './form/services/form-rendering.service';
|
import { FormRenderingService } from './form/services/form-rendering.service';
|
||||||
@@ -138,6 +139,7 @@ export function providers() {
|
|||||||
DatePipe,
|
DatePipe,
|
||||||
AppConfigService,
|
AppConfigService,
|
||||||
ContextMenuService,
|
ContextMenuService,
|
||||||
|
ContextMenuOverlayService,
|
||||||
ActivitiContentService,
|
ActivitiContentService,
|
||||||
EcmModelService,
|
EcmModelService,
|
||||||
FormRenderingService,
|
FormRenderingService,
|
||||||
|
Reference in New Issue
Block a user