mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-05-26 17:24:56 +00:00
[ADF-2503] conditional visibility for content actions (#3325)
* conditional visibility for content actions * fix typo * workaround for "target: all"
This commit is contained in:
parent
7154eb1e84
commit
d67f160fdc
@ -273,6 +273,25 @@
|
|||||||
</data-columns>
|
</data-columns>
|
||||||
|
|
||||||
<content-actions>
|
<content-actions>
|
||||||
|
<!-- Conditional actions demo -->
|
||||||
|
<content-action
|
||||||
|
icon="get_app"
|
||||||
|
title="Download this file now!"
|
||||||
|
handler="download"
|
||||||
|
[visible]="canDownloadNode">
|
||||||
|
</content-action>
|
||||||
|
<content-action
|
||||||
|
icon="get_app"
|
||||||
|
title="Never see this action again"
|
||||||
|
handler="download"
|
||||||
|
[visible]="false">
|
||||||
|
</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"
|
||||||
@ -417,6 +436,12 @@
|
|||||||
</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}}
|
||||||
|
@ -152,6 +152,8 @@ 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;
|
||||||
@ -491,4 +493,11 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
this.infinitePaginationComponent.reset();
|
this.infinitePaginationComponent.reset();
|
||||||
this.reloadForInfiniteScrolling();
|
this.reloadForInfiniteScrolling();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canDownloadNode = (node: MinimalNodeEntity): boolean => {
|
||||||
|
if (node && node.entry && node.entry.name === 'For Sale.docx') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,8 +90,9 @@ export class MyView {
|
|||||||
| handler | `string` | | System actions. Can be "delete", "download", "copy" or "move". |
|
| handler | `string` | | System actions. Can be "delete", "download", "copy" or "move". |
|
||||||
| icon | `string` | | The name of the icon to display next to the menu command (can be left blank). |
|
| icon | `string` | | The name of the icon to display next to the menu command (can be left blank). |
|
||||||
| permission | `string` | | The permission type. |
|
| permission | `string` | | The permission type. |
|
||||||
| target | `string` | ContentActionTarget.All | Type of item that the action appies to. Can be "document" or "folder" |
|
| target | `string` | ContentActionTarget.All | Type of item that the action applies to. Can be "document" or "folder" |
|
||||||
| title | `string` | "Action" | The title of the action as shown in the menu. |
|
| title | `string` | "Action" | The title of the action as shown in the menu. |
|
||||||
|
| visible | `boolean` or `Function` | Visibility state (see examples further in the document) |
|
||||||
|
|
||||||
### Events
|
### Events
|
||||||
|
|
||||||
@ -319,6 +320,84 @@ allow the item being copied/moved to be the destination if it is itself a folder
|
|||||||
</adf-document-list>
|
</adf-document-list>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Conditional visibility
|
||||||
|
|
||||||
|
The `<content-action>` component allows you to control visibility with the help of the `visible` property and supports three major scenarios:
|
||||||
|
|
||||||
|
* 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
|
||||||
|
|
||||||
|
#### Using direct value of boolean type
|
||||||
|
|
||||||
|
```html
|
||||||
|
<content-action
|
||||||
|
icon="get_app"
|
||||||
|
title="Never see this action again"
|
||||||
|
handler="download"
|
||||||
|
[visible]="false">
|
||||||
|
</content-action>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using a property of the boolean 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 at your component class level:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export class MyComponent {
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
showCustomDownloadAction = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using a property of the Function type
|
||||||
|
|
||||||
|
```html
|
||||||
|
<content-action
|
||||||
|
icon="get_app"
|
||||||
|
title="Download this file now!"
|
||||||
|
handler="download"
|
||||||
|
[visible]="canDownloadNode">
|
||||||
|
</content-action>
|
||||||
|
```
|
||||||
|
|
||||||
|
The code above relies on the `canDownloadNode` property of a `Function` type declared at your component class level:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export class MyComponent {
|
||||||
|
|
||||||
|
canDownloadNode = (node: MinimalNodeEntity): boolean => {
|
||||||
|
if (node && node.entry && node.entry.name === 'For Sale.docx') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Code above checks the node name, and evaluates to `true` only if corresponding node is called "For Sale.docx".
|
||||||
|
|
||||||
|
Please note that if you want to preserve `this` context within the evaluator function,
|
||||||
|
its property should be declared as a lambda one:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
functionName = (parameters): boolean => {
|
||||||
|
// implementation
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Customizing built-in actions
|
### Customizing built-in actions
|
||||||
|
|
||||||
The built-in actions are defined in the [Document Actions service](document-actions.service.md) and
|
The built-in actions are defined in the [Document Actions service](document-actions.service.md) and
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA, SimpleChange } from '@angular/core';
|
||||||
import { EventEmitter } from '@angular/core';
|
import { EventEmitter } from '@angular/core';
|
||||||
import { async, TestBed } from '@angular/core/testing';
|
import { async, TestBed } from '@angular/core/testing';
|
||||||
import { ContentService, setupTestBed } from '@alfresco/adf-core';
|
import { ContentService, setupTestBed } from '@alfresco/adf-core';
|
||||||
@ -81,6 +81,24 @@ describe('ContentAction', () => {
|
|||||||
expect(model.icon).toBe(action.icon);
|
expect(model.icon).toBe(action.icon);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should update visibility binding', () => {
|
||||||
|
let action = new ContentActionComponent(actionList, null, null);
|
||||||
|
action.target = 'document';
|
||||||
|
action.title = '<title>';
|
||||||
|
action.icon = '<icon>';
|
||||||
|
|
||||||
|
action.visible = true;
|
||||||
|
action.ngOnInit();
|
||||||
|
expect(action.documentActionModel.visible).toBeTruthy();
|
||||||
|
|
||||||
|
action.visible = false;
|
||||||
|
action.ngOnChanges({
|
||||||
|
'visible': new SimpleChange(true, false, false)
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(action.documentActionModel.visible).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
it('should get action handler from document actions service', () => {
|
it('should get action handler from document actions service', () => {
|
||||||
|
|
||||||
let handler = function () {
|
let handler = function () {
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
/* tslint:disable:component-selector */
|
/* tslint:disable:component-selector */
|
||||||
|
|
||||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
import { Component, EventEmitter, Input, OnInit, Output, OnChanges, SimpleChanges } from '@angular/core';
|
||||||
|
|
||||||
import { ContentActionHandler } from '../../models/content-action.model';
|
import { ContentActionHandler } from '../../models/content-action.model';
|
||||||
import { DocumentActionsService } from '../../services/document-actions.service';
|
import { DocumentActionsService } from '../../services/document-actions.service';
|
||||||
@ -33,7 +33,7 @@ import { ContentActionListComponent } from './content-action-list.component';
|
|||||||
FolderActionsService
|
FolderActionsService
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class ContentActionComponent implements OnInit {
|
export class ContentActionComponent implements OnInit, OnChanges {
|
||||||
|
|
||||||
/** The title of the action as shown in the menu. */
|
/** The title of the action as shown in the menu. */
|
||||||
@Input()
|
@Input()
|
||||||
@ -43,6 +43,9 @@ export class ContentActionComponent implements OnInit {
|
|||||||
@Input()
|
@Input()
|
||||||
icon: string;
|
icon: string;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
visible: boolean | Function = true;
|
||||||
|
|
||||||
/** System actions. Can be "delete", "download", "copy" or "move". */
|
/** System actions. Can be "delete", "download", "copy" or "move". */
|
||||||
@Input()
|
@Input()
|
||||||
handler: string;
|
handler: string;
|
||||||
@ -83,6 +86,9 @@ export class ContentActionComponent implements OnInit {
|
|||||||
@Output()
|
@Output()
|
||||||
success = new EventEmitter();
|
success = new EventEmitter();
|
||||||
|
|
||||||
|
documentActionModel: ContentActionModel;
|
||||||
|
folderActionModel: ContentActionModel;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private list: ContentActionListComponent,
|
private list: ContentActionListComponent,
|
||||||
private documentActions: DocumentActionsService,
|
private documentActions: DocumentActionsService,
|
||||||
@ -91,10 +97,21 @@ export class ContentActionComponent implements OnInit {
|
|||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
if (this.target === ContentActionTarget.All) {
|
if (this.target === ContentActionTarget.All) {
|
||||||
this.generateAction(ContentActionTarget.Folder);
|
this.folderActionModel = this.generateAction(ContentActionTarget.Folder);
|
||||||
this.generateAction(ContentActionTarget.Document);
|
this.documentActionModel = this.generateAction(ContentActionTarget.Document);
|
||||||
} else {
|
} else {
|
||||||
this.generateAction(this.target);
|
this.documentActionModel = this.generateAction(this.target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
|
if (changes.visible && !changes.visible.firstChange) {
|
||||||
|
if (this.documentActionModel) {
|
||||||
|
this.documentActionModel.visible = changes.visible.currentValue;
|
||||||
|
}
|
||||||
|
if (this.folderActionModel) {
|
||||||
|
this.folderActionModel.visible = changes.visible.currentValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,14 +122,15 @@ export class ContentActionComponent implements OnInit {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private generateAction(target: string) {
|
private generateAction(target: string): ContentActionModel {
|
||||||
let model = new ContentActionModel({
|
const model = new ContentActionModel({
|
||||||
title: this.title,
|
title: this.title,
|
||||||
icon: this.icon,
|
icon: this.icon,
|
||||||
permission: this.permission,
|
permission: this.permission,
|
||||||
disableWithNoPermission: this.disableWithNoPermission,
|
disableWithNoPermission: this.disableWithNoPermission,
|
||||||
target: target,
|
target: target,
|
||||||
disabled: this.disabled
|
disabled: this.disabled,
|
||||||
|
visible: this.visible
|
||||||
});
|
});
|
||||||
if (this.handler) {
|
if (this.handler) {
|
||||||
model.handler = this.getSystemHandler(target, this.handler);
|
model.handler = this.getSystemHandler(target, this.handler);
|
||||||
@ -125,6 +143,7 @@ export class ContentActionComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.register(model);
|
this.register(model);
|
||||||
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSystemHandler(target: string, name: string): ContentActionHandler {
|
getSystemHandler(target: string, name: string): ContentActionHandler {
|
||||||
|
@ -312,6 +312,48 @@ describe('DocumentList', () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not display hidden content actions', () => {
|
||||||
|
documentList.actions = [
|
||||||
|
new ContentActionModel({
|
||||||
|
target: 'document',
|
||||||
|
title: 'Action1',
|
||||||
|
visible: false
|
||||||
|
}),
|
||||||
|
new ContentActionModel({
|
||||||
|
target: 'document',
|
||||||
|
title: 'Action2',
|
||||||
|
visible: true
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
const nodeFile = { entry: { isFile: true, name: 'xyz' } };
|
||||||
|
const actions = documentList.getNodeActions(nodeFile);
|
||||||
|
|
||||||
|
expect(actions.length).toBe(1);
|
||||||
|
expect(actions[0].title).toBe('Action2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should evaluate conditional visibility for content actions', () => {
|
||||||
|
documentList.actions = [
|
||||||
|
new ContentActionModel({
|
||||||
|
target: 'document',
|
||||||
|
title: 'Action1',
|
||||||
|
visible: (): boolean => true
|
||||||
|
}),
|
||||||
|
new ContentActionModel({
|
||||||
|
target: 'document',
|
||||||
|
title: 'Action2',
|
||||||
|
visible: (): boolean => false
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
const nodeFile = { entry: { isFile: true, name: 'xyz' } };
|
||||||
|
const actions = documentList.getNodeActions(nodeFile);
|
||||||
|
|
||||||
|
expect(actions.length).toBe(1);
|
||||||
|
expect(actions[0].title).toBe('Action1');
|
||||||
|
});
|
||||||
|
|
||||||
it('should not disable the action if there is copy permission', () => {
|
it('should not disable the action if there is copy permission', () => {
|
||||||
let documentMenu = new ContentActionModel({
|
let documentMenu = new ContentActionModel({
|
||||||
disableWithNoPermission: true,
|
disableWithNoPermission: true,
|
||||||
|
@ -434,9 +434,15 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (target) {
|
if (target) {
|
||||||
let actionsByTarget = this.actions.filter(entry => {
|
let actionsByTarget = this.actions
|
||||||
return entry.target.toLowerCase() === target;
|
.filter(entry => {
|
||||||
}).map(action => new ContentActionModel(action));
|
const isVisible = (typeof entry.visible === 'function')
|
||||||
|
? entry.visible(node)
|
||||||
|
: entry.visible;
|
||||||
|
|
||||||
|
return isVisible && entry.target.toLowerCase() === target;
|
||||||
|
})
|
||||||
|
.map(action => new ContentActionModel(action));
|
||||||
|
|
||||||
actionsByTarget.forEach((action) => {
|
actionsByTarget.forEach((action) => {
|
||||||
this.disableActionsWithNoPermissions(node, action);
|
this.disableActionsWithNoPermissions(node, action);
|
||||||
|
@ -24,6 +24,7 @@ export class ContentActionModel {
|
|||||||
permission: string;
|
permission: string;
|
||||||
disableWithNoPermission: boolean = false;
|
disableWithNoPermission: boolean = false;
|
||||||
disabled: boolean = false;
|
disabled: boolean = false;
|
||||||
|
visible: boolean | Function = true;
|
||||||
|
|
||||||
constructor(obj?: any) {
|
constructor(obj?: any) {
|
||||||
if (obj) {
|
if (obj) {
|
||||||
@ -35,6 +36,10 @@ export class ContentActionModel {
|
|||||||
this.permission = obj.permission;
|
this.permission = obj.permission;
|
||||||
this.disableWithNoPermission = obj.disableWithNoPermission;
|
this.disableWithNoPermission = obj.disableWithNoPermission;
|
||||||
this.disabled = obj.disabled;
|
this.disabled = obj.disabled;
|
||||||
|
|
||||||
|
if (obj.hasOwnProperty('visible')) {
|
||||||
|
this.visible = obj.visible;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user