diff --git a/demo-shell/src/app/components/files/files.component.html b/demo-shell/src/app/components/files/files.component.html index d684d9a625..7428590744 100644 --- a/demo-shell/src/app/components/files/files.component.html +++ b/demo-shell/src/app/components/files/files.component.html @@ -273,6 +273,25 @@ + + + + + + + +
+ + Toggle custom download action + +
+
{{'DOCUMENT_LIST.MULTISELECT_CHECKBOXES' | translate}} diff --git a/demo-shell/src/app/components/files/files.component.ts b/demo-shell/src/app/components/files/files.component.ts index 7bda00ed4d..86504a4b9f 100644 --- a/demo-shell/src/app/components/files/files.component.ts +++ b/demo-shell/src/app/components/files/files.component.ts @@ -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; + } } diff --git a/docs/content-services/content-action.component.md b/docs/content-services/content-action.component.md index 2efb5ed12f..74593c7095 100644 --- a/docs/content-services/content-action.component.md +++ b/docs/content-services/content-action.component.md @@ -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 ``` +### Conditional visibility + +The `` 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 + + +``` + +#### Using a property of the boolean type + +```html + + +``` + +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 + + +``` + +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 diff --git a/lib/content-services/document-list/components/content-action/content-action.component.spec.ts b/lib/content-services/document-list/components/content-action/content-action.component.spec.ts index 6b039da92a..1b1c3503ac 100644 --- a/lib/content-services/document-list/components/content-action/content-action.component.spec.ts +++ b/lib/content-services/document-list/components/content-action/content-action.component.spec.ts @@ -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 = ''; + 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 () { diff --git a/lib/content-services/document-list/components/content-action/content-action.component.ts b/lib/content-services/document-list/components/content-action/content-action.component.ts index ed7becc777..ff6ab8eab3 100644 --- a/lib/content-services/document-list/components/content-action/content-action.component.ts +++ b/lib/content-services/document-list/components/content-action/content-action.component.ts @@ -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 { diff --git a/lib/content-services/document-list/components/document-list.component.spec.ts b/lib/content-services/document-list/components/document-list.component.spec.ts index b3af8f40e2..b863110411 100644 --- a/lib/content-services/document-list/components/document-list.component.spec.ts +++ b/lib/content-services/document-list/components/document-list.component.spec.ts @@ -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, diff --git a/lib/content-services/document-list/components/document-list.component.ts b/lib/content-services/document-list/components/document-list.component.ts index f80ca39b94..6fffb6a7d6 100644 --- a/lib/content-services/document-list/components/document-list.component.ts +++ b/lib/content-services/document-list/components/document-list.component.ts @@ -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); diff --git a/lib/content-services/document-list/models/content-action.model.ts b/lib/content-services/document-list/models/content-action.model.ts index 54e113a306..241b3da8b6 100644 --- a/lib/content-services/document-list/models/content-action.model.ts +++ b/lib/content-services/document-list/models/content-action.model.ts @@ -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; + } } } }