[ADF-2503] conditional visibility for content actions (#3325)

* conditional visibility for content actions

* fix typo

* workaround for "target: all"
This commit is contained in:
Denys Vuika 2018-05-15 16:53:52 +01:00 committed by Eugenio Romano
parent 7154eb1e84
commit d67f160fdc
8 changed files with 216 additions and 13 deletions

View File

@ -273,6 +273,25 @@
</data-columns>
<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 -->
<content-action
icon="get_app"
@ -417,6 +436,12 @@
</mat-slide-toggle>
</section>
<section>
<mat-slide-toggle color="primary" [(ngModel)]="showCustomDownloadAction">
Toggle custom download action
</mat-slide-toggle>
</section>
<section>
<mat-slide-toggle [color]="'primary'" [(ngModel)]="multiselect">{{'DOCUMENT_LIST.MULTISELECT_CHECKBOXES' |
translate}}

View File

@ -152,6 +152,8 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
@ViewChild(InfinitePaginationComponent)
infinitePaginationComponent: InfinitePaginationComponent;
@Input()
showCustomDownloadAction = false;
permissionsStyle: PermissionStyleModel[] = [];
infiniteScrolling: boolean;
@ -491,4 +493,11 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
this.infinitePaginationComponent.reset();
this.reloadForInfiniteScrolling();
}
canDownloadNode = (node: MinimalNodeEntity): boolean => {
if (node && node.entry && node.entry.name === 'For Sale.docx') {
return true;
}
return false;
}
}

View File

@ -90,8 +90,9 @@ export class MyView {
| 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). |
| 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. |
| visible | `boolean` or `Function` | Visibility state (see examples further in the document) |
### Events
@ -319,6 +320,84 @@ allow the item being copied/moved to be the destination if it is itself a folder
</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
The built-in actions are defined in the [Document Actions service](document-actions.service.md) and

View File

@ -15,7 +15,7 @@
* 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 { async, TestBed } from '@angular/core/testing';
import { ContentService, setupTestBed } from '@alfresco/adf-core';
@ -81,6 +81,24 @@ describe('ContentAction', () => {
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', () => {
let handler = function () {

View File

@ -17,7 +17,7 @@
/* 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 { DocumentActionsService } from '../../services/document-actions.service';
@ -33,7 +33,7 @@ import { ContentActionListComponent } from './content-action-list.component';
FolderActionsService
]
})
export class ContentActionComponent implements OnInit {
export class ContentActionComponent implements OnInit, OnChanges {
/** The title of the action as shown in the menu. */
@Input()
@ -43,6 +43,9 @@ export class ContentActionComponent implements OnInit {
@Input()
icon: string;
@Input()
visible: boolean | Function = true;
/** System actions. Can be "delete", "download", "copy" or "move". */
@Input()
handler: string;
@ -83,6 +86,9 @@ export class ContentActionComponent implements OnInit {
@Output()
success = new EventEmitter();
documentActionModel: ContentActionModel;
folderActionModel: ContentActionModel;
constructor(
private list: ContentActionListComponent,
private documentActions: DocumentActionsService,
@ -91,10 +97,21 @@ export class ContentActionComponent implements OnInit {
ngOnInit() {
if (this.target === ContentActionTarget.All) {
this.generateAction(ContentActionTarget.Folder);
this.generateAction(ContentActionTarget.Document);
this.folderActionModel = this.generateAction(ContentActionTarget.Folder);
this.documentActionModel = this.generateAction(ContentActionTarget.Document);
} 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;
}
private generateAction(target: string) {
let model = new ContentActionModel({
private generateAction(target: string): ContentActionModel {
const model = new ContentActionModel({
title: this.title,
icon: this.icon,
permission: this.permission,
disableWithNoPermission: this.disableWithNoPermission,
target: target,
disabled: this.disabled
disabled: this.disabled,
visible: this.visible
});
if (this.handler) {
model.handler = this.getSystemHandler(target, this.handler);
@ -125,6 +143,7 @@ export class ContentActionComponent implements OnInit {
}
this.register(model);
return model;
}
getSystemHandler(target: string, name: string): ContentActionHandler {

View File

@ -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', () => {
let documentMenu = new ContentActionModel({
disableWithNoPermission: true,

View File

@ -434,9 +434,15 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
}
if (target) {
let actionsByTarget = this.actions.filter(entry => {
return entry.target.toLowerCase() === target;
}).map(action => new ContentActionModel(action));
let actionsByTarget = this.actions
.filter(entry => {
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) => {
this.disableActionsWithNoPermissions(node, action);

View File

@ -24,6 +24,7 @@ export class ContentActionModel {
permission: string;
disableWithNoPermission: boolean = false;
disabled: boolean = false;
visible: boolean | Function = true;
constructor(obj?: any) {
if (obj) {
@ -35,6 +36,10 @@ export class ContentActionModel {
this.permission = obj.permission;
this.disableWithNoPermission = obj.disableWithNoPermission;
this.disabled = obj.disabled;
if (obj.hasOwnProperty('visible')) {
this.visible = obj.visible;
}
}
}
}