mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[ADF-2859] fixes for the conditional visibility and disabled states (#3465)
* fixes for the conditional visibility and disabled states * update docs * cleanup code * remove unused code
This commit is contained in:
committed by
Eugenio Romano
parent
1d69f5c407
commit
6f2cbdf697
@@ -279,13 +279,13 @@
|
|||||||
</data-columns>
|
</data-columns>
|
||||||
|
|
||||||
<content-actions>
|
<content-actions>
|
||||||
|
<!-- Conditional actions demo -->
|
||||||
<content-action
|
<content-action
|
||||||
target="all"
|
target="all"
|
||||||
title="Action for 'custom' node"
|
title="Action for 'custom' node"
|
||||||
[disabled]="isCustomActionDisabled"
|
[disabled]="isCustomActionDisabled"
|
||||||
(execute)="runCustomAction($event)">
|
(execute)="runCustomAction($event)">
|
||||||
</content-action>
|
</content-action>
|
||||||
<!-- Conditional actions demo -->
|
|
||||||
<content-action
|
<content-action
|
||||||
icon="get_app"
|
icon="get_app"
|
||||||
title="Download this file now!"
|
title="Download this file now!"
|
||||||
@@ -298,12 +298,6 @@
|
|||||||
handler="download"
|
handler="download"
|
||||||
[visible]="false">
|
[visible]="false">
|
||||||
</content-action>
|
</content-action>
|
||||||
<content-action
|
|
||||||
icon="get_app"
|
|
||||||
title="This can be toggled"
|
|
||||||
handler="download"
|
|
||||||
[visible]="showCustomDownloadAction">
|
|
||||||
</content-action>
|
|
||||||
<!-- common actions -->
|
<!-- common actions -->
|
||||||
<content-action
|
<content-action
|
||||||
icon="get_app"
|
icon="get_app"
|
||||||
@@ -448,12 +442,6 @@
|
|||||||
</mat-slide-toggle>
|
</mat-slide-toggle>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
|
||||||
<mat-slide-toggle color="primary" [(ngModel)]="showCustomDownloadAction">
|
|
||||||
Toggle custom download action
|
|
||||||
</mat-slide-toggle>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<mat-slide-toggle [color]="'primary'" [(ngModel)]="multiselect">{{'DOCUMENT_LIST.MULTISELECT_CHECKBOXES' |
|
<mat-slide-toggle [color]="'primary'" [(ngModel)]="multiselect">{{'DOCUMENT_LIST.MULTISELECT_CHECKBOXES' |
|
||||||
translate}}
|
translate}}
|
||||||
|
@@ -170,9 +170,6 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
@ViewChild(InfinitePaginationComponent)
|
@ViewChild(InfinitePaginationComponent)
|
||||||
infinitePaginationComponent: InfinitePaginationComponent;
|
infinitePaginationComponent: InfinitePaginationComponent;
|
||||||
|
|
||||||
@Input()
|
|
||||||
showCustomDownloadAction = false;
|
|
||||||
|
|
||||||
permissionsStyle: PermissionStyleModel[] = [];
|
permissionsStyle: PermissionStyleModel[] = [];
|
||||||
infiniteScrolling: boolean;
|
infiniteScrolling: boolean;
|
||||||
supportedPages: number[];
|
supportedPages: number[];
|
||||||
@@ -514,7 +511,7 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
canDownloadNode = (node: MinimalNodeEntity): boolean => {
|
canDownloadNode = (node: MinimalNodeEntity): boolean => {
|
||||||
if (node && node.entry && node.entry.name === 'For Sale.docx') {
|
if (node && node.entry && node.entry.name === 'custom') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@@ -324,12 +324,10 @@ allow the item being copied/moved to be the destination if it is itself a folder
|
|||||||
|
|
||||||
### Conditional visibility
|
### Conditional visibility
|
||||||
|
|
||||||
The Content-action component allows you to control visibility with the help of the `visible`
|
The `<content-action>` component allows you to control visibility with the help of the `visible` property and supports the following scenarios:
|
||||||
property which can receive its value in three main ways:
|
|
||||||
|
|
||||||
- direct `boolean` value
|
- direct value of `boolean` type
|
||||||
- binding to a `boolean` property
|
- binding to a property of the `Function` type that evaluates condition and returns `boolean` value
|
||||||
- binding to a `Function` property that evaluates the condition and returns a `boolean` value
|
|
||||||
|
|
||||||
#### Using direct boolean value
|
#### Using direct boolean value
|
||||||
|
|
||||||
@@ -342,30 +340,7 @@ property which can receive its value in three main ways:
|
|||||||
</content-action>
|
</content-action>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Binding to a boolean property
|
#### Using a property of the Function type
|
||||||
|
|
||||||
```html
|
|
||||||
<content-action
|
|
||||||
icon="get_app"
|
|
||||||
title="This can be toggled"
|
|
||||||
handler="download"
|
|
||||||
[visible]="showCustomDownloadAction">
|
|
||||||
</content-action>
|
|
||||||
```
|
|
||||||
|
|
||||||
The markup above relies on the `showCustomDownloadAction` property declared in your
|
|
||||||
component class:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
export class MyComponent {
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
showCustomDownloadAction = true;
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Binding to a Function property
|
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<content-action
|
<content-action
|
||||||
@@ -409,7 +384,6 @@ funcName = (parameters): boolean => {
|
|||||||
Similar to `visible` property, it is possible to control the `disabled` state with the following scenarios:
|
Similar to `visible` property, it is possible to control the `disabled` state with the following scenarios:
|
||||||
|
|
||||||
- direct value of `boolean` type
|
- direct value of `boolean` type
|
||||||
- binding to a property of the `boolean` type
|
|
||||||
- binding to a property of the `Function` type that evaluates condition and returns `boolean` value
|
- binding to a property of the `Function` type that evaluates condition and returns `boolean` value
|
||||||
|
|
||||||
#### Using direct value of boolean type
|
#### Using direct value of boolean type
|
||||||
@@ -423,28 +397,6 @@ Similar to `visible` property, it is possible to control the `disabled` state wi
|
|||||||
</content-action>
|
</content-action>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Using a property of the boolean type
|
|
||||||
|
|
||||||
```html
|
|
||||||
<content-action
|
|
||||||
target="all"
|
|
||||||
title="Action for 'custom' node"
|
|
||||||
[disabled]="shouldDisableAction"
|
|
||||||
(execute)="runCustomAction($event)">
|
|
||||||
</content-action>
|
|
||||||
```
|
|
||||||
|
|
||||||
The markup above relies on the `shouldDisableAction` property declared at your component class level:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
export class MyComponent {
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
shouldDisableAction = true;
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Using a property of the Function type
|
#### Using a property of the Function type
|
||||||
|
|
||||||
```html
|
```html
|
||||||
|
@@ -42,4 +42,15 @@ export class ContentActionListComponent {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unregisterAction(action: ContentActionModel): boolean {
|
||||||
|
if (this.documentList && action) {
|
||||||
|
const idx = this.documentList.actions.indexOf(action);
|
||||||
|
if (idx >= 0) {
|
||||||
|
this.documentList.actions.splice(idx, 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -132,9 +132,16 @@ export class ContentActionComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
this.subscriptions.forEach(subscription => subscription.unsubscribe());
|
this.subscriptions.forEach(subscription => subscription.unsubscribe());
|
||||||
this.subscriptions = [];
|
this.subscriptions = [];
|
||||||
|
|
||||||
|
if (this.documentActionModel) {
|
||||||
|
this.unregister(this.documentActionModel);
|
||||||
this.documentActionModel = null;
|
this.documentActionModel = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.folderActionModel) {
|
||||||
|
this.unregister(this.folderActionModel);
|
||||||
this.folderActionModel = null;
|
this.folderActionModel = null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
register(model: ContentActionModel): boolean {
|
register(model: ContentActionModel): boolean {
|
||||||
if (this.list) {
|
if (this.list) {
|
||||||
@@ -143,6 +150,13 @@ export class ContentActionComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unregister(model: ContentActionModel): boolean {
|
||||||
|
if (this.list) {
|
||||||
|
return this.list.unregisterAction(model);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private generateAction(target: string): ContentActionModel {
|
private generateAction(target: string): ContentActionModel {
|
||||||
const model = new ContentActionModel({
|
const model = new ContentActionModel({
|
||||||
title: this.title,
|
title: this.title,
|
||||||
|
@@ -13,6 +13,7 @@
|
|||||||
[display]="display"
|
[display]="display"
|
||||||
[noPermission]="noPermission"
|
[noPermission]="noPermission"
|
||||||
[showHeader]="!isEmpty() && showHeader"
|
[showHeader]="!isEmpty() && showHeader"
|
||||||
|
[rowMenuCacheEnabled]="false"
|
||||||
(showRowContextMenu)="onShowRowContextMenu($event)"
|
(showRowContextMenu)="onShowRowContextMenu($event)"
|
||||||
(showRowActionsMenu)="onShowRowActionsMenu($event)"
|
(showRowActionsMenu)="onShowRowActionsMenu($event)"
|
||||||
(executeRowAction)="onExecuteRowAction($event)"
|
(executeRowAction)="onExecuteRowAction($event)"
|
||||||
|
@@ -236,6 +236,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
|||||||
private _pagination: BehaviorSubject<PaginationModel>;
|
private _pagination: BehaviorSubject<PaginationModel>;
|
||||||
private layoutPresets = {};
|
private layoutPresets = {};
|
||||||
private subscriptions: Subscription[] = [];
|
private subscriptions: Subscription[] = [];
|
||||||
|
private rowMenuCache: { [key: string]: ContentActionModel[] } = {};
|
||||||
|
|
||||||
constructor(private documentListService: DocumentListService,
|
constructor(private documentListService: DocumentListService,
|
||||||
private ngZone: NgZone,
|
private ngZone: NgZone,
|
||||||
@@ -333,6 +334,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.rowMenuCache = {};
|
||||||
this.loadLayoutPresets();
|
this.loadLayoutPresets();
|
||||||
this.data = new ShareDataTableAdapter(this.documentListService, this.thumbnailService, null, this.getDefaultSorting(), this.sortingMode);
|
this.data = new ShareDataTableAdapter(this.documentListService, this.thumbnailService, null, this.getDefaultSorting(), this.sortingMode);
|
||||||
this.data.thumbnails = this.thumbnails;
|
this.data.thumbnails = this.thumbnails;
|
||||||
@@ -444,9 +446,10 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
|||||||
}
|
}
|
||||||
|
|
||||||
getNodeActions(node: MinimalNodeEntity | any): ContentActionModel[] {
|
getNodeActions(node: MinimalNodeEntity | any): ContentActionModel[] {
|
||||||
let target = null;
|
|
||||||
|
|
||||||
if (node && node.entry) {
|
if (node && node.entry) {
|
||||||
|
let target = null;
|
||||||
|
|
||||||
if (node.entry.isFile) {
|
if (node.entry.isFile) {
|
||||||
target = 'document';
|
target = 'document';
|
||||||
} else if (node.entry.isFolder) {
|
} else if (node.entry.isFolder) {
|
||||||
@@ -454,6 +457,14 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (target) {
|
if (target) {
|
||||||
|
const actions = this.rowMenuCache[node.entry.id];
|
||||||
|
if (actions) {
|
||||||
|
actions.forEach(action => {
|
||||||
|
this.refreshAction(action, node);
|
||||||
|
});
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
let actionsByTarget = this.actions
|
let actionsByTarget = this.actions
|
||||||
.filter(entry => {
|
.filter(entry => {
|
||||||
const isVisible = (typeof entry.visible === 'function')
|
const isVisible = (typeof entry.visible === 'function')
|
||||||
@@ -465,9 +476,10 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
|||||||
.map(action => new ContentActionModel(action));
|
.map(action => new ContentActionModel(action));
|
||||||
|
|
||||||
actionsByTarget.forEach((action) => {
|
actionsByTarget.forEach((action) => {
|
||||||
action.disabled = this.isActionDisabled(action, node);
|
this.refreshAction(action, node);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.rowMenuCache[node.entry.id] = actionsByTarget;
|
||||||
return actionsByTarget;
|
return actionsByTarget;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -475,6 +487,19 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private refreshAction(action: ContentActionModel, node: MinimalNodeEntity) {
|
||||||
|
action.disabled = this.isActionDisabled(action, node);
|
||||||
|
action.visible = this.isActionVisible(action, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
private isActionVisible(action: ContentActionModel, node: MinimalNodeEntity): boolean {
|
||||||
|
if (typeof action.visible === 'function') {
|
||||||
|
return action.visible(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
return action.visible;
|
||||||
|
}
|
||||||
|
|
||||||
private isActionDisabled(action: ContentActionModel, node: MinimalNodeEntity): boolean {
|
private isActionDisabled(action: ContentActionModel, node: MinimalNodeEntity): boolean {
|
||||||
if (typeof action.disabled === 'function') {
|
if (typeof action.disabled === 'function') {
|
||||||
return action.disabled(node);
|
return action.disabled(node);
|
||||||
@@ -484,7 +509,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return action.disabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('contextmenu', ['$event'])
|
@HostListener('contextmenu', ['$event'])
|
||||||
|
@@ -29,13 +29,15 @@ import { ContextMenuService } from './context-menu.service';
|
|||||||
template: `
|
template: `
|
||||||
<button mat-button [matMenuTriggerFor]="contextMenu"></button>
|
<button mat-button [matMenuTriggerFor]="contextMenu"></button>
|
||||||
<mat-menu #contextMenu="matMenu" class="context-menu">
|
<mat-menu #contextMenu="matMenu" class="context-menu">
|
||||||
<button *ngFor="let link of links"
|
<ng-container *ngFor="let link of links">
|
||||||
|
<button *ngIf="link.model?.visible"
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
[disabled]="link.model?.disabled"
|
[disabled]="link.model?.disabled"
|
||||||
(click)="onMenuItemClick($event, link)">
|
(click)="onMenuItemClick($event, link)">
|
||||||
<mat-icon *ngIf="showIcons && link.model?.icon">{{ link.model.icon }}</mat-icon>
|
<mat-icon *ngIf="showIcons && link.model?.icon">{{ link.model.icon }}</mat-icon>
|
||||||
{{ (link.title || link.model?.title) | translate }}
|
{{ (link.title || link.model?.title) | translate }}
|
||||||
</button>
|
</button>
|
||||||
|
</ng-container>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
`
|
`
|
||||||
})
|
})
|
||||||
|
@@ -155,6 +155,9 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck,
|
|||||||
@Input()
|
@Input()
|
||||||
noPermission: boolean = false;
|
noPermission: boolean = false;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
rowMenuCacheEnabled = true;
|
||||||
|
|
||||||
noContentTemplate: TemplateRef<any>;
|
noContentTemplate: TemplateRef<any>;
|
||||||
noPermissionTemplate: TemplateRef<any>;
|
noPermissionTemplate: TemplateRef<any>;
|
||||||
loadingTemplate: TemplateRef<any>;
|
loadingTemplate: TemplateRef<any>;
|
||||||
@@ -166,6 +169,8 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck,
|
|||||||
private click$: Observable<DataRowEvent>;
|
private click$: Observable<DataRowEvent>;
|
||||||
|
|
||||||
private differ: any;
|
private differ: any;
|
||||||
|
private rowMenuCache: object = {};
|
||||||
|
|
||||||
private subscriptions: Subscription[] = [];
|
private subscriptions: Subscription[] = [];
|
||||||
private singleClickStreamSub: Subscription;
|
private singleClickStreamSub: Subscription;
|
||||||
private multiClickStreamSub: Subscription;
|
private multiClickStreamSub: Subscription;
|
||||||
@@ -297,6 +302,7 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck,
|
|||||||
private initTable() {
|
private initTable() {
|
||||||
this.data = new ObjectDataTableAdapter(this.rows, this.columns);
|
this.data = new ObjectDataTableAdapter(this.rows, this.columns);
|
||||||
this.setupData(this.data);
|
this.setupData(this.data);
|
||||||
|
this.rowMenuCache = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupData(adapter: DataTableAdapter) {
|
private setupData(adapter: DataTableAdapter) {
|
||||||
@@ -553,10 +559,19 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck,
|
|||||||
}
|
}
|
||||||
|
|
||||||
getRowActions(row: DataRow, col: DataColumn): any[] {
|
getRowActions(row: DataRow, col: DataColumn): any[] {
|
||||||
|
const id = row.getValue('id');
|
||||||
|
|
||||||
|
if (!this.rowMenuCache[id]) {
|
||||||
let event = new DataCellEvent(row, col, []);
|
let event = new DataCellEvent(row, col, []);
|
||||||
this.showRowActionsMenu.emit(event);
|
this.showRowActionsMenu.emit(event);
|
||||||
|
if (!this.rowMenuCacheEnabled) {
|
||||||
return event.value.actions;
|
return event.value.actions;
|
||||||
}
|
}
|
||||||
|
this.rowMenuCache[id] = event.value.actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.rowMenuCache[id];
|
||||||
|
}
|
||||||
|
|
||||||
onExecuteRowAction(row: DataRow, action: any) {
|
onExecuteRowAction(row: DataRow, action: any) {
|
||||||
if (action.disabled || action.disabled) {
|
if (action.disabled || action.disabled) {
|
||||||
|
Reference in New Issue
Block a user